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
28 changes: 28 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2245,6 +2245,34 @@ before: `(x^2 - 1)/(x - 1) - x - 1` is zero for every `x` but 1, where `CC` woul
| `"sqrt(x)^2 = x".ToEntity().Solve("x")` | `{ -10, -49/5, -48/5, …, -8 - 8i, … }`, 191 numbers | `CC` |
| `"sin(x)^2 + cos(x)^2 = 1".ToEntity().Solve("x")` | the same 191 numbers | `CC` |

### The floors, the rounding and the extremes compile, and the solver's numerical search declines what it cannot represent

`floor`, `ceil`, `round`, `max` and `min` had no compiled form, and `max` and `min` had no
derivative. So `Compile` threw `UncompilableNodeException` on them, and so did `Solve`, whose last
resort, Newton's method, compiles the expression and its derivative. They compile now, through
`Compile` and `Compile<TIn, TOut>` alike, and they agree with the interpreter:

- `floor`, `ceil` and `round` are taken componentwise on a complex number, and `round` rounds a half
to even;
- `max` and `min` of two numbers that differ and are not both real have no value, since those are
not ordered;
- `max` and `min` differentiate where their arguments are real, as `|f|` does.

The search still declines what has no compiled form, so `erf(x) = x/2 + 1/7` is left unsolved. It
now also declines a set of roots that are not isolated: where the equation holds midway between two
of them, the points are samples of a continuum and not the set
([#1603](https://github.com/asc-community/AngouriMath/issues/1603),
[#1420](https://github.com/asc-community/AngouriMath/issues/1420)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"floor(x) = x/2 + 1/3".ToEntity().Solve("x")` | `UncompilableNodeException` | `{ 1.333… }`: 4/3, by Newton's method |
| `"max(x, 1) = 2*x".ToEntity().Solve("x")` | `UncompilableNodeException` | `{ 1/2 }` |
| `"max(x, 0) = x".ToEntity().Solve("x")` | `UncompilableNodeException` | `{ x : max(x, 0) = x }` |
| `"abs(x) - x = 0".ToEntity().Solve("x")` | `{ 0, 1/5, 2/5, …, 10 }`, 51 numbers | `{ x : abs(x) - x = 0 }` |
| `"floor(x)".Compile("x")`, and `ceil`, `round`, `max`, `min` | `UncompilableNodeException` | compiled |
| `"max(x, 1)".ToEntity().Differentiate("x")` | `derivative(max(x, 1), x)` | `(1 + sgn(x - 1)) / 2 provided not x - 1 = 0` |

### An equation the solver cannot invert is left unsolved, not answered with no roots

`x! = 6` was answered `{ }`, a claim that it has no roots, and it has 3. The solver isolates `x`
Expand Down
128 changes: 128 additions & 0 deletions Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ namespace AngouriMath
{
partial record Entity
{
/// <summary>
/// Whether this node, its children aside, has a compiled form: false by default, as
/// <see cref="CompileNode"/>'s default is to throw, and true beside every override that
/// compiles rather than throws.
/// </summary>
private protected virtual bool CompilesItself => false;

/// <summary>
/// Whether the compiler has a form for every node of this expression, so that a caller
/// that would fall back rather than fail can ask first instead of catching
/// <see cref="UncompilableNodeException"/>. The variables are not checked against a
/// list: which ones an expression is compiled over is the caller's to say.
/// https://github.com/asc-community/AngouriMath/issues/1603
/// </summary>
internal bool HasCompiledForm => CompilesItself && DirectChildren.All(child => child.HasCompiledForm);

private protected virtual void CompileNode(Compiler compiler)
=> throw new UncompilableNodeException(
$"`{Stringize()}`, a node of type {GetType()}, does not support compilation. "
Expand All @@ -38,12 +54,16 @@ internal void InnerCompile(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler) =>
compiler.Instructions.Add(new(InstructionType.PUSH_CONST, Value: ((Complex)this).ToNumerics()));
}

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

private protected override void CompileNode(Compiler compiler) =>
compiler.Instructions.Add(new(InstructionType.PUSH_VAR,
compiler.VarNamespace.TryGetValue(this, out var slot)
Expand All @@ -65,6 +85,8 @@ public override int GetHashCode()

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

// A constant is its value. The named ones are substituted by value before
// compiling; the base of ln and exp is Euler's number standing in the operator's
// definition, which substitution leaves alone (#994), so it arrives here.
Expand All @@ -82,6 +104,8 @@ private protected override void CompileNode(Compiler compiler) =>
// Note: We pop values when executing instructions, so we add instructions in reverse child order
public partial record Sumf
{
private protected override bool CompilesItself => true;

private protected override void CompileNode(Compiler compiler)
{
Addend.InnerCompile(compiler);
Expand All @@ -92,6 +116,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Subtrahend.InnerCompile(compiler);
Expand All @@ -102,6 +128,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Multiplicand.InnerCompile(compiler);
Expand All @@ -112,6 +140,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Divisor.InnerCompile(compiler);
Expand All @@ -122,6 +152,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Divisor.InnerCompile(compiler);
Expand All @@ -132,6 +164,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Exponent.InnerCompile(compiler);
Expand All @@ -142,6 +176,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -151,6 +187,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -160,6 +198,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -169,6 +209,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -178,6 +220,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -187,6 +231,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -196,6 +242,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
// Unlike AngouriMath which accepts Base as the first parameter,
Expand All @@ -209,6 +257,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -218,6 +268,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -227,6 +279,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -236,6 +290,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -245,6 +301,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -254,6 +312,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -263,6 +323,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -288,8 +350,70 @@ 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
public partial record Floorf
{
private protected override bool CompilesItself => true;

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

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

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

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

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

public partial record Maxf
{
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_MAX));
}
}

public partial record Minf
{
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_MIN));
}
}

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -299,6 +423,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand All @@ -308,6 +434,8 @@ private protected override void CompileNode(Compiler compiler)

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

private protected override void CompileNode(Compiler compiler)
{
Argument.InnerCompile(compiler);
Expand Down
Loading
Loading