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: 12 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,18 @@ are now also found wherever the `1` and the square sit in a sum, under the condi
| `"1 + tan(x)^2 - sec(x)^2".Simplify()` | `1 + tan(x) ^ 2 - sec(x) ^ 2` | `0 provided not cos(x) = 0` |
| `"a + 1 + tan(y)^2 - sec(y)^2".Simplify()` | `a + 1 + tan(y) ^ 2 - sec(y) ^ 2` | `a provided not cos(y) = 0` |

### A scalar over a matrix argument is not compiled

**Not silent: what compiled to a wrong value now throws `UncompilableNodeException`.** A scalar over a matrix that is an
argument of the compiled function was compiled as the scalar over every element, where evaluation takes it as the scalar
times the inverse. The inverse is not compiled, as a negative matrix power is not, so the quotient is refused. A matrix
written into the expression is inverted before it is compiled, as before
([#2287](https://github.com/asc-community/AngouriMath/issues/2287)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"x / A"`, compiled over `GenTensor<double>` and called at `A = [[1, 2], [3, 4]]`, `x = 2` | `[[2, 1], [0.667, 0.5]]` | throws `UncompilableNodeException` |

### A square system is not answered with rows that are no solution

**Silent: a row that is no solution is gone, and a system with a row that fails for some values is `null`.**
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Common/ListArgumentOverloadTest.cs]
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

[Tests/UnitTests/Common/AScalarOverAMatrixArgumentTest.cs]
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

[Tests/UnitTests/Core/Multithreading/SettingsAcrossAsync.cs]
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,13 @@ private sealed class Kit
internal MethodInfo SubtractFromScalar { get; }
internal MethodInfo MultiplyScalar { get; }
internal MethodInfo DivideScalar { get; }
internal MethodInfo DivideFromScalar { get; }
internal MethodInfo Power { get; }

private Kit(Type tensor, MethodInfo empty, MethodInfo set, MethodInfo add, MethodInfo subtract, MethodInfo multiply,
MethodInfo addScalar, MethodInfo subtractScalar, MethodInfo subtractFromScalar, MethodInfo multiplyScalar,
MethodInfo divideScalar, MethodInfo divideFromScalar, MethodInfo power)
=> (Tensor, Empty, Set, Add, Subtract, Multiply, AddScalar, SubtractScalar, SubtractFromScalar, MultiplyScalar, DivideScalar, DivideFromScalar, Power)
= (tensor, empty, set, add, subtract, multiply, addScalar, subtractScalar, subtractFromScalar, multiplyScalar, divideScalar, divideFromScalar, power);
MethodInfo divideScalar, MethodInfo power)
=> (Tensor, Empty, Set, Add, Subtract, Multiply, AddScalar, SubtractScalar, SubtractFromScalar, MultiplyScalar, DivideScalar, Power)
= (tensor, empty, set, add, subtract, multiply, addScalar, subtractScalar, subtractFromScalar, multiplyScalar, divideScalar, power);

// An expression tree is how C# names a method without a string: the call inside it
// is resolved at compile time, closed over T and TWrapper, and rooted for the trimmer.
Expand All @@ -274,7 +273,6 @@ internal static Kit For<T, TWrapper>() where TWrapper : struct, IOperations<T>
Named<Func<T, GenTensor<T, TWrapper>, GenTensor<T, TWrapper>>>((s, a) => TensorCompilation.SubtractFromScalar(s, a)),
Named<Func<GenTensor<T, TWrapper>, T, GenTensor<T, TWrapper>>>((a, s) => TensorCompilation.MultiplyScalar(a, s)),
Named<Func<GenTensor<T, TWrapper>, T, GenTensor<T, TWrapper>>>((a, s) => TensorCompilation.DivideScalar(a, s)),
Named<Func<T, GenTensor<T, TWrapper>, GenTensor<T, TWrapper>>>((s, a) => TensorCompilation.DivideFromScalar(s, a)),
Named<Func<GenTensor<T, TWrapper>, int, GenTensor<T, TWrapper>>>((a, n) => TensorCompilation.Power(a, n)));
}

