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
17 changes: 17 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,23 @@ compiled to `bool`, it is `false`, as one that does not hold
| `"x provided x > 1".ToEntity().Compile<Complex, Complex>("x")(2)` | `UncompilableNodeException` | `(2, 0)` |
| `"x provided x > 1".ToEntity().Compile<Complex, Complex>("x")(Complex.ImaginaryOne)` | `UncompilableNodeException` | `(NaN, 0)` |

### `Compile` compiles statements, `provided` and `piecewise`

**Not silent: what was refused is compiled.** `Entity.Compile(...)`, which returns a `FastExpression`, refused a
comparison, a connective, a Boolean, a `provided`, a `piecewise` and an Iverson bracket with `UncompilableNodeException`.
A statement now compiles to its truth value, `1` where it holds, `0` where it does not, and `NaN` where it has none, as
`i > 1` has none. Comparisons and connectives are those of the LINQ compiler. A `provided` is `NaN` where its predicate
does not hold, and a `piecewise` is the first case whose predicate holds, or `NaN`. Every case is computed, since the
instructions have no jump ([#2195](https://github.com/asc-community/AngouriMath/issues/2195)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"x > 1".ToEntity().Compile("x").Call(2)` | `UncompilableNodeException` | `(1, 0)` |
| `"x > 1".ToEntity().Compile("x").Call(Complex.ImaginaryOne)` | `UncompilableNodeException` | `(NaN, 0)` |
| `"x provided x > 0".ToEntity().Compile("x").Call(-1)` | `UncompilableNodeException` | `(NaN, 0)` |
| `"piecewise(x provided x > 0, -x)".ToEntity().Compile("x").Call(-3)` | `UncompilableNodeException` | `(3, 0)` |
| `"iverson(x > 0)".ToEntity().Compile("x").Call(2)` | `UncompilableNodeException` | `(1, 0)` |

### The conjugate, the argument, the parts and the Iverson bracket compile

**Not silent: these compile where they threw `UncompilableNodeException`.** `conj`, `arg`, `Re` and `Im` compile
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Common/ThreeValuedCompilationTest.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/FastExpressionStatementTest.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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ partial record Entity
/// Constants, i.e. <see cref="MathS.pi"/> and <see cref="MathS.e"/> will be ignored.
/// </param>
/// <returns></returns>
/// <remarks>
/// A statement compiles to its truth value: 1 where it holds, 0 where it does not, and NaN
/// where it has none, as <c>i &gt; 1</c> has none. A <c>provided</c> or a <c>piecewise</c> is NaN
/// where no predicate holds. https://github.com/asc-community/AngouriMath/issues/2195
/// </remarks>
public FastExpression Compile(params Variable[] variables) => Compiler.Compile(this, variables);

/// <summary>
Expand All @@ -31,6 +36,7 @@ partial record Entity
/// Constants, i.e. <see cref="MathS.pi"/> and <see cref="MathS.e"/> will be ignored.
/// </param>
/// <returns></returns>
/// <remarks>A statement compiles to its truth value, as <see cref="Compile(Variable[])"/> says.</remarks>
public FastExpression Compile(params string[] variables) =>
Compiler.Compile(this, variables.Select(x => (Variable)x));
}
Expand Down
169 changes: 169 additions & 0 deletions Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,175 @@ private protected override void CompileNode(Compiler compiler)
}
}

// A statement compiles to its truth value: 1 where it holds, 0 where it does not, and NaN
// where it has none, as evaluation leaves one. A provided and a piecewise compile every
// case and keep the one whose predicate holds, which needs no jump in the instructions.
// https://github.com/asc-community/AngouriMath/issues/2195
public partial record Boolean
{
private protected override bool CompilesItself => true;

private protected override void CompileNode(Compiler compiler)
=> compiler.Instructions.Add(new(InstructionType.PUSH_CONST, Value: Value ? 1 : 0));
}

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
compiler.Instructions.Add(new(InstructionType.CALL_NOT));
}
}

