Skip to content

Commit 11e948e

Browse files
authored
Merge branch 'main' into summation_extension
2 parents 80f878a + 19d4d5f commit 11e948e

Some content is hidden

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

56 files changed

+1028
-695
lines changed

CHANGELOG.md

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
1-
## 0.22.0
1+
## [Unreleased]
22

3-
**Release Date:** 2023-11-13
3+
## Bug Fixes
4+
5+
- The expression `\frac5 7` is now parsed correctly as `\frac{5}{7}` instead of
6+
`\frac{5}{}7`.
7+
- Do not sugar non-canonical expression. Previously,
8+
`ce.parse('\frac{1}{2}', {canonical: false})` would return `Half` instead of
9+
`['Divide', '1', '2']`.
10+
- **#132** Attempting to set a value to 0 with
11+
`ce.defineSymbol("count", {value: 0})` would fail: the symbol would be
12+
undefined.
13+
14+
## Improvements
15+
16+
- Significant improvements to symbolic computation. Now, boxing,
17+
canonicalization and evaluation are more consistent and produce more
18+
predictable results.
19+
- Adedd the `\neg` command, synonym for `\lnot` -> `Not`.
20+
21+
## 0.22.0
22+
23+
**Release Date:** 2023-11-13
424

525
### Breaking Changes
626

