Skip to content

Commit afe7966

Browse files
A matrix compiles to a GenTensor over its elements' type, and the arithmetic on it to GenericTensor's operations (#1425)
* A matrix compiles to a GenTensor over its elements' type, and the arithmetic on it to GenericTensor's operations #526: "just run GenericTensor through our functions". Under the LINQ compiler a Matrix node is a GenTensor<T, TWrapper> over the common type of its compiled elements, filled element by element, and a sum, difference, product, quotient or whole power with a matrix in it is the tensor operation of that name -- PiecewiseAdd, PiecewiseSubtract, MatrixMultiply, the piecewise operations by a scalar converted to the element type, MatrixPower -- so that "A * B" compiled over two tensors is the matrix product and "2 A + B - 1" scales, adds and shifts. GenericTensor takes its element arithmetic from a struct and ships none for the numeric types, so DoubleOperations, SingleOperations, ComplexOperations, Int32Operations, Int64Operations and BigIntegerOperations are here, in AngouriMath.Core.Compilation.IntoLinq, and GenTensor<double, DoubleOperations> is the type to compile to and to pass a matrix in as. The methods the compiled tree calls are closed over each element type at compile time and named through expression trees, the way the protocol keeps its own tables: nothing generic is constructed at run time, which the trimming and NativeAOT analyzers refuse (#363). A matrix as a number, a number as a matrix, and a function of a matrix are UncompilableNodeException with a message that says so, where sin(A) was a KeyNotFoundException out of a type table. An expression of matrices with a scalar value still simplifies to the scalar first, as #425 made it. Closes #526. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * The compiled matrix arithmetic is plain loops over the wrapper, not GenericTensor's expression-compiled ones, so the NativeAOT gate passes The first push failed TrimmingAndAot on both platforms: GenTensor.PiecewiseAdd, MatrixMultiply and the rest build and compile an expression tree for their loops, which ILC reports as IL3050 the moment they are reachable -- and Kit.For roots them for six element types. Add, Subtract, Multiply, the scalar operations and Power are now loops over the wrapper's Add and Multiply, and Power is by repeated squaring, refusing a negative exponent. GenTensor stays the container. The smoke test compiles a matrix expression now, so the gate covers this path from here on. Published natively and run locally: 45 checks, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 86af577 commit afe7966

9 files changed

Lines changed: 750 additions & 10 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,20 @@ now, which is the one silent change: a variable spelled `forall` or `exists` no
520520
| `forall + 1` | `forall + 1`, a variable named `forall` | `UnhandledParseException` |
521521
| `exists(x)` | `exists * x` | `UnhandledParseException` |
522522

523+
### A matrix compiles
524+
525+
**Addition, not silent.** Under the LINQ compiler a `Matrix` node compiles to a
526+
`GenTensor<T, TWrapper>` over its elements' type, with the wrappers `DoubleOperations`,
527+
`SingleOperations`, `ComplexOperations`, `Int32Operations`, `Int64Operations` and
528+
`BigIntegerOperations` in `AngouriMath.Core.Compilation.IntoLinq`, and a sum, difference, product,
529+
quotient or whole power with a matrix in it to GenericTensor's operation of that name
530+
([#526](https://github.com/asc-community/AngouriMath/issues/526)).
531+
532+
| Input | Was (2.5.0) | Now |
533+
|---|---|---|
534+
| `"[[x, 2 x], [x + 1, x^2]]".ToEntity().Compile<double, GenTensor<double, DoubleOperations>>("x")` | `UncompilableNodeException` | a delegate; at `3` the matrix `[[3, 6], [4, 9]]` |
535+
| `"A * B".ToEntity().Compile<GenTensor<…>, GenTensor<…>, GenTensor<…>>("A", "B")` | `UncompilableNodeException` | the matrix product |
536+
| `"[[1, 0]] * [[a, b], [c, d]] * [[0], [1]]".ToEntity().Compile<double, double, double, double, double>("a", "b", "c", "d")` | `2` at `(1, 2, 3, 4)` — simplified to the scalar first | unchanged |
523537
### `lcm` is a function, and a congruence is solved to a residue class
524538

525539
`lcm(a, b, ...)` is a node, `Entity.Lcmf`, by the conventions of `gcd`: non-negative, `0` where

‎Sources/.editorconfig‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,12 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
267267
[AngouriMath/Functions/Continuous/ExtremumOverSet.cs]
268268
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
269269

270+
[AngouriMath/Functions/Compilation/IntoLinq/TensorCompilation.cs]
271+
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
272+
273+
[Tests/UnitTests/Common/CompileMatrixTest.cs]
274+
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
275+
270276
[AngouriMath/Functions/IntervalArithmetic.cs]
271277
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
272278

‎Sources/AngouriMath/Docs/Usage/Exceptions.md‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,24 @@ take.
154154

155155
`Compile` was asked for a function it cannot build. Two distinct causes, and the message says which:
156156

157-
- a node with no compiled form — a matrix, a derivative, an integral, a limit;
157+
- a node with no compiled form — a derivative, an integral, a limit, a function of a matrix;
158158
- a variable the expression mentions that the compilation was not given. The message names the
159159
variable and lists the ones it *was* given, since `Compile("x")` on `a * x` failing used to
160160
surface as a bare `KeyNotFoundException` about some dictionary.
161161

162162
Both compilers raise it: the stack-machine one (`Compile`) and the LINQ one
163163
(`Compile<TIn, TOut>`).
164164

165+
A matrix is not on the first list any more: under the LINQ compiler it compiles to a
166+
`GenTensor<T, TWrapper>` over its elements' type — `GenTensor<double, DoubleOperations>`,
167+
`GenTensor<Complex, ComplexOperations>`, and the same for `float`, `int`, `long`, `BigInteger`,
168+
the wrappers living in `AngouriMath.Core.Compilation.IntoLinq` — and that is the type to compile
169+
to and to pass a matrix in as: `"A * B".ToEntity().Compile<GenTensor<double, DoubleOperations>,
170+
GenTensor<double, DoubleOperations>, GenTensor<double, DoubleOperations>>("A", "B")` is the matrix
171+
product, `2 A + B - 1` scales, adds and shifts element by element, `A ^ 3` is the matrix power
172+
([#526](https://github.com/asc-community/AngouriMath/issues/526)). Compiling a matrix as a
173+
number, or a number as a matrix, or `sin(A)`, is still this exception.
174+
165175
### `CannotEvalException`
166176

167177
Something did not collapse to a single number or boolean. Overwhelmingly this is

‎Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public virtual Expression ConvertType(Expression expr, Type type)
7474

7575
if (expr.Type == type)
7676
return expr;
77+
// A tensor converts to nothing but itself: a matrix over one element type is not
78+
// a matrix over another, and it is not a number.
79+
if (TensorCompilation.IsTensor(expr.Type) || TensorCompilation.IsTensor(type))
80+
throw new UncompilableNodeException($"A value of type {expr.Type} cannot be compiled as {type}");
7781

7882
bool exprNullable = (Nullable.GetUnderlyingType(expr.Type) is not null) || (expr.Type == typeof(object));
7983
bool typeNullable = (Nullable.GetUnderlyingType(type) is not null) || (type == typeof(object));
@@ -107,7 +111,11 @@ public virtual Expression ConvertType(Expression expr, Type type)
107111
/// </summary>
108112
public virtual Expression ConvertUnaryNode(Expression e, Entity typeHolder)
109113
{
110-
114+
// A function of a matrix has no compiled form here: the tensor operations are the
115+
// arithmetic ones, and sin(A) is not an element-by-element sine of a matrix.
116+
if (TensorCompilation.IsTensor(e.Type))
117+
throw new UncompilableNodeException(
118+
$"There is no compiled form for {typeHolder.GetType().Name} of a matrix.");
111119
return typeHolder switch
112120
{
113121
Sinf when ShouldBeAtLeastDouble(e) is var newE => Expression.Call(GetDef("Sin", 1, newE.Type), newE),
@@ -155,7 +163,13 @@ private Expression ShouldBeAtLeastDouble(Expression expr)
155163
/// </summary>
156164
public virtual Expression ConvertBinaryNode(Expression left, Expression right, Entity typeHolder)
157165
{
158-
166+
// A matrix in either place: the tensor operation of the same name, the scalar side
167+
// converted to the element type. https://github.com/asc-community/AngouriMath/issues/526
168+
if (TensorCompilation.IsTensor(left.Type) || TensorCompilation.IsTensor(right.Type))
169+
return TensorCompilation.Binary(typeHolder, left, right, ConvertType)
170+
?? throw new UncompilableNodeException(
171+
$"The node of type {typeHolder.GetType()} has no compiled form over a matrix.");
172+
159173
(left, right) = EqualizeTypesIfAble(left, right);
160174
return typeHolder switch
161175
{
@@ -214,13 +228,30 @@ public virtual Expression ConvertOtherNode(IEnumerable<Expression> en, Entity ty
214228
return typeHolder switch
215229
{
216230
Piecewise => HandlePiecewise(en),
231+
// A matrix is a tensor over its elements' common type, built from the compiled
232+
// elements in row-major order, which is the order DirectChildren gives them.
233+
// https://github.com/asc-community/AngouriMath/issues/526
234+
Entity.Matrix matrix => HandleMatrix(matrix, en),
217235
// TODO: finite set -> hash set
218236
_ => throw new UncompilableNodeException(
219237
$"The node of type {typeHolder.GetType()} does not support compilation.")
220238
//FiniteSet => Expression.
221239
};
222240
}
223241

242+
private Expression HandleMatrix(Entity.Matrix matrix, IEnumerable<Expression> en)
243+
{
244+
var elements = en.ToArray();
245+
if (elements.Length == 0)
246+
throw new UncompilableNodeException("An empty matrix has no compiled form");
247+
var common = elements[0].Type;
248+
foreach (var element in elements)
249+
common = MaxType(Nullable.GetUnderlyingType(common) ?? common, Nullable.GetUnderlyingType(element.Type) ?? element.Type);
250+
for (var i = 0; i < elements.Length; i++)
251+
elements[i] = ConvertType(elements[i], common);
252+
return TensorCompilation.Build(matrix, elements);
253+
}
254+
224255
private Expression HandlePiecewise(IEnumerable<Expression> en)
225256
{
226257
Expression[] children = en.ToArray();

0 commit comments

Comments
 (0)