// The Iverson bracket of a statement is its truth value.
public partial record Iversonf
{
private protected override bool CompilesItself => true;

private protected override void CompileNode(Compiler compiler) => Argument.InnerCompile(compiler);
}

public partial record Andf
{
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_AND));
}
}

public partial record Orf
{
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_OR));
}
}

public partial record Xorf
{
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_XOR));
}
}

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

private protected override void CompileNode(Compiler compiler)
{
Conclusion.InnerCompile(compiler);
Assumption.InnerCompile(compiler);
compiler.Instructions.Add(new(InstructionType.CALL_IMPLIES));
}
}

public partial record Equalsf
{
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_EQUAL));
}
}

public partial record Lessf
{
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_LESS));
}
}

public partial record LessOrEqualf
{
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_LESS_OR_EQUAL));
}
}

public partial record Greaterf
{
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_GREATER));
}
}

public partial record GreaterOrEqualf
{
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_GREATER_OR_EQUAL));
}
}

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

private protected override void CompileNode(Compiler compiler)
{
Predicate.InnerCompile(compiler);
Expression.InnerCompile(compiler);
compiler.Instructions.Add(new(InstructionType.CALL_PROVIDED));
}
}

// The first case whose predicate holds, and NaN where none does: from the last case up,
// each is kept where its predicate holds and the cases below it are kept elsewhere.
public partial record Piecewise
{
private protected override bool CompilesItself => true;

private protected override void CompileNode(Compiler compiler)
{
compiler.Instructions.Add(new(InstructionType.PUSH_CONST, Value: double.NaN));
foreach (var @case in Cases.Reverse())
{
@case.Predicate.InnerCompile(compiler);
@case.Expression.InnerCompile(compiler);
compiler.Instructions.Add(new(InstructionType.CALL_SELECT));
}
}
}

public partial record Maxf
{
private protected override bool CompilesItself => true;
Expand Down
69 changes: 69 additions & 0 deletions Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

using System.Runtime.CompilerServices;
using AngouriMath.Core.Exceptions;
using Logic = AngouriMath.Core.Compilation.IntoLinq.ThreeValuedLogic;


namespace AngouriMath.Core
Expand Down Expand Up @@ -53,6 +54,7 @@ internal enum InstructionType
CALL_ARG,
CALL_RE,
CALL_IM,
CALL_NOT,

// 2-arg functions
CALL_SUM = 100,
Expand All @@ -64,6 +66,20 @@ internal enum InstructionType
CALL_LOG,
CALL_MAX,
CALL_MIN,
CALL_LESS,
CALL_LESS_OR_EQUAL,
CALL_GREATER,
CALL_GREATER_OR_EQUAL,
CALL_EQUAL,
CALL_AND,
CALL_OR,
CALL_XOR,
CALL_IMPLIES,
// A value where its predicate holds, and NaN elsewhere.
CALL_PROVIDED,

// 3-arg: a value where its predicate holds, and the one below it elsewhere.
CALL_SELECT = 150,
}
internal sealed partial record Instruction(InstructionType Type, int Reference = -1, System.Numerics.Complex Value = default)
{
Expand Down Expand Up @@ -285,12 +301,65 @@ public System.Numerics.Complex Substitute(params System.Numerics.Complex[] value
case InstructionType.CALL_IM:
stack.Push(AngouriMath.Numerics.ComplexParts.Im(stack.Pop()));
break;
// A truth value is 1, 0, or NaN where there is none, as evaluation leaves one;
// the comparisons and the connectives are the LINQ compiler's.
// https://github.com/asc-community/AngouriMath/issues/2195
case InstructionType.CALL_LESS:
stack.Push(Truth(Logic.Less(stack.Pop(), stack.Pop())));
break;
case InstructionType.CALL_LESS_OR_EQUAL:
stack.Push(Truth(Logic.LessOrEqual(stack.Pop(), stack.Pop())));
break;
case InstructionType.CALL_GREATER:
stack.Push(Truth(Logic.Greater(stack.Pop(), stack.Pop())));
break;
case InstructionType.CALL_GREATER_OR_EQUAL:
stack.Push(Truth(Logic.GreaterOrEqual(stack.Pop(), stack.Pop())));
break;
case InstructionType.CALL_EQUAL:
stack.Push(Truth(Logic.Equal(stack.Pop(), stack.Pop())));
break;
case InstructionType.CALL_NOT:
stack.Push(Truth(Logic.Not(Truth(stack.Pop()))));
break;
case InstructionType.CALL_AND:
stack.Push(Truth(Logic.And(Truth(stack.Pop()), Truth(stack.Pop()))));
break;
case InstructionType.CALL_OR:
stack.Push(Truth(Logic.Or(Truth(stack.Pop()), Truth(stack.Pop()))));
break;
case InstructionType.CALL_XOR:
stack.Push(Truth(Logic.Xor(Truth(stack.Pop()), Truth(stack.Pop()))));
break;
case InstructionType.CALL_IMPLIES:
stack.Push(Truth(Logic.Implies(Truth(stack.Pop()), Truth(stack.Pop()))));
break;
case InstructionType.CALL_PROVIDED:
{
var value = stack.Pop();
stack.Push(Truth(stack.Pop()) == true ? value : double.NaN);
break;
}
case InstructionType.CALL_SELECT:
{
var value = stack.Pop();
var holds = Truth(stack.Pop()) == true;
var otherwise = stack.Pop();
stack.Push(holds ? value : otherwise);
break;
}
}
if (stack.Count != 1)
throw new AngouriBugException($"Unused values remain in the stack: {stack.Count} instead of 1");
return stack.Pop();
}

