Skip to content

Commit a25cc37

Browse files
committed
fix: BoxedSymbol property computations (various)
- Fixes the return value of 'sgn' for BoxedNumbers with a value of 'NaN': as a corollary fixes & now correctly computes return-value of getter `BoxedSymbol.isNaN`, too. - Fix: for consistency, 'isNaN' & 'isInfinity' now trigger definition binding (canonicalization), in similar manner to 'isOdd', 'isEven', 'isInteger'... (This is appropriate because these may be considered direct 'value inquiry' getters, similarly to these sibling properties) - Adds missing getter 'isFinite': computing the result using 'sgn' like its sign-checking siblings 'isPositive', 'isNegative'... - Fix: several properties/getters now return a boolean in cases where 'undefined' should be returned (because, the symbol is unbound and therefore not enough information is known). ^Notably, this is restricted to the aforementioned - 'sgn' referencing - properties 'is(NaN/Infinity)' et cetera. - Fix: revise getter 'isInfinity' to now account for 'complex-infinity' Also includes various extra doc., including some corrections
1 parent 2a24e45 commit a25cc37

File tree

6 files changed

+95
-32
lines changed

6 files changed

+95
-32
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ export class BoxedNumber extends _BoxedExpression {
364364

365365
let s: number | undefined;
366366
if (typeof this._value === 'number') {
367-
if (Number.isNaN(this._value)) return 'unsigned';
367+
if (Number.isNaN(this._value)) return 'nan';
368368
s = Math.sign(this._value);
369369
} else s = this._value.sgn();
370370
// The sign of a complex Numeric Value is `undefined`

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

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
nonPositiveSign,
5151
negativeSign,
5252
nonNegativeSign,
53+
infinitySgn,
5354
} from './sgn';
5455
import type { BigNum } from '../numerics/types';
5556
import type { OneOf } from '../../common/one-of';
@@ -68,27 +69,47 @@ import { BoxedType } from '../../common/type/boxed-type';
6869
* not a function expression, i.e. `Sin`, not `["Sin", "Pi"]`. This is used
6970
* for example in `["InverseFunction", "Sin"]`
7071
*
72+
* BoxedSymbols are bound *lazily* to a definition: unless constructed with either 'canonical =
73+
* true' or a definition is explicitly given.
74+
*
75+
* Various, predicate-based properties & getters for this class either:
76+
*
77+
* - Trigger the symbol to be bound (if not already). This encompasses those properties that
78+
* imply, or rely on accessing the symbol definition - including its value or type. Included are:
79+
* 'value', 'isOdd/Even', 're', 'im', or 'isReal/Rational': amongst others.
80+
*
81+
* - Do not trigger the symbol to be bound, but nevertheless require this to be the case to return
82+
* an effective value.
83+
* These properties typically return with type `boolean | undefined`, and spans those such as
84+
* 'isNaN', 'isInfinity' 'isPositive' & 'isNegative'.
85+
* Notably, this is the case for properties which relate to a *range* of values or 'assumptions'
86+
* about the symbol (e.g. 'is..Positive/Negative'); or in which looking up the value directly is
87+
* not necessary to compute the result (e.g. 'isFinite').
88+
*
89+
*
90+
* Methods which require consultation to a definition value also trigger binding: comparison methods
91+
* ('is', 'isSame', 'isEqual', 'isLess'...), 'evaluate' & 'N', and methods pertinent to
92+
* 'collection'-valued symbols such as 'contains' & 'subsetOf'.
93+
*
7194
*
7295
*/
7396
export class BoxedSymbol extends _BoxedExpression {
7497
private _scope: RuntimeScope | null;
7598
protected _id: string;
7699
private _hash: number | undefined;
77100

78-
// Note: a `BoxedSymbol` is bound lazily to a definition. This is important
79-
// during the creation of a scope to avoid circular references.
101+
// Note: a `BoxedSymbol` is bound lazily to a definition: unless constructed canonically, or with
102+
// a given/explicit definition. This is important during the creation of a scope to avoid
103+
// circular references.
80104
//
81105
// This can also happen if a symbol is used before being defined
82106
// and the engine has no default domain specified. If there
83107
// is a default domain, a definition is created automatically.
84108

85-
// `undefined` indicate the symbol has not been bound yet,
86-
// `null` indicate that the symbol is not canonical and it should not be
87-
// bound.
109+
// `undefined` indicates that the symbol has not been bound yet
88110

89111
private _def:
90112
| OneOf<[BoxedSymbolDefinition, BoxedFunctionDefinition]>
91-
| null
92113
| undefined;
93114

94115
private _isStructural: boolean = false;
@@ -114,6 +135,7 @@ export class BoxedSymbol extends _BoxedExpression {
114135

115136
if (options?.structural) this._isStructural = true;
116137

138+
//(A symbol having a _scope is indicator of it being canonical)
117139
if ((options?.canonical ?? true) !== true) this._scope = null;
118140
else if (this._def) this._scope = ce.context;
119141
else this.bind();
@@ -165,7 +187,13 @@ export class BoxedSymbol extends _BoxedExpression {
165187
return ce.lookupSymbol(this._id) ?? ce.lookupFunction(this._id);
166188
}
167189

168-
/** This method returns the definition associated with the value of this symbol, or associated with the symbol if it has no value. This is the definition to use with most operations on the symbol. Indeed, "x[2]" is accessing the second element of **the value** of "x".*/
190+
/** This method returns the definition associated with the value of this symbol, or associated with
191+
* the symbol if it has no value. Used primarily to check, or obtain the value definition for, the
192+
* case where this symbol has a 'collection' definition
193+
*
194+
* This is the definition to use with most operations on the symbol. Indeed, "x[2]" is accessing
195+
* the second element of **the value** of "x".
196+
*/
169197
private _getDef(): BoxedBaseDefinition | undefined {
170198
let def: BoxedBaseDefinition | BoxedSymbolDefinition | undefined =
171199
this.symbolDefinition;
@@ -500,7 +528,7 @@ export class BoxedSymbol extends _BoxedExpression {
500528
}
501529

502530
// The type of the value of the symbol.
503-
// If the symbol is not bound to a definition, the type is 'any'
531+
// If the symbol is not bound to a definition, the type is 'unknown'
504532
get type(): BoxedType {
505533
const def = this._def;
506534
if (!def) return BoxedType.unknown;
@@ -555,6 +583,9 @@ export class BoxedSymbol extends _BoxedExpression {
555583
return match(this, pattern, options);
556584
}
557585

586+
/**
587+
* **Note**: This check _binds_ the symbol.
588+
*/
558589
get isFunction(): boolean | undefined {
559590
return !!this.functionDefinition;
560591
}
@@ -567,14 +598,18 @@ export class BoxedSymbol extends _BoxedExpression {
567598
return this.symbolDefinition?.even;
568599
}
569600

570-
get isInfinity(): boolean | undefined {
601+
get isFinite(): boolean | undefined {
571602
const s = this.sgn;
572-
return s === 'negative-infinity' || s === 'positive-infinity';
603+
if (!s) return undefined;
604+
return !(infinitySgn(s) || s === 'nan');
605+
}
606+
607+
get isInfinity(): boolean | undefined {
608+
return infinitySgn(this.symbolDefinition?.sgn);
573609
}
574610

575611
get isNaN(): boolean | undefined {
576-
const s = this.sgn;
577-
return s === 'nan';
612+
return this.symbolDefinition?.sgn === 'nan';
578613
}
579614

580615
// x > 0
@@ -594,6 +629,12 @@ export class BoxedSymbol extends _BoxedExpression {
594629
return nonNegativeSign(this.sgn);
595630
}
596631

632+
/**
633+
*
634+
* Checking this does *not bind* the definition if this is symbol.
635+
*
636+
* @inheritdoc
637+
*/
597638
get isNumber(): boolean | undefined {
598639
// Since we infer the type of a symbol to a `number`, we don't need
599640
// to check if the type was inferred.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ export function sgn(expr: BoxedExpression): Sign | undefined {
3535
return 'unsigned';
3636
}
3737

38+
export function infinitySgn(s: Sign | undefined): boolean | undefined {
39+
if (s === undefined) return undefined;
40+
return (
41+
s === 'positive-infinity' ||
42+
s === 'negative-infinity' ||
43+
s === 'complex-infinity'
44+
);
45+
}
46+
3847
// > 0
3948
export function positiveSign(s: Sign | undefined): boolean | undefined {
4049
if (s === undefined) return undefined;

src/compute-engine/global-types.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,21 @@ import type {
2626
MathJsonFunction,
2727
MathJsonIdentifier,
2828
} from '../math-json';
29-
import {
29+
import type {
3030
LatexDictionaryEntry,
3131
LatexString,
3232
ParseLatexOptions,
3333
SerializeLatexOptions,
3434
} from './latex-syntax/types';
35-
import {
35+
import type {
3636
ExactNumericValueData,
3737
NumericValue,
3838
NumericValueData,
3939
} from './numeric-value/types';
40-
import { BigNum, IBigNum, Rational } from './numerics/types';
41-
import { Type, TypeString } from '../common/type/types';
42-
import { BoxedType } from '../common/type/boxed-type';
43-
import { IndexedLatexDictionary } from './latex-syntax/dictionary/definitions';
40+
import type { BigNum, IBigNum, Rational } from './numerics/types';
41+
import type { Type, TypeString } from '../common/type/types';
42+
import type { BoxedType } from '../common/type/boxed-type';
43+
import type { IndexedLatexDictionary } from './latex-syntax/dictionary/definitions';
4444

4545
/** @category Compiling */
4646
export type CompiledType = boolean | number | string | object;
@@ -711,13 +711,17 @@ export interface BoxedExpression {
711711
* Note that if `isNaN` is true, `isNumber` is also true (yes, `NaN` is a
712712
* number).
713713
*
714+
* If this expression is a symbol, this lookup also causes binding to a definition.
715+
*
714716
* @category Numeric Expression
715717
*
716718
*/
717719
readonly isNaN: boolean | undefined;
718720

719721
/**
720-
* The numeric value of this expression is `±Infinity` or Complex Infinity
722+
* The numeric value of this expression is `±Infinity` or Complex Infinity.
723+
*
724+
* If this is a symbol, causes it to be bound to a definition.
721725
*
722726
* @category Numeric Expression
723727
*/
@@ -901,6 +905,9 @@ export interface BoxedExpression {
901905
* will return `positive` if the symbol is assumed to be positive
902906
* (using `ce.assume()`).
903907
*
908+
* For a symbol also, requires that the symbol be bound with its definition (i.e. canonical);
909+
* otherwise, will return `undefined`.
910+
*
904911
* @category Numeric Expression
905912
*
906913
*/
@@ -1010,7 +1017,8 @@ export interface BoxedExpression {
10101017
* definition.
10111018
*
10121019
* :::info[Note]
1013-
* `undefined` if not a canonical expression.
1020+
* For a symbol, always binds - potentially creating - a definition. For `BoxedFunctions`, will
1021+
* return `undefined` if not canonical.
10141022
* :::
10151023
*
10161024
*/
@@ -1029,6 +1037,8 @@ export interface BoxedExpression {
10291037
/**
10301038
* For symbols, a definition associated with the expression.
10311039
*
1040+
* Bind the expression to a definition, if not already bound.
1041+
*
10321042
* Return `undefined` if not a symbol
10331043
*
10341044
*/
@@ -1205,14 +1215,13 @@ export interface BoxedExpression {
12051215
get value(): number | boolean | string | object | undefined;
12061216

12071217
/**
1208-
* Only the value of variables can be changed (symbols that are not
1209-
* constants).
1218+
* Set the value of this expression (applicable only to `BoxedSymbol`).
12101219
*
1211-
* Throws a runtime error if a constant.
1220+
* Will throw a runtime error if either not a BoxedSymbol, or if a symbol expression which is
1221+
* non-variable/constant.
12121222
*
1213-
* :::info[Note]
1214-
* If non-canonical, does nothing
1215-
* :::
1223+
* Setting the value of a symbol results in the forgetting of all assumptions about it in the
1224+
* current scope.
12161225
*
12171226
*/
12181227
set value(
@@ -1241,6 +1250,7 @@ export interface BoxedExpression {
12411250
* If not valid, return `"error"`.
12421251
* If non-canonical, return `undefined`.
12431252
* If the type is not known, return `"unknown"`.
1253+
* If a symbol with a 'function' definition, returns the 'signature' type.
12441254
* :::
12451255
*
12461256
*/
@@ -1336,7 +1346,10 @@ export interface BoxedExpression {
13361346
isEqual(other: number | BoxedExpression): boolean | undefined;
13371347

13381348
/**
1339-
* Return true if the expression is a collection: a list, a vector, a matrix, a map, a tuple, etc...
1349+
* Return true if the expression is a collection: a list, a vector, a matrix, a map, a tuple,
1350+
* etc...
1351+
*
1352+
* For symbols, this check involves binding to a definition, if not already canonical.
13401353
*/
13411354
isCollection: boolean;
13421355

src/compute-engine/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2175,7 +2175,7 @@ export class ComputeEngine implements IComputeEngine {
21752175
}
21762176
}
21772177

2178-
/** Remove all assumptions about one or more symbols */
2178+
/** Remove all assumptions (in the current scope) about one or more symbols */
21792179
forget(symbol: undefined | string | string[]): void {
21802180
if (!this.context) throw Error('No scope available');
21812181

test/compute-engine/numbers.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { BoxedExpression } from '../../src/compute-engine.ts';
21
import { Expression } from '../../src/math-json/types.ts';
2+
import type { BoxedExpression } from '../../src/compute-engine/global-types.ts';
33
import { engine as ce } from '../utils';
44

55
describe('BOXING OF NUMBER', () => {
@@ -198,7 +198,7 @@ describe('PROPERTIES OF NUMBERS', () => {
198198
-1: false
199199
0: false
200200
+1: false
201-
finite: undefined
201+
finite: true
202202
infinite: false
203203
nan: false
204204
even: undefined
@@ -374,7 +374,7 @@ describe('PROPERTIES OF NUMBERS', () => {
374374
-1: false
375375
0: false
376376
+1: false
377-
finite: undefined
377+
finite: true
378378
infinite: false
379379
nan: false
380380
even: undefined

0 commit comments

Comments
 (0)