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
23 changes: 23 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ A not-equal written `<>` is read as `not (a = b)`, and printed that way.

---

## Unreleased — since 2.7.0

### `gcd` and `lcm` compile at whole arguments

**Not silent: what was refused is compiled.** `gcd` and `lcm` were refused by both compilers with
`UncompilableNodeException`. They compile now, exactly at whole arguments, rounded once, and to `NaN` at any other.
Evaluation takes them over the rationals too, but a compiled argument is a double, and the double nearest `0.1` is not
`1/10`, so a compiled value there would be exact for the wrong number. Both are non-negative, as evaluation has them
([#2195](https://github.com/asc-community/AngouriMath/issues/2195)).

| Input | Was (2.7.0) | Now |
|---|---|---|
| `"gcd(x, 6)".ToEntity().Compile("x").Call(4)` | `UncompilableNodeException` | `(2, 0)` |
| `"lcm(x, 6)".ToEntity().Compile<double, double>("x")(4)` | `UncompilableNodeException` | `12` |
| `"gcd(x, 6)".ToEntity().Compile<double, double>("x")(2.5)` | `UncompilableNodeException` | `NaN` |

The calculus nodes and the binders — `derivative`, `integral`, `limit`, `sum`, `product`, an extremum over a set,
`lambda` and `apply` — are still refused, by both compilers with `UncompilableNodeException` as before; the message now
names the node and says to compile `.Evaled` or `Simplify()` instead. The LINQ compiler said the bound variable was not
among the arguments.

---

## 2.7.0 — since 2.6.0

**Read these first: names that read differently.** Each of these was read as something else on 2.6.0. Code that used
Expand Down
9 changes: 9 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,15 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[AngouriMath/Functions/Compilation/StatementPositions.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

[AngouriMath/Functions/Compilation/Binders.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/GcdLcmCompilationTest.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/BinderCompilationRefusalTest.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/StatementInANumericPositionTest.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
5 changes: 4 additions & 1 deletion Sources/AngouriMath/Docs/Usage/Exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ take.

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

- a node with no compiled form — a derivative, an integral, a limit, a comparison of a matrix;
- a node with no compiled form — a comparison of a matrix, or a calculus node or a binder: a
derivative, an integral, a limit, a sum, a product, an extremum over a set, a lambda and its
application. For those the message says to call `.Evaled` or `Simplify()` first and compile the
result, which is computed once there rather than at every call;
- a variable the expression mentions that the compilation was not given. The message names the
variable and lists the ones it *was* given, since `Compile("x")` on `a * x` failing used to
surface as a bare `KeyNotFoundException` about some dictionary.
Expand Down
53 changes: 53 additions & 0 deletions Sources/AngouriMath/Functions/Compilation/Binders.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// 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.Core.Exceptions;
using static AngouriMath.Entity;

namespace AngouriMath.Core.Compilation
{
/// <summary>
/// The calculus nodes and the binders -- a derivative, an integral, a limit, a sum, a product, an extremum over a
/// set, a lambda and its application -- have no compiled form, and both compilers refuse them alike. Each is
/// computed by evaluation, which compiling them would have to repeat at every call, as slowly as evaluating; so the
/// caller evaluates once and compiles what comes back. The LINQ compiler reached the bound variable first and said
/// it was not among the arguments, which named the symptom rather than the node.
/// https://github.com/asc-community/AngouriMath/issues/2195
/// </summary>
internal static class Binders
{
internal static void Check(Entity expression)
{
foreach (var node in expression.Nodes)
if (Kind(node) is { } kind)
throw new UncompilableNodeException(
$"`{node.Stringize()}` is {kind}, which has no compiled form. "
+ (node is Lambda
? "Apply it to an argument, call .Evaled or Simplify() on that, and compile the result."
: "Call .Evaled or Simplify() on the expression first, and compile the result."));
}

private static string? Kind(Entity node) => node switch
{
Derivativef => "a derivative",
Integralf { Range: null } => "an integral",
Integralf => "a definite integral",
Limitf => "a limit",
Summationf => "a sum",
Productf => "a product",
SumOverSetf => "a sum over a set",
Maximumf => "a maximum over a set",
Minimumf => "a minimum over a set",
Argmaxf => "the set of points where a maximum is taken",
Argminf => "the set of points where a minimum is taken",
CalculusOperator => "a binder",
Application => "an application of a function",
Lambda => "a function rather than a number",
_ => null
};
}
}
45 changes: 27 additions & 18 deletions Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,24 +340,6 @@ private protected override void CompileNode(Compiler compiler)
}
}

public partial record Derivativef
{
private protected override void CompileNode(Compiler compiler) =>
throw new UncompilableNodeException($"A derivative cannot be compiled: `{Stringize()}`");
}

public partial record Integralf
{
private protected override void CompileNode(Compiler compiler) =>
throw new UncompilableNodeException($"An integral cannot be compiled: `{Stringize()}`");
}

public partial record Limitf
{
private protected override void CompileNode(Compiler compiler) =>
throw new UncompilableNodeException($"A limit cannot be compiled: `{Stringize()}`");
}

// The floors, the rounding and the extremes, which the solver's numerical search needs
// compiled to run on `floor(x) = x/2 + 1/3` or `max(x, 1) = 2 x` at all.
// https://github.com/asc-community/AngouriMath/issues/1603
Expand Down Expand Up @@ -761,6 +743,32 @@ private protected override void CompileNode(Compiler compiler)
}
}