/// <summary>A truth value on the stack: 1 where it holds, 0 where it does not, NaN where it has none.</summary>
private static System.Numerics.Complex Truth(bool? value) => value is { } holds ? (holds ? 1 : 0) : double.NaN;

/// <summary>The truth value a number on the stack stands for: none but for exactly 1 or 0.</summary>
private static bool? Truth(System.Numerics.Complex value) => value == 1 ? true : value == 0 ? false : null;

/// <summary>Might be useful for debug if a function works too slowly</summary>
public override string ToString() => string.Join(" \n| ", instructions);
}
Expand Down
6 changes: 6 additions & 0 deletions Sources/Tests/AotSmokeTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@ private static void Compilation()
$"{undecided(Complex.ImaginaryOne, true)}, {undecided(Complex.ImaginaryOne, false)?.ToString() ?? "null"}");
var underIt = "x provided x > 1".Compile<Complex, Complex>(x);
Check("compiled x provided x > 1 at 2 and at i", underIt(2) == 2 && double.IsNaN(underIt(Complex.ImaginaryOne).Real), $"{underIt(2)}, {underIt(Complex.ImaginaryOne)}");
// FastExpression: a statement is its truth value, and a piecewise the case that holds.
var fastPiecewise = "piecewise(x provided x > 0, -x)".Compile(x);
var fastStatement = "x > 1 or x < 0".Compile(x);
Check("FastExpression piecewise at -3 and 2, x > 1 or x < 0 at 2 and at i",
fastPiecewise.Call(-3) == 3 && fastPiecewise.Call(2) == 2 && fastStatement.Call(2) == 1 && double.IsNaN(fastStatement.Call(Complex.ImaginaryOne).Real),
$"{fastPiecewise.Call(-3)}, {fastPiecewise.Call(2)}, {fastStatement.Call(2)}, {fastStatement.Call(Complex.ImaginaryOne)}");

// A matrix, which compiles to a tensor: the arithmetic on it must stay ahead-of-time
// compilable, which GenericTensor's own expression-compiled loops are not.
Expand Down
Loading
Loading