Skip to content

Commit 94e1f8c

Browse files
committed
Refactor interval bounds handling and enhance expression parsing
- Updated `assume.ts` to use `lower` and `upper` properties instead of `lowerBound` and `upperBound` for consistency in bounds representation. - Introduced `extractIntervalBounds` function in `inequality-bounds.ts` to extract interval bounds from expressions, supporting various comparison forms and conditions. - Enhanced `_BoxedExpression` class with a `getInterval` method to retrieve bounds for a specified symbol. - Modified `compare.ts` to align with the new bounds structure and ensure correct comparisons. - Updated `engine-assumptions.ts` to reflect changes in bounds retrieval. - Adjusted LaTeX syntax definitions to accommodate new parsing rules for compact piecewise expressions. - Added tests for interval extraction and compact piecewise parsing to ensure functionality and correctness.
1 parent eaf3e36 commit 94e1f8c

13 files changed

Lines changed: 1355 additions & 73 deletions

File tree

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,44 @@
112112
`Less`/`LessEqual` operator — the signed-function semantics are preserved
113113
through the normalization.
114114

115+
- **`BoxedExpression.getInterval(symbol)`** — new method for extracting
116+
domain bounds from restriction expressions. Returns `IntervalBounds`
117+
with `lower`/`upper`/`lowerStrict`/`upperStrict` for `When(e, cond)`,
118+
`And(c1, c2, …)`, and bare comparison expressions (`Less`, `LessEqual`,
119+
`Greater`, `GreaterEqual`); returns `undefined` for unsupported shapes.
120+
Useful for 2D-plot domain derivation (e.g. clipping
121+
`y = f(x)\{0 < x < 5\}` to `[0, 5]`). Also handles the
122+
`Multiply(f, When(…))` parse shape produced by trailing
123+
restriction-brace syntax. Exported alongside the existing
124+
`getInequalityBoundsFromAssumptions` helper in `inequality-bounds.ts`.
125+
Added `IntervalBounds` type to the public type surface.
126+
127+
- **Compact Desmos piecewise `\{cond_1 : val_1, …, default\}`** — now
128+
parses to `Which(c_1, v_1, c_2, v_2, …, True, default)`, the same head
129+
CE produces for `\begin{cases}…\end{cases}`. Disambiguates from
130+
set-builder `\{x : type\}`: if the LHS of the top-level `Colon` has a
131+
comparison/boolean head (`Less`/`Greater`/`Equal`/`And`/`Or`/`Not`/…),
132+
it's a piecewise branch; otherwise it's set-builder. Normal set
133+
literals (`\{1, 2, 3\}`) and set-builder via `\mid` are unchanged.
134+
- **Parser change**: `Colon` precedence lowered from 250 to 240
135+
(below comparison operators) so `cond : val` parses as
136+
`Colon(cond, val)` instead of binding the colon tighter than the
137+
comparison. Function type annotations (`f : A \to B` where `\to`
138+
is at 270) and set-builder via `\mid` (`Divides` at 160)
139+
continue to parse correctly. Full test suite confirmed zero
140+
regressions from the precedence change.
141+
115142
#### Improved
116143

117144
- **`parseTrig` Arctan2 handling** — the 2-arg `\arctan` lowering uses a
118145
tighter, single-branch form instead of an `effectiveFn` intermediate.
119146

147+
- **`When` evaluator description** — expanded to document the masking
148+
rule (`When(e, False) → Undefined`), stacked-restriction
149+
canonicalization (`When(When(e, c1), c2) → When(e, And(c1, c2))`),
150+
and ternary compilation behavior. No behavioral change — the rules
151+
were already implemented; only the description was incomplete.
152+
120153
#### Known issues
121154

122155
- **JS `Loop` compile produces `undefined`** — the imperative `for`-loop IIFE

docs/plans/2026-05-22-058-a2-restrictions.md

Lines changed: 773 additions & 0 deletions
Large diffs are not rendered by default.

