Skip to content

Commit e531fbe

Browse files
committed
arch: cleanup round, with more helper functions, removing unnecessary checks/guards
1 parent 5e50823 commit e531fbe

80 files changed

Lines changed: 900 additions & 1276 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
### Features
44

5+
- **`numericValue()` convenience helper**: New standalone function that combines
6+
the `isNumber()` guard with `.numericValue` access. Returns the numeric value
7+
if the expression is a number literal, or `undefined` otherwise. Useful for
8+
safely extracting numeric values without verbose ternary patterns:
9+
10+
```ts
11+
import { numericValue } from '@cortex-js/compute-engine';
12+
13+
// Before
14+
const val = isNumber(expr) ? expr.numericValue : undefined;
15+
16+
// After
17+
const val = numericValue(expr);
18+
```
19+
520
- **Stochastic equality check for expressions with unknowns**: `expr.isEqual()`
621
now uses a stochastic fallback when symbolic methods (expand + simplify) can't
722
prove equality. Both expressions are evaluated at 50 sample points (9
@@ -414,9 +429,9 @@ now only accessible after narrowing with a type guard.
414429

415430
| Removed from `Expression` | Access via |
416431
| :------------------------------------ | :---------------------------------------- |
417-
| `.symbol` | `isSymbol(expr)` then `expr.symbol` |
432+
| `.symbol` | `isSymbol(expr)` or `isSymbol(expr, 'Pi')` then `expr.symbol` |
418433
| `.string` | `isString(expr)` then `expr.string` |
419-
| `.ops`, `.nops`, `.op1`/`.op2`/`.op3` | `isFunction(expr)` then `expr.ops` etc. |
434+
| `.ops`, `.nops`, `.op1`/`.op2`/`.op3` | `isFunction(expr)` or `isFunction(expr, 'Add')` then `expr.ops` etc. |
420435
| `.numericValue`, `.isNumberLiteral` | `isNumber(expr)` then `expr.numericValue` |
421436
| `.tensor` | `isTensor(expr)` then `expr.tensor` |
422437

@@ -428,8 +443,16 @@ if (expr.symbol !== null) console.log(expr.symbol);
428443
import { isSymbol, sym } from '@cortex-js/compute-engine';
429444

430445
if (isSymbol(expr)) console.log(expr.symbol);
446+
// isSymbol() accepts an optional symbol name:
447+
if (isSymbol(expr, 'Pi')) { /* expr is the Pi symbol */ }
431448
// or use the convenience helper:
432449
if (sym(expr) === 'Pi') { /* ... */ }
450+
451+
// isFunction() accepts an optional operator name:
452+
if (isFunction(expr, 'Add')) {
453+
// expr is narrowed to a function AND has operator 'Add'
454+
console.log(expr.ops);
455+
}
433456
```
434457

435458
Properties that remain on `Expression`: `.operator`, `.re`/`.im`, `.shape`, all

MIGRATION_GUIDE_0.50.0.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,11 @@ base interface. They are now only accessible after narrowing with a type guard.
7474

7575
| Property | Access via |
7676
| :---------------------- | :-------------------------------------------------- |
77-
| `.symbol` | `isSymbol(expr)` then `expr.symbol` |
77+
| `.symbol` | `isSymbol(expr)` or `isSymbol(expr, 'Pi')` then `expr.symbol` |
7878
| `.string` | `isString(expr)` then `expr.string` |
79-
| `.ops` | `isFunction(expr)` then `expr.ops` |
80-
| `.nops` | `isFunction(expr)` then `expr.nops` |
81-
| `.op1`/`.op2`/`.op3` | `isFunction(expr)` then `expr.op1` etc. |
79+
| `.ops` | `isFunction(expr)` or `isFunction(expr, 'Add')` then `expr.ops` |
80+
| `.nops` | `isFunction(expr)` or `isFunction(expr, 'Add')` then `expr.nops` |
81+
| `.op1`/`.op2`/`.op3` | `isFunction(expr)` or `isFunction(expr, 'Add')` then `expr.op1` etc. |
8282
| `.isFunctionExpression` | `isFunction(expr)` then `expr.isFunctionExpression` |
8383
| `.numericValue` | `isNumber(expr)` then `expr.numericValue` |
8484
| `.isNumberLiteral` | `isNumber(expr)` then `expr.isNumberLiteral` |
@@ -99,7 +99,7 @@ if (expr.numericValue !== null) {
9999
### After
100100

101101
```ts
102-
import { isSymbol, isNumber, sym } from '@cortex-js/compute-engine';
102+
import { isSymbol, isNumber, sym, numericValue } from '@cortex-js/compute-engine';
103103

104104
if (isSymbol(expr)) {
105105
// expr.symbol is `string` — guaranteed non-undefined
@@ -111,10 +111,12 @@ if (isNumber(expr)) {
111111
console.log(expr.numericValue);
112112
}
113113

114-
// Convenience helper for symbol checks
114+
// Convenience helpers
115115
if (sym(expr) === 'Pi') {
116116
console.log('This is Pi');
117117
}
118+
119+
const val = numericValue(expr); // number | NumericValue | undefined
118120
```
119121

120122
See Section 6 for the full list of type guards and role interfaces.
@@ -316,12 +318,23 @@ if (isSymbol(expr)) {
316318
console.log(expr.symbol);
317319
}
318320

321+
// Pass a symbol name to narrow and check the name in one step:
322+
if (isSymbol(expr, 'Pi')) {
323+
// expr is a symbol with name "Pi"
324+
}
325+
319326
if (isFunction(expr)) {
320327
// expr.ops is `ReadonlyArray<Expression>` (not undefined)
321328
// expr.isFunctionExpression is `true`
322329
console.log(expr.ops, expr.nops, expr.op1);
323330
}
324331

332+
// Pass an operator name to narrow and check the operator in one step:
333+
if (isFunction(expr, 'Add')) {
334+
// expr is a function expression with operator "Add"
335+
console.log(expr.op1, expr.op2);
336+
}
337+
325338
if (isString(expr)) {
326339
// expr.string is `string` (not undefined)
327340
console.log(expr.string);
@@ -344,7 +357,7 @@ if (isIndexedCollection(expr)) {
344357
}
345358
```
346359

347-
### Convenience Helper: `sym()`
360+
### Convenience Helpers: `sym()` and `numericValue()`
348361

349362
For quick symbol name checks, use the `sym()` helper:
350363

@@ -361,13 +374,25 @@ if (sym(expr) === 'Pi') { /* ... */ }
361374
const name = sym(expr); // string | undefined
362375
```
363376

377+
For safe numeric value extraction, use the `numericValue()` helper:
378+
379+
```ts
380+
import { numericValue } from '@cortex-js/compute-engine';
381+
382+
// Instead of:
383+
const val = isNumber(expr) ? expr.numericValue : undefined;
384+
385+
// You can write:
386+
const val = numericValue(expr); // number | NumericValue | undefined
387+
```
388+
364389
### Role Interfaces
365390

366391
| Guard | Narrows to |
367392
| :-------------------- | :---------------------------------------- |
368393
| `isNumber` | `Expression & NumberLiteralInterface` |
369-
| `isSymbol` | `Expression & SymbolInterface` |
370-
| `isFunction` | `Expression & FunctionInterface` |
394+
| `isSymbol` | `Expression & SymbolInterface` (optional second arg: symbol name) |
395+
| `isFunction` | `Expression & FunctionInterface` (optional second arg: operator name) |
371396
| `isString` | `Expression & StringInterface` |
372397
| `isTensor` | `Expression & TensorInterface` |
373398
| `isDictionary` | `Expression & DictionaryInterface` |

src/compute-engine.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export {
6969
isDictionary,
7070
isCollection,
7171
isIndexedCollection,
72+
numericValue,
7273
isBoxedExpression,
7374
isBoxedNumber,
7475
isBoxedSymbol,

src/compute-engine/assume.ts

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ function assumeEquality(proposition: Expression): AssumeResult {
100100
const unknowns = proposition.unknowns;
101101
if (unknowns.length === 0) {
102102
const val = proposition.evaluate();
103-
if (isSymbol(val) && val.symbol === 'True') return 'tautology';
104-
if (isSymbol(val) && val.symbol === 'False') return 'contradiction';
103+
if (isSymbol(val, 'True')) return 'tautology';
104+
if (isSymbol(val, 'False')) return 'contradiction';
105105
console.log(proposition.canonical.evaluate());
106106
return 'not-a-predicate';
107107
}
@@ -254,8 +254,8 @@ function assumeInequality(proposition: Expression): AssumeResult {
254254
// Case 2
255255
const result = ce.box([op === '<' ? 'Less' : 'LessEqual', p, 0]).evaluate();
256256

257-
if (isSymbol(result) && result.symbol === 'True') return 'tautology';
258-
if (isSymbol(result) && result.symbol === 'False') return 'contradiction';
257+
if (isSymbol(result, 'True')) return 'tautology';
258+
if (isSymbol(result, 'False')) return 'contradiction';
259259

260260
const unknowns = result.unknowns;
261261
if (unknowns.length === 0) return 'not-a-predicate';
@@ -274,7 +274,7 @@ function assumeInequality(proposition: Expression): AssumeResult {
274274
const originalOp = proposition.operator;
275275
const propOp1 = proposition.op1;
276276
const propOp2 = proposition.op2;
277-
const isSymbolOnLeft = isSymbol(propOp1) && propOp1.symbol === symbol;
277+
const isSymbolOnLeft = isSymbol(propOp1, symbol);
278278
const otherSide = isSymbolOnLeft ? propOp2 : propOp1;
279279

280280
// Only do bounds checking for simple comparisons like "x > k" where k is numeric
@@ -486,8 +486,8 @@ function assumeElement(proposition: Expression): AssumeResult {
486486

487487
// Case 4
488488
const val = proposition.evaluate();
489-
if (isSymbol(val) && val.symbol === 'True') return 'tautology';
490-
if (isSymbol(val) && val.symbol === 'False') return 'contradiction';
489+
if (isSymbol(val, 'True')) return 'tautology';
490+
if (isSymbol(val, 'False')) return 'contradiction';
491491
return 'not-a-predicate';
492492
}
493493

@@ -550,36 +550,31 @@ export function getSignFromAssumptions(
550550
// Case 1: Direct symbol comparison
551551
// x < 0 means x is negative
552552
// x <= 0 means x is non-positive
553-
if (isSymbol(lhs) && lhs.symbol === symbol) {
553+
if (isSymbol(lhs, symbol)) {
554554
if (op === 'Less') return 'negative';
555555
if (op === 'LessEqual') return 'non-positive';
556556
}
557557

558558
// Case 2: Negated symbol comparison
559559
// -x < 0 means x > 0 (positive)
560560
// -x <= 0 means x >= 0 (non-negative)
561-
if (
562-
isFunction(lhs) &&
563-
lhs.operator === 'Negate' &&
564-
isSymbol(lhs.op1) &&
565-
lhs.op1.symbol === symbol
566-
) {
561+
if (isFunction(lhs, 'Negate') && isSymbol(lhs.op1, symbol)) {
567562
if (op === 'Less') return 'positive';
568563
if (op === 'LessEqual') return 'non-negative';
569564
}
570565

571566
// Case 3: Symbol with subtraction from constant
572567
// a - x < 0 means x > a, so if a >= 0, x is positive
573568
// x - a < 0 means x < a, so if a <= 0, x is negative
574-
if (isFunction(lhs) && lhs.operator === 'Subtract') {
569+
if (isFunction(lhs, 'Subtract')) {
575570
const [a, b] = lhs.ops;
576571
if (a && b) {
577572
// a - x < 0 => x > a
578-
if (isSymbol(b) && b.symbol === symbol && a.isNonNegative === true) {
573+
if (isSymbol(b, symbol) && a.isNonNegative === true) {
579574
if (op === 'Less') return 'positive';
580575
}
581576
// x - a < 0 => x < a
582-
if (isSymbol(a) && a.symbol === symbol && b.isNonPositive === true) {
577+
if (isSymbol(a, symbol) && b.isNonPositive === true) {
583578
if (op === 'Less') return 'negative';
584579
}
585580
}
@@ -588,10 +583,10 @@ export function getSignFromAssumptions(
588583
// Case 4: Addition form (canonical form of subtraction)
589584
// x + (-a) < 0 means x < a, so if a <= 0, x is negative
590585
// -x + a < 0 means -x < -a means x > a, so if a >= 0, x is positive
591-
if (isFunction(lhs) && lhs.operator === 'Add') {
586+
if (isFunction(lhs, 'Add')) {
592587
for (const term of lhs.ops) {
593588
// Direct symbol in sum: check if other terms give us bounds
594-
if (isSymbol(term) && term.symbol === symbol) {
589+
if (isSymbol(term, symbol)) {
595590
// x + ... < 0, check if other terms are all non-negative
596591
// That would mean x < -(sum of others), so x < non-positive = negative
597592
const otherTerms = lhs.ops.filter((t) => t !== term);
@@ -604,12 +599,7 @@ export function getSignFromAssumptions(
604599
}
605600
}
606601
// Negated symbol in sum: -x + ... < 0
607-
if (
608-
isFunction(term) &&
609-
term.operator === 'Negate' &&
610-
isSymbol(term.op1) &&
611-
term.op1.symbol === symbol
612-
) {
602+
if (isFunction(term, 'Negate') && isSymbol(term.op1, symbol)) {
613603
// -x + ... < 0 means x > ...
614604
const otherTerms = lhs.ops.filter((t) => t !== term);
615605
if (

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,9 +480,8 @@ export abstract class _BoxedExpression implements Expression {
480480

481481
if (typeof other === 'boolean') {
482482
const val = this.value;
483-
if (other === true) return isSymbol(val) ? val.symbol === 'True' : false;
484-
if (other === false)
485-
return isSymbol(val) ? val.symbol === 'False' : false;
483+
if (other === true) return isSymbol(val, 'True');
484+
if (other === false) return isSymbol(val, 'False');
486485
return false;
487486
}
488487

src/compute-engine/boxed-expression/arithmetic-add.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export function canonicalAdd(
103103
if (isNumber(op)) {
104104
// Skip pure imaginary terms (already summed above)
105105
const facExpr = getImaginaryFactor(op);
106-
if (facExpr !== undefined && isNumber(facExpr)) {
106+
if (isNumber(facExpr)) {
107107
const f = facExpr.numericValue;
108108
const im = typeof f === 'number' ? f : f.re;
109109
if (im !== 0 && typeof im === 'number') continue;
@@ -292,7 +292,7 @@ export class Terms {
292292
this.terms = [{ term: ce.ComplexInfinity, coef: [] }];
293293
return;
294294
}
295-
if (term.isNaN || (isSymbol(term) && term.symbol === 'Undefined')) {
295+
if (term.isNaN || isSymbol(term, 'Undefined')) {
296296
this.terms = [{ term: ce.NaN, coef: [] }];
297297
return;
298298
}
@@ -337,15 +337,15 @@ export class Terms {
337337
return;
338338
}
339339

340-
if (isFunction(term) && term.operator === 'Add') {
340+
if (isFunction(term, 'Add')) {
341341
for (const x of term.ops) {
342342
const [c, t] = x.toNumericValue();
343343
this._add(coef.mul(c), t);
344344
}
345345
return;
346346
}
347347

348-
if (isFunction(term) && term.operator === 'Negate') {
348+
if (isFunction(term, 'Negate')) {
349349
this._add(coef.neg(), term.op1);
350350
return;
351351
}

0 commit comments

Comments
 (0)