src/compute-engine/assume.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ function assumeEquality(proposition: BoxedExpression): AssumeResult {
9898
ce.assumptions.set(
9999
ce.fn('Equal', [
100100
ce
101-
.add([proposition.op1.canonical, ce.neg(proposition.op2.canonical)])
101+
.add(proposition.op1.canonical, ce.neg(proposition.op2.canonical))
102102
.simplify(),
103103
0,
104104
]),
@@ -193,7 +193,7 @@ function assumeInequality(proposition: BoxedExpression): AssumeResult {
193193
op = '<=';
194194
}
195195
if (!op) return 'internal-error';
196-
const p = ce.add([lhs!.canonical, ce.neg(rhs!.canonical)]).simplify();
196+
const p = ce.add(lhs!.canonical, ce.neg(rhs!.canonical)).simplify();
197197

198198
// Case 2
199199
const result = ce.box([op === '<' ? 'Less' : 'LessEqual', p, 0]).evaluate();

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ export function boxFunction(
352352
const op1 = box(ce, ops[0], options);
353353
const im = asFloat(op1);
354354
if (im !== null && im !== 0) return ce.number(ce.complex(0, im), options);
355-
return ce.mul([op1, ce.I]);
355+
return ce.mul(op1, ce.I);
356356
}
357357
if (ops.length === 2) {
358358
const op1 = box(ce, ops[0], options);
@@ -365,7 +365,7 @@ export function boxFunction(
365365
return ce.number(ce.complex(re, im), options);
366366
return op1;
367367
}
368-
return ce.add([op1, ce.mul([op2, ce.I])], options.metadata);
368+
return ce.add(op1, ce.mul(op2, ce.I));
369369
}
370370
}
371371

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

+19-9
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ import { apply } from '../function-utils';
4040
import { shouldHold } from '../symbolic/utils';
4141
import { at, isFiniteIndexableCollection } from '../collection-utils';
4242
import { narrow } from './boxed-domain';
43+
import { canonicalAdd } from '../library/arithmetic-add';
44+
import { canonicalPower } from '../library/arithmetic-power';
45+
import { canonicalNegate } from '../symbolic/negate';
46+
import { canonicalDivide } from '../library/arithmetic-divide';
47+
import { canonicalMultiply } from '../library/arithmetic-multiply';
4348

4449
/**
4550
* A boxed function represent an expression that can be
@@ -374,7 +379,7 @@ export class BoxedFunction extends _BoxedExpression {
374379
if (s !== undefined) return false;
375380

376381
// Try to simplify the difference of the expressions
377-
const diff = this.engine.add([this, this.engine.neg(rhs)]).simplify();
382+
const diff = this.engine.add(this, this.engine.neg(rhs)).simplify();
378383
if (diff.isZero) return true;
379384

380385
return this.isSame(rhs);
@@ -690,19 +695,24 @@ function makeNumericFunction(
690695
// Short path for some functions
691696
// (avoid looking up a definition)
692697
//
693-
if (head === 'Add') return ce.add(ops, metadata);
694-
if (head === 'Negate') return ce.neg(ops[0], metadata);
695-
if (head === 'Multiply') return ce.mul(ops, metadata);
696-
if (head === 'Divide') return ce.div(ops[0], ops[1], metadata);
697-
if (head === 'Exp') return ce.pow(ce.E, ops[0], metadata);
698-
if (head === 'Power') return ce.pow(ops[0], ops[1], metadata);
699-
if (head === 'Square') return ce.pow(ops[0], 2, metadata);
698+
if (head === 'Add')
699+
return canonicalAdd(ce, flattenOps(flattenSequence(ops), 'Add'));
700+
if (head === 'Negate') return canonicalNegate(ops[0]);
701+
if (head === 'Multiply')
702+
return canonicalMultiply(ce, flattenOps(flattenSequence(ops), 'Multiply'));
703+
if (head === 'Divide')
704+
return canonicalDivide(ce, ops[0].canonical, ops[1].canonical);
705+
if (head === 'Exp') return canonicalPower(ce, ce.E, ops[0].canonical);
706+
if (head === 'Power')
707+
return canonicalPower(ce, ops[0].canonical, ops[1].canonical);
708+
if (head === 'Square')
709+
return canonicalPower(ce, ops[0].canonical, ce.number(2));
700710
if (head === 'Sqrt') {
701711
const op = ops[0].canonical;
702712
// We preserve square roots of rationals as "exact" values
703713
if (isRational(op.numericValue)) return ce._fn('Sqrt', [op], metadata);
704714

705-
return ce.pow(op, ce.Half, metadata);
715+
return canonicalPower(ce, op, ce.Half);
706716
}
707717
if (head === 'Ln') return ce._fn('Ln', ops, metadata);
708718

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class _BoxedSymbolDefinition implements BoxedSymbolDefinition {
9797
this._defValue = def.value;
9898
this._value = null;
9999
} else {
100-
if (def.value) {
100+
if (def.value !== undefined) {
101101
if (isLatexString(def.value))
102102
this._value = ce.parse(def.value) ?? ce.symbol('Undefined');
103103
else if (typeof def.value === 'function')

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

+46-48
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,36 @@ export function serializeJsonCanonicalFunction(
166166
}
167167
}
168168

169+
if (head === 'Add' && args.length === 2 && !exclusions.includes('Subtract')) {
170+
if (args[1]?.numericValue !== null) {
171+
const t1 = asSmallInteger(args[1]);
172+
if (t1 !== null && t1 < 0)
173+
return serializeJsonFunction(
174+
ce,
175+
'Subtract',
176+
[args[0], ce.number(-t1)],
177+
metadata
178+
);
179+
}
180+
if (args[1]?.head === 'Negate') {
181+
return serializeJsonFunction(
182+
ce,
183+
'Subtract',
184+
[args[0], args[1].op1],
185+
metadata
186+
);
187+
}
188+
}
189+
190+
if (head === 'Tuple') {
191+
if (args.length === 1 && !exclusions.includes('Single'))
192+
return serializeJsonFunction(ce, 'Single', args, metadata);
193+
if (args.length === 2 && !exclusions.includes('Pair'))
194+
return serializeJsonFunction(ce, 'Pair', args, metadata);
195+
if (args.length === 3 && !exclusions.includes('Triple'))
196+
return serializeJsonFunction(ce, 'Triple', args, metadata);
197+
}
198+
169199
return serializeJsonFunction(ce, head, args, metadata);
170200
}
171201

@@ -175,32 +205,27 @@ export function serializeJsonFunction(
175205
args: (undefined | BoxedExpression)[],
176206
metadata?: Metadata
177207
): Expression {
178-
// Special case some functions...
179-
180208
const exclusions = ce.jsonSerializationOptions.exclude;
181209

182-
if (
183-
(head === 'Rational' || head === 'Divide') &&
184-
args.length === 2 &&
185-
asSmallInteger(args[0]) === 1 &&
186-
asSmallInteger(args[1]) === 2 &&
187-
!exclusions.includes('Half')
188-
) {
189-
return serializeJsonSymbol(ce, 'Half', {
190-
...metadata,
191-
wikidata: 'Q39373172',
192-
});
193-
}
194-
195-
if (args.length === 1) {
210+
//
211+
// The only sugaring done for both canonical and non-canonical expressions
212+
// is for 'Negate', since `-2` gets parsed as `['Negate', 2]` and not
213+
// `-2`.
214+
//
215+
if (head === 'Negate' && args.length === 1) {
196216
const num0 = args[0]?.numericValue;
197-
if (head === 'Negate' && num0 !== null) {
217+
if (num0 !== null) {
198218
if (typeof num0 === 'number') return serializeJsonNumber(ce, -num0);
199219
if (num0 instanceof Decimal) return serializeJsonNumber(ce, num0.neg());
200220
if (num0 instanceof Complex) return serializeJsonNumber(ce, num0.neg());
201221
if (isRational(num0)) return serializeJsonNumber(ce, neg(num0));
202222
}
203223
}
224+
225+
//
226+
// If there are some exclusions, try to avoid them.
227+
// This is done both to canonical or non-canonical expressions.
228+
//
204229
if (typeof head === 'string' && exclusions.includes(head)) {
205230
if (head === 'Rational' && args.length === 2)
206231
return serializeJsonFunction(ce, 'Divide', args, metadata);
@@ -267,8 +292,11 @@ export function serializeJsonFunction(
267292
if (head === 'Exp' && args.length === 1)
268293
return serializeJsonFunction(ce, 'Power', [ce.E, args[0]], metadata);
269294

295+
if (head === 'Pair' || head == 'Single' || head === 'Triple')
296+
return serializeJsonFunction(ce, 'Tuple', args, metadata);
297+
270298
// Note: even though 'Subtract' is boxed out, we still need to handle it here
271-
// because the function may be called with a 'Subtract' head.
299+
// because the function may be called with a non-canonical 'Subtract' head.
272300
if (head === 'Subtract' && args.length === 2)
273301
return serializeJsonFunction(
274302
ce,
@@ -280,36 +308,6 @@ export function serializeJsonFunction(
280308
return serializeJsonFunction(ce, 'Negate', args, metadata);
281309
}
282310

283-
if (head === 'Add' && args.length === 2 && !exclusions.includes('Subtract')) {
284-
if (args[1]?.numericValue !== null) {
285-
const t1 = asSmallInteger(args[1]);
286-
if (t1 !== null && t1 < 0)
287-
return serializeJsonFunction(
288-
ce,
289-
'Subtract',
290-
[args[0], ce.number(-t1)],
291-
metadata
292-
);
293-
}
294-
if (args[1]?.head === 'Negate') {
295-
return serializeJsonFunction(
296-
ce,
297-
'Subtract',
298-
[args[0], args[1].op1],
299-
metadata
300-
);
301-
}
302-
}
303-
304-
if (head === 'Tuple') {
305-
if (args.length === 1 && !exclusions.includes('Single'))
306-
return serializeJsonFunction(ce, 'Single', args, metadata);
307-
if (args.length === 2 && !exclusions.includes('Pair'))
308-
return serializeJsonFunction(ce, 'Pair', args, metadata);
309-
if (args.length === 3 && !exclusions.includes('Triple'))
310-
return serializeJsonFunction(ce, 'Triple', args, metadata);
311-
}
312-
313311
const jsonHead =
314312
typeof head === 'string' ? _escapeJsonString(head) : head.json;
315313

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,16 @@ export function getSymbols(expr: BoxedExpression, result: Set<string>): void {
7070
*/
7171
export function getUnknowns(expr: BoxedExpression, result: Set<string>): void {
7272
if (expr.symbol) {
73-
const def = expr.engine.lookupSymbol(expr.symbol);
73+
const s = expr.symbol;
74+
if (s === 'Unknown' || s === 'Undefined' || s === 'Nothing') return;
75+
76+
const def = expr.engine.lookupSymbol(s);
7477
if (def && def.value !== undefined) return;
7578

76-
const fnDef = expr.engine.lookupFunction(expr.symbol);
79+
const fnDef = expr.engine.lookupFunction(s);
7780
if (fnDef && (fnDef.signature.evaluate || fnDef.signature.N)) return;
7881

79-
result.add(expr.symbol);
82+
result.add(s);
8083
return;
8184
}
8285

0 commit comments

Comments
 (0)