src/compute-engine/assume.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,9 @@ function assumeInequality(proposition: Expression): AssumeResult {
312312
// We're asserting symbol > k or symbol >= k
313313
const isStrict = effectiveOp === 'greater';
314314

315-
if (bounds.lowerBound !== undefined) {
316-
const lowerVal = isNumber(bounds.lowerBound)
317-
? bounds.lowerBound.numericValue
315+
if (bounds.lower !== undefined) {
316+
const lowerVal = isNumber(bounds.lower)
317+
? bounds.lower.numericValue
318318
: undefined;
319319
if (typeof lowerVal === 'number' && isFinite(lowerVal)) {
320320
// We already know symbol > lowerVal (or >=)
@@ -333,9 +333,9 @@ function assumeInequality(proposition: Expression): AssumeResult {
333333
}
334334
}
335335

336-
if (bounds.upperBound !== undefined) {
337-
const upperVal = isNumber(bounds.upperBound)
338-
? bounds.upperBound.numericValue
336+
if (bounds.upper !== undefined) {
337+
const upperVal = isNumber(bounds.upper)
338+
? bounds.upper.numericValue
339339
: undefined;
340340
if (typeof upperVal === 'number' && isFinite(upperVal)) {
341341
// We know symbol < upperVal (or <=), now checking symbol > k
@@ -357,9 +357,9 @@ function assumeInequality(proposition: Expression): AssumeResult {
357357
// We're asserting symbol < k or symbol <= k
358358
const isStrict = effectiveOp === 'less';
359359

360-
if (bounds.upperBound !== undefined) {
361-
const upperVal = isNumber(bounds.upperBound)
362-
? bounds.upperBound.numericValue
360+
if (bounds.upper !== undefined) {
361+
const upperVal = isNumber(bounds.upper)
362+
? bounds.upper.numericValue
363363
: undefined;
364364
if (typeof upperVal === 'number' && isFinite(upperVal)) {
365365
// We already know symbol < upperVal (or <=)
@@ -375,9 +375,9 @@ function assumeInequality(proposition: Expression): AssumeResult {
375375
}
376376
}
377377

378-
if (bounds.lowerBound !== undefined) {
379-
const lowerVal = isNumber(bounds.lowerBound)
380-
? bounds.lowerBound.numericValue
378+
if (bounds.lower !== undefined) {
379+
const lowerVal = isNumber(bounds.lower)
380+
? bounds.lower.numericValue
381381
: undefined;
382382
if (typeof lowerVal === 'number' && isFinite(lowerVal)) {
383383
// We know symbol > lowerVal (or >=), now checking symbol < k

src/compute-engine/boxed-expression/abstract-boxed-expression.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { toAsciiMath } from './ascii-math';
3939
import { cmp, eq, same } from './compare';
4040
import { CancellationError } from '../../common/interruptible';
4141
import { isSymbol, isString, isNumber, isFunction } from './type-guards';
42+
import { extractIntervalBounds } from './inequality-bounds';
4243

4344
// Lazy reference to break circular dependency:
4445
// serialize → numerics → utils → abstract-boxed-expression
@@ -890,6 +891,10 @@ export abstract class _BoxedExpression implements Expression {
890891
}
891892
}
892893

894+
getInterval(symbol: string) {
895+
return extractIntervalBounds(this, symbol);
896+
}
897+
893898
simplify(_options?: Partial<SimplifyOptions>): Expression {
894899
return this;
895900
}

src/compute-engine/boxed-expression/compare.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ export function cmp(
234234
if (aNum !== undefined && Number.isFinite(aNum)) {
235235
// We're comparing a (number) to b (symbol)
236236
// If b has a lower bound > a, then a < b
237-
if (bounds.lowerBound !== undefined) {
238-
const lb = bounds.lowerBound;
237+
if (bounds.lower !== undefined) {
238+
const lb = bounds.lower;
239239
const lowerNum = isNumber(lb)
240240
? typeof lb.numericValue === 'number'
241241
? lb.numericValue
@@ -254,8 +254,8 @@ export function cmp(
254254
}
255255

256256
// If b has an upper bound < a, then a > b
257-
if (bounds.upperBound !== undefined) {
258-
const ub = bounds.upperBound;
257+
if (bounds.upper !== undefined) {
258+
const ub = bounds.upper;
259259
const upperNum = isNumber(ub)
260260
? typeof ub.numericValue === 'number'
261261
? ub.numericValue
@@ -302,8 +302,8 @@ export function cmp(
302302

303303
// We're comparing a (symbol) to b (number)
304304
// If a has a lower bound >= b, then a > b (or a >= b)
305-
if (bounds.lowerBound !== undefined) {
306-
const lb = bounds.lowerBound;
305+
if (bounds.lower !== undefined) {
306+
const lb = bounds.lower;
307307
const lowerNum = isNumber(lb)
308308
? typeof lb.numericValue === 'number'
309309
? lb.numericValue
@@ -322,8 +322,8 @@ export function cmp(
322322
}
323323

324324
// If a has an upper bound <= b, then a < b (or a <= b)
325-
if (bounds.upperBound !== undefined) {
326-
const ub = bounds.upperBound;
325+
if (bounds.upper !== undefined) {
326+
const ub = bounds.upper;
327327
const upperNum = isNumber(ub)
328328
? typeof ub.numericValue === 'number'
329329
? ub.numericValue
@@ -420,8 +420,8 @@ export function cmp(
420420

421421
if (bNum !== undefined && Number.isFinite(bNum)) {
422422
// If symbol has a lower bound >= b, then symbol > b (or symbol >= b)
423-
if (bounds.lowerBound !== undefined) {
424-
const lb = bounds.lowerBound;
423+
if (bounds.lower !== undefined) {
424+
const lb = bounds.lower;
425425
const lowerNum = isNumber(lb)
426426
? typeof lb.numericValue === 'number'
427427
? lb.numericValue
@@ -440,8 +440,8 @@ export function cmp(
440440
}
441441

442442
// If symbol has an upper bound <= b, then symbol < b (or symbol <= b)
443-
if (bounds.upperBound !== undefined) {
444-
const ub = bounds.upperBound;
443+
if (bounds.upper !== undefined) {
444+
const ub = bounds.upper;
445445
const upperNum = isNumber(ub)
446446
? typeof ub.numericValue === 'number'
447447
? ub.numericValue

0 commit comments

Comments
 (0)