Expand Down Expand Up @@ -380,6 +378,10 @@ public static GenTensor<T, TWrapper> MultiplyScalar<T, TWrapper>(GenTensor<T, TW
public static GenTensor<T, TWrapper> DivideScalar<T, TWrapper>(GenTensor<T, TWrapper> a, T scalar) where TWrapper : struct, IOperations<T>
=> Elementwise(a, x => default(TWrapper).Divide(x, scalar));
/// <summary>The scalar over every element.</summary>
/// <remarks>
/// Not what a scalar over a matrix means, which is the scalar times the inverse, and so not what the compiler
/// makes of one. https://github.com/asc-community/AngouriMath/issues/2287
/// </remarks>
public static GenTensor<T, TWrapper> DivideFromScalar<T, TWrapper>(T scalar, GenTensor<T, TWrapper> a) where TWrapper : struct, IOperations<T>
=> Elementwise(a, x => default(TWrapper).Divide(scalar, x));
/// <summary>The matrix power, a non-negative whole exponent, by repeated squaring.</summary>
Expand Down Expand Up @@ -452,7 +454,12 @@ internal static Expression Build(Entity.Matrix matrix, IReadOnlyList<Expression>
Entity.Sumf => Expression.Call(rightKit!.AddScalar, right, value),
Entity.Minusf => Expression.Call(rightKit!.SubtractFromScalar, value, right),
Entity.Mulf => Expression.Call(rightKit!.MultiplyScalar, right, value),
Entity.Divf => Expression.Call(rightKit!.DivideFromScalar, value, right),
// A scalar over a matrix is the scalar times its inverse, as evaluation has it, and the
// inverse is not compiled, as a negative power is not: dividing by every element
// instead gave 2 / [[1, 2], [3, 4]] as [[2, 1], [2/3, 1/2]].
// https://github.com/asc-community/AngouriMath/issues/2287
Entity.Divf => throw new UncompilableNodeException(
"A scalar over a matrix is the scalar times its inverse, which is not compiled: invert the matrix first"),
_ => null,
};
}
Expand Down
47 changes: 47 additions & 0 deletions Sources/Tests/UnitTests/Common/AScalarOverAMatrixArgumentTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Copyright (c) 2019-2026 Angouri.
// AngouriMath is licensed under MIT.
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
// Website: https://am.angouri.org.
//

using AngouriMath;
using AngouriMath.Core.Compilation.IntoLinq;
using AngouriMath.Core.Exceptions;
using AngouriMath.Extensions;
using GenericTensor.Core;
using Xunit;

namespace AngouriMath.Tests.Common
{
/// <summary>
/// A scalar over a matrix argument is not compiled, where it was compiled as the scalar over every element: evaluation
/// takes it as the scalar times the inverse, and the inverse is not compiled, as a negative matrix power is not. A matrix
/// written into the expression is inverted before it is compiled, and that is unchanged.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2287">#2287</a>
/// </summary>
[Trait("Area", "Common")]
public sealed class AScalarOverAMatrixArgumentTest
{
[Fact]
public void ItIsRefused()
=> Assert.Throws<UncompilableNodeException>(() => "x / A".ToEntity()
.Compile<GenTensor<double, DoubleOperations>, double, GenTensor<double, DoubleOperations>>("A", "x"));

[Fact]
public void AMatrixArgumentOverAScalarIsStillCompiled()
{
var matrix = GenTensor<double, DoubleOperations>.CreateMatrix(new double[,] { { 1, 2 }, { 3, 4 } });
var divided = "A / x".ToEntity()
.Compile<GenTensor<double, DoubleOperations>, double, GenTensor<double, DoubleOperations>>("A", "x")(matrix, 2);
Assert.Equal(new[] { 0.5, 1, 1.5, 2 }, new[] { divided[0, 0], divided[0, 1], divided[1, 0], divided[1, 1] });
}

[Fact]
public void AWrittenMatrixIsInvertedAsBefore()
{
var inverse = "x / [[1, 0], [2, 4]]".ToEntity().Compile<double, GenTensor<double, DoubleOperations>>("x")(1);
Assert.Equal(new[] { 1, 0, -0.5, 0.25 }, new[] { inverse[0, 0], inverse[0, 1], inverse[1, 0], inverse[1, 1] });
}
}
}
Loading