Skip to content

Commit 67c422a

Browse files
The floors, the rounding and the extremes compile, and Solve's numerical search declines what it cannot represent (#1605)
floor(x) = x/2 + 1/3 and max(x, 1) = 2*x threw UncompilableNodeException out of Solve, in 2.5.0 as on master. When no analytical route answers, Solve falls back to Newton's method, which compiles the expression and its derivative. The compiler had no form for floor, ceil, round, max or min, and max and min had no derivative. They compile now, through Compile and Compile<TIn, TOut> alike, in agreement with the interpreter. floor, ceil and round act componentwise on a complex number, and round takes a half to even. max and min of two numbers that differ and are not both real have no value. max and min differentiate where their arguments are real, as |f| does. The solver's numerical search declines what it cannot represent. First, an expression with no compiled form: erf and Si are left unsolved (#1607 is for their compiled forms). Second, roots that are not isolated. max(x, 0) = x and abs(x) - x = 0 each answered 51 points of [0, 10]. Where the equation also holds midway between two roots, the points sample a continuum, and the equation is left unsolved. SolveNt, called by name, still throws for what does not compile, and still returns the points it finds. Closes #1603. Closes #1420. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent afcf11d commit 67c422a

13 files changed

Lines changed: 568 additions & 10 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,6 +2289,34 @@ before: `(x^2 - 1)/(x - 1) - x - 1` is zero for every `x` but 1, where `CC` woul
22892289
| `"sqrt(x)^2 = x".ToEntity().Solve("x")` | `{ -10, -49/5, -48/5, …, -8 - 8i, … }`, 191 numbers | `CC` |
22902290
| `"sin(x)^2 + cos(x)^2 = 1".ToEntity().Solve("x")` | the same 191 numbers | `CC` |
22912291

2292+
### The floors, the rounding and the extremes compile, and the solver's numerical search declines what it cannot represent
2293+
2294+
`floor`, `ceil`, `round`, `max` and `min` had no compiled form, and `max` and `min` had no
2295+
derivative. So `Compile` threw `UncompilableNodeException` on them, and so did `Solve`, whose last
2296+
resort, Newton's method, compiles the expression and its derivative. They compile now, through
2297+
`Compile` and `Compile<TIn, TOut>` alike, and they agree with the interpreter:
2298+
2299+
- `floor`, `ceil` and `round` are taken componentwise on a complex number, and `round` rounds a half
2300+
to even;
2301+
- `max` and `min` of two numbers that differ and are not both real have no value, since those are
2302+
not ordered;
2303+
- `max` and `min` differentiate where their arguments are real, as `|f|` does.
2304+
2305+
The search still declines what has no compiled form, so `erf(x) = x/2 + 1/7` is left unsolved. It
2306+
now also declines a set of roots that are not isolated: where the equation holds midway between two
2307+
of them, the points are samples of a continuum and not the set
2308+
([#1603](https://github.com/asc-community/AngouriMath/issues/1603),
2309+
[#1420](https://github.com/asc-community/AngouriMath/issues/1420)).
2310+
2311+
| Input | Was (2.5.0) | Now |
2312+
|---|---|---|
2313+
| `"floor(x) = x/2 + 1/3".ToEntity().Solve("x")` | `UncompilableNodeException` | `{ 1.333… }`: 4/3, by Newton's method |
2314+
| `"max(x, 1) = 2*x".ToEntity().Solve("x")` | `UncompilableNodeException` | `{ 1/2 }` |
2315+
| `"max(x, 0) = x".ToEntity().Solve("x")` | `UncompilableNodeException` | `{ x : max(x, 0) = x }` |
2316+
| `"abs(x) - x = 0".ToEntity().Solve("x")` | `{ 0, 1/5, 2/5, …, 10 }`, 51 numbers | `{ x : abs(x) - x = 0 }` |
2317+
| `"floor(x)".Compile("x")`, and `ceil`, `round`, `max`, `min` | `UncompilableNodeException` | compiled |
2318+
| `"max(x, 1)".ToEntity().Differentiate("x")` | `derivative(max(x, 1), x)` | `(1 + sgn(x - 1)) / 2 provided not x - 1 = 0` |
2319+
22922320
### An equation the solver cannot invert is left unsolved, not answered with no roots
22932321

22942322
`x! = 6` was answered `{ }`, a claim that it has no roots, and it has 3. The solver isolates `x`

‎Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs‎

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ namespace AngouriMath
1313
{
1414
partial record Entity
1515
{
16+
/// <summary>
17+
/// Whether this node, its children aside, has a compiled form: false by default, as
18+
/// <see cref="CompileNode"/>'s default is to throw, and true beside every override that
19+
/// compiles rather than throws.
20+
/// </summary>
21+
private protected virtual bool CompilesItself => false;
22+
23+
/// <summary>
24+
/// Whether the compiler has a form for every node of this expression, so that a caller
25+
/// that would fall back rather than fail can ask first instead of catching
26+
/// <see cref="UncompilableNodeException"/>. The variables are not checked against a
27+
/// list: which ones an expression is compiled over is the caller's to say.
28+
/// https://github.com/asc-community/AngouriMath/issues/1603
29+
/// </summary>
30+
internal bool HasCompiledForm => CompilesItself && DirectChildren.All(child => child.HasCompiledForm);
31+
1632
private protected virtual void CompileNode(Compiler compiler)
1733
=> throw new UncompilableNodeException(
1834
$"`{Stringize()}`, a node of type {GetType()}, does not support compilation. "
@@ -38,12 +54,16 @@ internal void InnerCompile(Compiler compiler)
3854

3955
public partial record Number
4056
{
57+
private protected override bool CompilesItself => true;
58+
4159
private protected override void CompileNode(Compiler compiler) =>
4260
compiler.Instructions.Add(new(InstructionType.PUSH_CONST, Value: ((Complex)this).ToNumerics()));
4361
}
4462

4563
public partial record Variable
4664
{
65+
private protected override bool CompilesItself => true;
66+
4767
private protected override void CompileNode(Compiler compiler) =>
4868
compiler.Instructions.Add(new(InstructionType.PUSH_VAR,
4969
compiler.VarNamespace.TryGetValue(this, out var slot)
@@ -65,6 +85,8 @@ public override int GetHashCode()
6585

6686
public partial record Constant
6787
{
88+
private protected override bool CompilesItself => true;
89+
6890
// A constant is its value. The named ones are substituted by value before
6991
// compiling; the base of ln and exp is Euler's number standing in the operator's
7092
// definition, which substitution leaves alone (#994), so it arrives here.
@@ -82,6 +104,8 @@ private protected override void CompileNode(Compiler compiler) =>
82104
// Note: We pop values when executing instructions, so we add instructions in reverse child order
83105
public partial record Sumf
84106
{
107+
private protected override bool CompilesItself => true;
108+
85109
private protected override void CompileNode(Compiler compiler)
86110
{
87111
Addend.InnerCompile(compiler);
@@ -92,6 +116,8 @@ private protected override void CompileNode(Compiler compiler)
92116

93117
public partial record Minusf
94118
{
119+
private protected override bool CompilesItself => true;
120+
95121
private protected override void CompileNode(Compiler compiler)
96122
{
97123
Subtrahend.InnerCompile(compiler);
@@ -102,6 +128,8 @@ private protected override void CompileNode(Compiler compiler)
102128

103129
public partial record Mulf
104130
{
131+
private protected override bool CompilesItself => true;
132+
105133
private protected override void CompileNode(Compiler compiler)
106134
{
107135
Multiplicand.InnerCompile(compiler);
@@ -112,6 +140,8 @@ private protected override void CompileNode(Compiler compiler)
112140

113141
public partial record Divf
114142
{
143+
private protected override bool CompilesItself => true;
144+
115145
private protected override void CompileNode(Compiler compiler)
116146
{
117147
Divisor.InnerCompile(compiler);
@@ -122,6 +152,8 @@ private protected override void CompileNode(Compiler compiler)
122152

123153
public partial record Modf
124154
{
155+
private protected override bool CompilesItself => true;
156+
125157
private protected override void CompileNode(Compiler compiler)
126158
{
127159
Divisor.InnerCompile(compiler);
@@ -132,6 +164,8 @@ private protected override void CompileNode(Compiler compiler)
132164

133165
public partial record Powf
134166
{
167+
private protected override bool CompilesItself => true;
168+
135169
private protected override void CompileNode(Compiler compiler)
136170
{
137171
Exponent.InnerCompile(compiler);
@@ -142,6 +176,8 @@ private protected override void CompileNode(Compiler compiler)
142176

143177
public partial record Sinf
144178
{
179+
private protected override bool CompilesItself => true;
180+
145181
private protected override void CompileNode(Compiler compiler)
146182
{
147183
Argument.InnerCompile(compiler);
@@ -151,6 +187,8 @@ private protected override void CompileNode(Compiler compiler)
151187

152188
public partial record Cosf
153189
{
190+
private protected override bool CompilesItself => true;
191+
154192
private protected override void CompileNode(Compiler compiler)
155193
{
156194
Argument.InnerCompile(compiler);
@@ -160,6 +198,8 @@ private protected override void CompileNode(Compiler compiler)
160198

161199
public partial record Secantf
162200
{
201+
private protected override bool CompilesItself => true;
202+
163203
private protected override void CompileNode(Compiler compiler)
164204
{
165205
Argument.InnerCompile(compiler);
@@ -169,6 +209,8 @@ private protected override void CompileNode(Compiler compiler)
169209

170210
public partial record Cosecantf
171211
{
212+
private protected override bool CompilesItself => true;
213+
172214
private protected override void CompileNode(Compiler compiler)
173215
{
174216
Argument.InnerCompile(compiler);
@@ -178,6 +220,8 @@ private protected override void CompileNode(Compiler compiler)
178220

179221
public partial record Tanf
180222
{
223+
private protected override bool CompilesItself => true;
224+
181225
private protected override void CompileNode(Compiler compiler)
182226
{
183227
Argument.InnerCompile(compiler);
@@ -187,6 +231,8 @@ private protected override void CompileNode(Compiler compiler)
187231

188232
public partial record Cotanf
189233
{
234+
private protected override bool CompilesItself => true;
235+
190236
private protected override void CompileNode(Compiler compiler)
191237
{
192238
Argument.InnerCompile(compiler);
@@ -196,6 +242,8 @@ private protected override void CompileNode(Compiler compiler)
196242

197243
public partial record Logf
198244
{
245+
private protected override bool CompilesItself => true;
246+
199247
private protected override void CompileNode(Compiler compiler)
200248
{
201249
// Unlike AngouriMath which accepts Base as the first parameter,
@@ -209,6 +257,8 @@ private protected override void CompileNode(Compiler compiler)
209257

210258
public partial record Arcsinf
211259
{
260+
private protected override bool CompilesItself => true;
261+
212262
private protected override void CompileNode(Compiler compiler)
213263
{
214264
Argument.InnerCompile(compiler);
@@ -218,6 +268,8 @@ private protected override void CompileNode(Compiler compiler)
218268

219269
public partial record Arccosf
220270
{
271+
private protected override bool CompilesItself => true;
272+
221273
private protected override void CompileNode(Compiler compiler)
222274
{
223275
Argument.InnerCompile(compiler);
@@ -227,6 +279,8 @@ private protected override void CompileNode(Compiler compiler)
227279

228280
public partial record Arctanf
229281
{
282+
private protected override bool CompilesItself => true;
283+
230284
private protected override void CompileNode(Compiler compiler)
231285
{
232286
Argument.InnerCompile(compiler);
@@ -236,6 +290,8 @@ private protected override void CompileNode(Compiler compiler)
236290

237291
public partial record Arccotanf
238292
{
293+
private protected override bool CompilesItself => true;
294+
239295
private protected override void CompileNode(Compiler compiler)
240296
{
241297
Argument.InnerCompile(compiler);
@@ -245,6 +301,8 @@ private protected override void CompileNode(Compiler compiler)
245301

246302
public partial record Arcsecantf
247303
{
304+
private protected override bool CompilesItself => true;
305+
248306
private protected override void CompileNode(Compiler compiler)
249307
{
250308
Argument.InnerCompile(compiler);
@@ -254,6 +312,8 @@ private protected override void CompileNode(Compiler compiler)
254312

255313
public partial record Arccosecantf
256314
{
315+
private protected override bool CompilesItself => true;
316+
257317
private protected override void CompileNode(Compiler compiler)
258318
{
259319
Argument.InnerCompile(compiler);
@@ -263,6 +323,8 @@ private protected override void CompileNode(Compiler compiler)
263323

264324
public partial record Factorialf
265325
{
326+
private protected override bool CompilesItself => true;
327+
266328
private protected override void CompileNode(Compiler compiler)
267329
{
268330
Argument.InnerCompile(compiler);
@@ -288,8 +350,70 @@ private protected override void CompileNode(Compiler compiler) =>
288350
throw new UncompilableNodeException($"A limit cannot be compiled: `{Stringize()}`");
289351
}
290352

353+
// The floors, the rounding and the extremes, which the solver's numerical search needs
354+
// compiled to run on `floor(x) = x/2 + 1/3` or `max(x, 1) = 2 x` at all.
355+
// https://github.com/asc-community/AngouriMath/issues/1603
356+
public partial record Floorf
357+
{
358+
private protected override bool CompilesItself => true;
359+
360+
private protected override void CompileNode(Compiler compiler)
361+
{
362+
Argument.InnerCompile(compiler);
363+
compiler.Instructions.Add(new(InstructionType.CALL_FLOOR));
364+
}
365+
}
366+
367+
public partial record Ceilf
368+
{
369+
private protected override bool CompilesItself => true;
370+
371+
private protected override void CompileNode(Compiler compiler)
372+
{
373+
Argument.InnerCompile(compiler);
374+
compiler.Instructions.Add(new(InstructionType.CALL_CEIL));
375+
}
376+
}
377+
378+
public partial record Roundf
379+
{
380+
private protected override bool CompilesItself => true;
381+
382+
private protected override void CompileNode(Compiler compiler)
383+
{
384+
Argument.InnerCompile(compiler);
385+
compiler.Instructions.Add(new(InstructionType.CALL_ROUND));
386+
}
387+
}
388+
389+
public partial record Maxf
390+
{
391+
private protected override bool CompilesItself => true;
392+
393+
private protected override void CompileNode(Compiler compiler)
394+
{
395+
Right.InnerCompile(compiler);
396+
Left.InnerCompile(compiler);
397+
compiler.Instructions.Add(new(InstructionType.CALL_MAX));
398+
}
399+
}
400+
401+
public partial record Minf
402+
{
403+
private protected override bool CompilesItself => true;
404+
405+
private protected override void CompileNode(Compiler compiler)
406+
{
407+
Right.InnerCompile(compiler);
408+
Left.InnerCompile(compiler);
409+
compiler.Instructions.Add(new(InstructionType.CALL_MIN));
410+
}
411+
}
412+
291413
public partial record Signumf
292414
{
415+
private protected override bool CompilesItself => true;
416+
293417
private protected override void CompileNode(Compiler compiler)
294418
{
295419
Argument.InnerCompile(compiler);
@@ -299,6 +423,8 @@ private protected override void CompileNode(Compiler compiler)
299423

300424
public partial record Absf
301425
{
426+
private protected override bool CompilesItself => true;
427+
302428
private protected override void CompileNode(Compiler compiler)
303429
{
304430
Argument.InnerCompile(compiler);
@@ -308,6 +434,8 @@ private protected override void CompileNode(Compiler compiler)
308434

309435
public partial record Phif
310436
{
437+
private protected override bool CompilesItself => true;
438+
311439
private protected override void CompileNode(Compiler compiler)
312440
{
313441
Argument.InnerCompile(compiler);

0 commit comments

Comments
 (0)