// The gcd and the lcm, exact at whole arguments and NaN at any other.
// https://github.com/asc-community/AngouriMath/issues/2195
public partial record Gcdf
{
private protected override bool CompilesItself => true;

private protected override void CompileNode(Compiler compiler)
{
Right.InnerCompile(compiler);
Left.InnerCompile(compiler);
compiler.Instructions.Add(new(InstructionType.CALL_GCD));
}
}

public partial record Lcmf
{
private protected override bool CompilesItself => true;

private protected override void CompileNode(Compiler compiler)
{
Right.InnerCompile(compiler);
Left.InnerCompile(compiler);
compiler.Instructions.Add(new(InstructionType.CALL_LCM));
}
}

// A membership whose set the compilers rewrite into a statement about the element is gone by the time
// a node is compiled, and what remains is one in a set of numbers, decided where evaluation decides it.
// https://github.com/asc-community/AngouriMath/issues/2195
Expand Down Expand Up @@ -1076,6 +1084,7 @@ internal static FastExpression Compile(Entity func, IEnumerable<Variable> variab
func = func.InnerSimplified;
func = Compilation.CompiledMembership.Rewritten(func);
func = Functions.ExponentialQuotients.Rewritten(func);
Compilation.Binders.Check(func);
Compilation.StatementPositions.Check(func);
var visited = new HashSet<Entity>();
var cache = new Dictionary<Entity, int>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ internal enum InstructionType
CALL_HARMONIC_NUMBER,
CALL_DIVISOR_SIGMA,
CALL_VALUATION,
CALL_GCD,
CALL_LCM,
CALL_DIVIDES,

// 3-arg: whether two numbers are congruent modulo a third.
Expand Down Expand Up @@ -418,6 +420,12 @@ public System.Numerics.Complex Substitute(params System.Numerics.Complex[] value
case InstructionType.CALL_VALUATION:
stack.Push(AngouriMath.Numerics.NumberTheoreticFunctions.Valuation(stack.Pop(), stack.Pop()));
break;
case InstructionType.CALL_GCD:
stack.Push(AngouriMath.Numerics.NumberTheoreticFunctions.Gcd(stack.Pop(), stack.Pop()));
break;
case InstructionType.CALL_LCM:
stack.Push(AngouriMath.Numerics.NumberTheoreticFunctions.Lcm(stack.Pop(), stack.Pop()));
break;
case InstructionType.CALL_MEMBER:
stack.Push(Truth(AngouriMath.Numerics.NumberStatements.In(stack.Pop(), instruction.Reference)));
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,11 @@ DivisorSigmaf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeast
=> Expression.Call(GetDef("DivisorSigma", 2, newLeft.Type), newLeft, newRight),
Valuationf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeastDouble(right) is var newRight
=> Expression.Call(GetDef("Valuation", 2, newLeft.Type), newLeft, newRight),
// Exact at whole arguments and NaN at any other. https://github.com/asc-community/AngouriMath/issues/2195
Gcdf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeastDouble(right) is var newRight
=> Expression.Call(GetDef("Gcd", 2, newLeft.Type), newLeft, newRight),
Lcmf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeastDouble(right) is var newRight
=> Expression.Call(GetDef("Lcm", 2, newLeft.Type), newLeft, newRight),
Dividesf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeastDouble(right) is var newRight
=> newLeft.Type == typeof(System.Numerics.Complex)
? Expression.Call(complexDivisibility, newLeft, ConvertType(newRight, typeof(System.Numerics.Complex)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ internal static TDelegate Compile<TDelegate>(
expr = TensorCompilation.SimplifiedAroundMatrixArguments(expr, IsMatrixArgument);
expr = CompiledMembership.Rewritten(expr);
expr = Functions.ExponentialQuotients.Rewritten(expr);
Binders.Check(expr);
StatementPositions.Check(expr);
// Two matrices whose shapes are written into the expression and do not fit are refused here, rather than by
// a delegate that throws at every point it is called at. https://github.com/asc-community/AngouriMath/issues/526
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,8 @@ public static System.Numerics.Complex Round(System.Numerics.Complex a)
public static System.Numerics.Complex HarmonicNumber(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.PolygammaFunction.HarmonicNumber(a, b);
public static double HarmonicNumber(double a, double b) => AngouriMath.Numerics.PolygammaFunction.HarmonicNumber(a, b);

// The functions of a factorisation, the valuation, the n-th prime and the prime-counting function, by the
// interpreter's routines. https://github.com/asc-community/AngouriMath/issues/2195
// The functions of a factorisation, the valuation, the n-th prime, the prime-counting function, the gcd and
// the lcm, by the interpreter's routines. https://github.com/asc-community/AngouriMath/issues/2195
public static System.Numerics.Complex Mobius(System.Numerics.Complex a) => AngouriMath.Numerics.NumberTheoreticFunctions.Mobius(a);
public static double Mobius(double a) => AngouriMath.Numerics.NumberTheoreticFunctions.Mobius(a);
public static System.Numerics.Complex PrimeNu(System.Numerics.Complex a) => AngouriMath.Numerics.NumberTheoreticFunctions.PrimeNu(a);
Expand All @@ -385,6 +385,10 @@ public static System.Numerics.Complex Round(System.Numerics.Complex a)
public static double DivisorSigma(double a, double b) => AngouriMath.Numerics.NumberTheoreticFunctions.DivisorSigma(a, b);
public static System.Numerics.Complex Valuation(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.NumberTheoreticFunctions.Valuation(a, b);
public static double Valuation(double a, double b) => AngouriMath.Numerics.NumberTheoreticFunctions.Valuation(a, b);
public static System.Numerics.Complex Gcd(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.NumberTheoreticFunctions.Gcd(a, b);
public static double Gcd(double a, double b) => AngouriMath.Numerics.NumberTheoreticFunctions.Gcd(a, b);
public static System.Numerics.Complex Lcm(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.NumberTheoreticFunctions.Lcm(a, b);
public static double Lcm(double a, double b) => AngouriMath.Numerics.NumberTheoreticFunctions.Lcm(a, b);

/// <summary>
/// True where <c>System.Numerics.Complex.IsNaN</c> is, spelled out because that overload is
Expand Down Expand Up @@ -602,6 +606,10 @@ public static bool IsNaN(System.Numerics.Complex a)
{ ("DivisorSigma", 2, typeof(double)), Def<double>((a, b) => DivisorSigma(a, b)) },
{ ("Valuation", 2, typeof(System.Numerics.Complex)), Def<System.Numerics.Complex>((a, b) => Valuation(a, b)) },
{ ("Valuation", 2, typeof(double)), Def<double>((a, b) => Valuation(a, b)) },
{ ("Gcd", 2, typeof(System.Numerics.Complex)), Def<System.Numerics.Complex>((a, b) => Gcd(a, b)) },
{ ("Gcd", 2, typeof(double)), Def<double>((a, b) => Gcd(a, b)) },
{ ("Lcm", 2, typeof(System.Numerics.Complex)), Def<System.Numerics.Complex>((a, b) => Lcm(a, b)) },
{ ("Lcm", 2, typeof(double)), Def<double>((a, b) => Lcm(a, b)) },
};

// An expression tree is how C# spells "the MethodInfo of this method" without a string:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
var combinatorialBinary = new[]{ "Binomial", "FallingFactorial", "RisingFactorial" };
// The number-theoretic functions, exact at a whole argument and NaN at any other.
var numberTheoretic = new[]{ "Mobius", "PrimeNu", "PrimeOmega", "Prime", "PrimePi" };
var numberTheoreticBinary = new[]{ "DivisorSigma", "Valuation" };
var numberTheoreticBinary = new[]{ "DivisorSigma", "Valuation", "Gcd", "Lcm" };
var types = new[]{ "System.Numerics.Complex", "double", "float", "long", "int", "BigInteger" };
#>
//
Expand Down Expand Up @@ -266,8 +266,8 @@ namespace AngouriMath.Core.Compilation.IntoLinq
public static System.Numerics.Complex HarmonicNumber(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.PolygammaFunction.HarmonicNumber(a, b);
public static double HarmonicNumber(double a, double b) => AngouriMath.Numerics.PolygammaFunction.HarmonicNumber(a, b);

// The functions of a factorisation, the valuation, the n-th prime and the prime-counting function, by the
// interpreter's routines. https://github.com/asc-community/AngouriMath/issues/2195
// The functions of a factorisation, the valuation, the n-th prime, the prime-counting function, the gcd and
// the lcm, by the interpreter's routines. https://github.com/asc-community/AngouriMath/issues/2195
<# foreach (var name in numberTheoretic) { #>
public static System.Numerics.Complex <#= name #>(System.Numerics.Complex a) => AngouriMath.Numerics.NumberTheoreticFunctions.<#= name #>(a);
public static double <#= name #>(double a) => AngouriMath.Numerics.NumberTheoreticFunctions.<#= name #>(a);
Expand Down
Loading
Loading