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
16 changes: 16 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,22 @@ interpreter leaves them as written ([#2195](https://github.com/asc-community/Ang
| `"valuation(x, 2)".ToEntity().Compile("x").Call(12)` | `UncompilableNodeException` | `(2, 0)` |
| `"valuation(x, 2)".ToEntity().Compile<double, double>("x")(0)` | `UncompilableNodeException` | `Infinity` |

### Membership and divisibility compile

**Not silent: what was refused is compiled.** `x in S` and `a divides b` were refused by both compilers with
`UncompilableNodeException`. A membership compiles to the truth value evaluation gives, `1`, `0` or `NaN` in
FastExpression and `true`, `false` or `null` in the LINQ compiler. The set is fixed when the expression is compiled:
a union, an intersection and a difference by Kleene's connectives, an interval by comparing the element with its ends,
a finite set by equality with its members, a conditional set by its predicate, and `RR`, `CC`, `ZZ`, `ZZ+`, `ZZ*` and
`PP` as evaluation decides them. Membership in `QQ` stays refused, since every finite double is a rational and
`pi in QQ` is `False`, and so does membership in `BB`. A divisibility holds or fails between whole numbers and has no
truth value at any other ([#2195](https://github.com/asc-community/AngouriMath/issues/2195)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"x in [0; 1]".ToEntity().Compile("x").Call(0.5)` | `UncompilableNodeException` | `(1, 0)` |
| `"3 divides x".ToEntity().Compile("x").Call(6)` | `UncompilableNodeException` | `(1, 0)` |

### `mod` compiles over `Complex`

**Not silent: what was refused is compiled.** `Compile<Complex, ...>` refused `mod` with `UncompilableNodeException`,
Expand Down
9 changes: 9 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,18 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[AngouriMath/Numerics/NumberTheoreticFunctions.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/Numerics/NumberStatements.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/CompiledMembership.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/NumberTheoreticCompilationTest.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/MembershipCompilationTest.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/CompileMatrixTest.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
49 changes: 49 additions & 0 deletions Sources/AngouriMath/Functions/Compilation/CompiledMembership.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// 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 System.Linq;
using NumberStatements = AngouriMath.Numerics.NumberStatements;
using static AngouriMath.Entity;
using static AngouriMath.Entity.Set;

namespace AngouriMath.Core.Compilation
{
/// <summary>
/// A membership written, for the compilers, as the statement it makes about its element; the set is fixed
/// when the expression is compiled, so only the element is computed. In a union, an intersection or a
/// difference, Kleene's connectives of the memberships in its sides, as evaluation decides them; in an
/// interval, the element real and between the ends; in a finite set, equal to one of its members; and in a
/// conditional set, the set's predicate of it. A membership in a set of numbers is left, for each compiler to
/// compile itself, and one in any other set, or with any other set inside, is left as written, which neither
/// compiles.
/// https://github.com/asc-community/AngouriMath/issues/2195
/// </summary>
internal static class CompiledMembership
{
internal static Entity Rewritten(Entity expression)
=> expression.Nodes.Any(node => node is Inf)
? expression.Replace(node => node is Inf(var element, var set) && Statement(element, set) is { } statement ? statement : node)
: expression;

private static Entity? Statement(Entity element, Entity set) => set switch
{
Unionf(var left, var right) => Statement(element, left) is { } inLeft && Statement(element, right) is { } inRight ? inLeft | inRight : null,
Intersectionf(var left, var right) => Statement(element, left) is { } inLeft && Statement(element, right) is { } inRight ? inLeft & inRight : null,
SetMinusf(var left, var right) => Statement(element, left) is { } inLeft && Statement(element, right) is { } inRight ? inLeft & !inRight : null,
FiniteSet finite => finite.Count == 0 ? Boolean.False : finite.Select(member => element.Equalizes(member)).Aggregate((one, another) => one | another),
// A number that is not real is in no interval, which the comparisons alone would leave with no truth
// value: i in [0; 1] is False.
Interval(var from, var fromClosed, var to, var toClosed)
=> new Equalsf(new Imf(element), Number.Integer.Zero)
& (fromClosed ? new LessOrEqualf(from, element) : (Entity)new Lessf(from, element))
& (toClosed ? new LessOrEqualf(element, to) : (Entity)new Lessf(element, to)),
ConditionalSet(var variable, var predicate) => predicate.Substitute(variable, element),
SpecialSet special when NumberStatements.Of(special) is not null => new Inf(element, special),
_ => null
};
}
}
36 changes: 36 additions & 0 deletions Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,41 @@ private protected override void CompileNode(Compiler compiler)
}
}

// 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
public partial record Set
{
public partial record Inf
{
private protected override bool CompilesItself => SupSet is SpecialSet special && AngouriMath.Numerics.NumberStatements.Of(special) is not null;

private protected override void CompileNode(Compiler compiler)
{
if (SupSet is not SpecialSet special || AngouriMath.Numerics.NumberStatements.Of(special) is not { } set)
{
base.CompileNode(compiler);
return;
}
Element.InnerCompile(compiler);
compiler.Instructions.Add(new(InstructionType.CALL_MEMBER, (int)set));
}
}
}

// Divisibility of whole numbers, with no truth value at any other.
public partial record Dividesf
{
private protected override bool CompilesItself => true;

private protected override void CompileNode(Compiler compiler)
{
Dividend.InnerCompile(compiler);
Divisor.InnerCompile(compiler);
compiler.Instructions.Add(new(InstructionType.CALL_DIVIDES));
}
}

public partial record Maxf
{
private protected override bool CompilesItself => true;
Expand Down Expand Up @@ -966,6 +1001,7 @@ internal static FastExpression Compile(Entity func, IEnumerable<Variable> variab
varNamespace[varName] = id++;
foreach (var constant in Variable.NamedConstants.Values)
func = func.Substitute(constant, constant.Value);
func = Compilation.CompiledMembership.Rewritten(func);
var visited = new HashSet<Entity>();
var cache = new Dictionary<Entity, int>();
foreach (var node in func.Nodes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ internal enum InstructionType
CALL_PRIME_OMEGA,
CALL_PRIME,
CALL_PRIME_PI,
// Membership in the set of numbers its reference names, a NumberStatements.NumberSet.
CALL_MEMBER,

// 2-arg functions
CALL_SUM = 100,
Expand Down Expand Up @@ -92,6 +94,7 @@ internal enum InstructionType
CALL_HARMONIC_NUMBER,
CALL_DIVISOR_SIGMA,
CALL_VALUATION,
CALL_DIVIDES,

// 3-arg: a value where its predicate holds, and the one below it elsewhere.
CALL_SELECT = 150,
Expand Down Expand Up @@ -396,6 +399,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_MEMBER:
stack.Push(Truth(AngouriMath.Numerics.NumberStatements.In(stack.Pop(), instruction.Reference)));
break;
case InstructionType.CALL_DIVIDES:
stack.Push(Truth(AngouriMath.Numerics.NumberStatements.Divides(stack.Pop(), stack.Pop())));
break;
case InstructionType.CALL_CATALAN_NUMBER:
stack.Push(AngouriMath.Numerics.CombinatorialFunctions.CatalanNumber(stack.Pop()));
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ 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),
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)))
: Expression.Call(realDivisibility, ConvertType(newLeft, typeof(double)), ConvertType(newRight, typeof(double))),
Maxf => Expression.Call(GetDef("Max", 2, left.Type), left, right),
Minf => Expression.Call(GetDef("Min", 2, left.Type), left, right),

Expand Down Expand Up @@ -485,6 +489,29 @@ private static MethodInfo Conv<TFrom, TTo>(Expression<Func<TFrom, TTo>> e)
{ (ExpressionType.GreaterThanOrEqual, typeof(BigInteger)), Cmp<BigInteger>((a, b) => a >= b) },
};

// Membership in a set of numbers and divisibility, with no truth value where evaluation has none.
// https://github.com/asc-community/AngouriMath/issues/2195
[ConstantField] private static readonly MethodInfo complexMembership =
((MethodCallExpression)((Expression<Func<System.Numerics.Complex, int, bool?>>)((z, set) => AngouriMath.Numerics.NumberStatements.In(z, set))).Body).Method;
[ConstantField] private static readonly MethodInfo realMembership =
((MethodCallExpression)((Expression<Func<double, int, bool?>>)((x, set) => AngouriMath.Numerics.NumberStatements.In(x, set))).Body).Method;
[ConstantField] private static readonly MethodInfo complexDivisibility =
((MethodCallExpression)((Expression<Func<System.Numerics.Complex, System.Numerics.Complex, bool?>>)((a, b) => AngouriMath.Numerics.NumberStatements.Divides(a, b))).Body).Method;
[ConstantField] private static readonly MethodInfo realDivisibility =
((MethodCallExpression)((Expression<Func<double, double, bool?>>)((a, b) => AngouriMath.Numerics.NumberStatements.Divides(a, b))).Body).Method;

/// <summary>
/// Membership of a compiled element in a set of numbers, over <see cref="System.Numerics.Complex"/> where the
/// element is one and over <see cref="double"/> otherwise.
/// </summary>
internal Expression ConvertMembership(Expression element, AngouriMath.Numerics.NumberStatements.NumberSet set)
{
var widened = ShouldBeAtLeastDouble(element);
return widened.Type == typeof(System.Numerics.Complex)
? Expression.Call(complexMembership, widened, Expression.Constant((int)set))
: Expression.Call(realMembership, ConvertType(widened, typeof(double)), Expression.Constant((int)set));
}

[ConstantField] private static readonly MethodInfo complexRemainder =
((MethodCallExpression)((Expression<Func<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>>)((a, b) => RealOnly.Mod(a, b))).Body).Method;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal static TDelegate Compile<TDelegate>(
// Only expressions that mention a matrix pay for this.
if (expr.Nodes.Any(node => node is Entity.Matrix))
expr = expr.InnerSimplified;
expr = CompiledMembership.Rewritten(expr);

var subexpressionsCache = typesAndNames.ToDictionary(c => (Entity)c.variable, c => Expression.Parameter(c.type));
var functionArguments = subexpressionsCache.Select(c => c.Value).ToArray(); // copying
Expand Down Expand Up @@ -95,6 +96,12 @@ IUnaryNode oneArg
BuildTree(oneArg.NodeChild, cachedSubexpressions, variableAssignments, newLocalVars, protocol),
expr),

// A membership in a set of numbers, the set not being a value: only the element is computed.
// https://github.com/asc-community/AngouriMath/issues/2195
Set.Inf(var element, Set.SpecialSet special) when AngouriMath.Numerics.NumberStatements.Of(special) is { } set
=> protocol.ConvertMembership(
BuildTree(element, cachedSubexpressions, variableAssignments, newLocalVars, protocol), set),

IBinaryNode twoArg
=> protocol.ConvertBinaryNode(
BuildTree(twoArg.NodeFirstChild, cachedSubexpressions, variableAssignments, newLocalVars, protocol),
Expand Down
89 changes: 89 additions & 0 deletions Sources/AngouriMath/Numerics/NumberStatements.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// 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 System;
using AngouriMath.Functions;
using PeterO.Numbers;
using NumericsComplex = System.Numerics.Complex;

namespace AngouriMath.Numerics
{
/// <summary>
/// Membership in the sets of numbers and divisibility, for the compilers, on the terms evaluation decides
/// them: no truth value at <c>NaN</c>, an infinity in <c>RR</c> and <c>CC</c> as evaluation has it and in
/// none of the sets of whole numbers, and primality left open past the machine word, where the interpreter
/// leaves it. Divisibility is a statement about whole numbers, and has no truth value at any other.
/// https://github.com/asc-community/AngouriMath/issues/2195
/// </summary>
internal static class NumberStatements
{
/// <summary>The sets of numbers membership in which compiles, as FastExpression's instruction names them.</summary>
internal enum NumberSet { Complexes, Reals, Integers, PositiveIntegers, NonNegativeIntegers, Primes }

/// <summary>
/// The set of numbers <paramref name="set"/> is, or <see langword="null"/> for one membership in which does
/// not compile: <c>QQ</c>, of which every finite double is a member while <c>pi in QQ</c> is
/// <c>False</c>, and <c>BB</c>, whose members are not numbers.
/// </summary>
internal static NumberSet? Of(Entity.Set.SpecialSet set) => set switch
{
Entity.Set.SpecialSet.Complexes => NumberSet.Complexes,
Entity.Set.SpecialSet.Reals => NumberSet.Reals,
Entity.Set.SpecialSet.Integers => NumberSet.Integers,
Entity.Set.SpecialSet.PositiveIntegers => NumberSet.PositiveIntegers,
Entity.Set.SpecialSet.NonNegativeIntegers => NumberSet.NonNegativeIntegers,
Entity.Set.SpecialSet.Primes => NumberSet.Primes,
_ => null
};

// 2^63: a whole double below it is a long, and the trial division the interpreter decides primality by
// takes it.
private const double PastTheWord = 9223372036854775808.0;

/// <summary>Whether <paramref name="z"/> is in the set of numbers <paramref name="set"/>, a <see cref="NumberSet"/>.</summary>
public static bool? In(NumericsComplex z, int set)
{
if (double.IsNaN(z.Real) || double.IsNaN(z.Imaginary))
return null;
// +oo and -oo are reals as evaluation holds them, which #1019 item 15 takes up for v3:
// https://github.com/asc-community/AngouriMath/issues/2220
switch ((NumberSet)set)
{
case NumberSet.Complexes:
return true;
case NumberSet.Reals:
return z.Imaginary == 0;
}
if (z.Imaginary != 0 || double.IsInfinity(z.Real) || Math.Floor(z.Real) != z.Real)
return false;
return (NumberSet)set switch
{
NumberSet.Integers => true,
NumberSet.PositiveIntegers => z.Real > 0,
NumberSet.NonNegativeIntegers => z.Real >= 0,
_ => z.Real < 2 ? false : z.Real >= PastTheWord ? null : Primes.IsPrime(EInteger.FromInt64((long)z.Real))
};
}

public static bool? In(double x, int set) => In(new NumericsComplex(x, 0), set);

/// <summary>
/// Whether <paramref name="divisor"/> divides <paramref name="dividend"/>: for whole numbers, whether the
/// remainder is 0, and 0 dividing only 0; for any other, no truth value.
/// </summary>
public static bool? Divides(NumericsComplex divisor, NumericsComplex dividend)
=> IsWhole(divisor) && IsWhole(dividend)
? divisor.Real == 0 ? dividend.Real == 0 : dividend.Real % divisor.Real == 0
: null;

public static bool? Divides(double divisor, double dividend) => Divides(new NumericsComplex(divisor, 0), new NumericsComplex(dividend, 0));

// A double's remainder by another is exact, so a whole one is divided exactly however large.
private static bool IsWhole(NumericsComplex z)
=> z.Imaginary == 0 && !double.IsNaN(z.Real) && !double.IsInfinity(z.Real) && Math.Floor(z.Real) == z.Real;
}
}
Loading
Loading