Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,8 @@ public Entity AsScalar()
// GenericTensor's Laplace determinant writes into a process-wide scratch
// matrix, so two threads here silently corrupt each other's minors. See
// Functions.GenTensorGuard.
lock (Functions.GenTensorGuard.ScratchPool)
return @this.InnerMatrix.DeterminantLaplace().InnerSimplified;
return Functions.GenTensorGuard
.DeterminantLaplace(@this.InnerMatrix).InnerSimplified;
},
this
);
Expand All @@ -323,8 +323,7 @@ public Entity AsScalar()
return null;
// Inverting goes through the adjugate, which takes its minors in the same
// process-wide scratch matrix the determinant does. See Functions.GenTensorGuard.
lock (Functions.GenTensorGuard.ScratchPool)
cp.InvertMatrix();
Functions.GenTensorGuard.InvertMatrix(cp);
return ToMatrix(new Matrix(cp).InnerSimplified);
}, this);
private LazyPropertyA<Matrix?> inverse;
Expand Down Expand Up @@ -498,10 +497,7 @@ public IEnumerator<Entity> GetEnumerator()
return null;
// The adjugate takes every minor in one process-wide scratch matrix.
// See Functions.GenTensorGuard.
Entity innerSimplified;
lock (Functions.GenTensorGuard.ScratchPool)
innerSimplified = new Matrix(@this.InnerMatrix.Adjoint()).InnerSimplified;
return ToMatrix(innerSimplified);
return ToMatrix(Functions.GenTensorGuard.Adjoint(@this.InnerMatrix));
},
this);
private LazyPropertyA<Matrix?> adjugate;
Expand Down
42 changes: 41 additions & 1 deletion Sources/AngouriMath/Functions/GenTensorGuard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,47 @@ internal static class GenTensorGuard
/// goes through the adjugate. <c>DeterminantGaussianSafeDivision</c> and
/// <c>MatrixMultiply</c> do not touch the pool and are deliberately not covered.
/// </summary>
[ConstantField] internal static readonly object ScratchPool = new object();
/// <remarks>
/// It is private, and the three operations below are the whole surface, because mutual
/// exclusion here is not a property any one caller can hold up: a single call that skips
/// the lock corrupts every call that takes it, and reads corrupted minors back. Exposing
/// the lock lets a caller reach GenericTensor without it; exposing only the operations
/// does not.
/// </remarks>
[ConstantField] private static readonly object ScratchPool = new object();

/// <summary>Laplace expansion, serialised on <see cref="ScratchPool"/>.</summary>
internal static Entity DeterminantLaplace(GenTensor matrix)
{
lock (ScratchPool)
return matrix.DeterminantLaplace();
}

/// <summary>
/// The adjugate, serialised on <see cref="ScratchPool"/>, and returned already read out
/// of the tensor rather than as one.
/// </summary>
/// <remarks>
/// Reading it out is what the lock has to cover, and returning a <c>GenTensor</c> would
/// put that read on the far side of the lock: <c>Entity.Matrix</c>'s constructor copies,
/// so the copy is where the elements are actually touched, and nothing here documents
/// whether the adjugate is a fresh tensor or the pool's own.
/// </remarks>
internal static Entity Adjoint(GenTensor matrix)
{
lock (ScratchPool)
return new Entity.Matrix(matrix.Adjoint()).InnerSimplified;
}

/// <summary>
/// Inversion in place, serialised on <see cref="ScratchPool"/> -- it goes through the
/// adjugate, so it takes its minors in the same pool.
/// </summary>
internal static void InvertMatrix(GenTensor matrix)
{
lock (ScratchPool)
matrix.InvertMatrix();
}

[ConstantField] private static readonly Lazy<bool> piecewiseCache =
new Lazy<bool>(WarmPiecewiseCache, LazyThreadSafetyMode.ExecutionAndPublication);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,13 @@ public void EliminationAgreesWithLaplaceWhereverBothApply()
if (ByElimination(matrix) is not { } byElimination)
continue;
compared++;
var byLaplace = matrix.InnerMatrix.DeterminantLaplace().InnerSimplified;
// Through the guard rather than straight to GenericTensor, whose Laplace
// expansion takes its minors in a process-wide scratch matrix. xUnit runs test
// classes in parallel and MatrixConcurrencyTest expands forty matrices across
// every core, so an unguarded call here reads that test's minors and reports the
// determinant of an integer matrix as containing sin(a).
var byLaplace = GenTensorGuard.DeterminantLaplace(matrix.InnerMatrix)
.InnerSimplified;
if ((byElimination - byLaplace).Simplify() != 0)
disagreements.Add($"{matrix.Stringize()}: "
+ $"{byElimination.Stringize()} vs {byLaplace.Stringize()}");
Expand Down
Loading