Skip to content

Commit 4bea486

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/add-integration-patterns-9zUpR
2 parents 4f8ef93 + 9fae900 commit 4bea486

15 files changed

Lines changed: 1464 additions & 232 deletions

File tree

.gitignore

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,6 @@ CLAUDE.md
33

44
doc/
55

6-
# Auto-generated API documentation
7-
src/api.md
8-
9-
TODO.md
10-
REVIEW.md
11-
PLAYGROUND.md
12-
136
build/
147
dist/
158
stage/

CHANGELOG.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,37 @@
216216

217217
Note: OEIS lookups require network access to oeis.org.
218218

219+
- **Multi-Index Sequences**: Define sequences with multiple indices like Pascal's
220+
triangle P_{n,k} or grid-based recurrences:
221+
222+
```javascript
223+
// Pascal's Triangle: P_{n,k} = P_{n-1,k-1} + P_{n-1,k}
224+
ce.declareSequence('P', {
225+
variables: ['n', 'k'],
226+
base: { 'n,0': 1, 'n,n': 1 }, // Pattern-based base cases
227+
recurrence: 'P_{n-1,k-1} + P_{n-1,k}',
228+
domain: { n: { min: 0 }, k: { min: 0 } },
229+
constraints: 'k <= n', // k must not exceed n
230+
});
231+
232+
ce.parse('P_{5,2}').evaluate(); // → 10
233+
ce.parse('P_{10,5}').evaluate(); // → 252
234+
```
235+
236+
Features:
237+
- Multiple index variables with `variables: ['n', 'k']`
238+
- Pattern-based base cases: `'n,0'` matches any (n, 0), `'n,n'` matches diagonal
239+
- Per-variable domain constraints
240+
- Constraint expressions (e.g., `'k <= n'`)
241+
- Composite key memoization (e.g., `'5,2'`)
242+
- Full introspection support with `isMultiIndex` flag
243+
244+
Pattern matching for base cases:
245+
- Exact values: `'0,0'` matches only (0, 0)
246+
- Wildcards: `'n,0'` matches any value for n with k=0
247+
- Equality: `'n,n'` matches when both indices are equal
248+
- Priority: exact matches are checked before patterns
249+
219250
#### Special Functions
220251

221252
- **Special Function Definitions**: Added type signatures for special mathematical
@@ -383,6 +414,20 @@
383414
guard against pathological expressions. All recursive calls now track depth
384415
and gracefully return `undefined` if the limit is exceeded.
385416

417+
### Bug Fixes
418+
419+
- **Equation Equivalence in `isEqual()`** (Issue #275): Two equations are now
420+
correctly recognized as equivalent if they have the same solution set:
421+
422+
```javascript
423+
ce.parse('2x+1=0').isEqual(ce.parse('x=-1/2')); // → true
424+
ce.parse('3x+1=0').isEqual(ce.parse('6x+2=0')); // → true
425+
```
426+
427+
The implementation uses a sampling-based approach to check if the ratio of
428+
(LHS₁-RHS₁) to (LHS₂-RHS₂) is a non-zero constant, which indicates equivalent
429+
solution sets.
430+
386431
## 0.33.0 _2026-01-30_
387432

388433
### Bug Fixes

PLAYGROUND.md

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
# Playground Test Results
2+
3+
This document summarizes the status of various test snippets from
4+
`test/playground.ts`.
5+
6+
## Summary
7+
8+
| Category | Fixed | Issues Remaining |
9+
| ----------------- | ----- | ---------------- |
10+
| Parsing | 14 | 6 |
11+
| Simplification | 18 | 0 |
12+
| Evaluation | 17 | 1 |
13+
| Solve | 4 | 1 |
14+
| Matrix Operations | 6 | 0 |
15+
| Pattern Matching | 0 | 2 |
16+
| Formatting | 3 | 0 |
17+
| Other | 4 | 1 |
18+
19+
---
20+
21+
## Parsing
22+
23+
### Working Correctly
24+
25+
| Test | Line | Result |
26+
| ----------------------------------------------- | ------- | ------------------------------------------------ |
27+
| `\textcolor{red}{y + 1}` in expression | 6 | Parses with `Annotated` wrapper |
28+
| Sum with multiple bounds `\sum_{n=0,m=4}^{4,8}` | 8 | Correctly parses as multiple `Limits` |
29+
| `f()` and `f(x)` parsing | 99-103 | All variants parse correctly |
30+
| Plus/minus `21\pm1` | 79-80 | Parses as `PlusMinus`, evaluates to `Tuple` |
31+
| Non-canonical parsing `12+(-2)` | 196-197 | Correctly preserves structure |
32+
| `x_\text{a}` subscript | 226 | Parses as `"x_a"` |
33+
| `f'` derivative notation | 613 | Parses as `["Derivative","f"]` |
34+
| `x_{1,2}` subscript with comma | 615-616 | Parses as `Subscript(x, 1 2)` |
35+
| Sum with subscript `\sum_{n,m} k_{n,m}` | 455 | Correctly uses `Subscript`, not `At` |
36+
| Integral LaTeX output | 618-619 | Correct LaTeX: `\int_{0}^{1}\!x^2\, \mathrm{d}x` |
37+
| `\sin(-23\pi/12)` | 499 | Parses and evaluates correctly |
38+
| Error handling for `(`, `(3+x`, `)` | 668-674 | Correct error messages |
39+
| `\operatorname{HorizontalScaling}(3)` | 628-629 | Parses as `["HorizontalScaling", 3]` |
40+
| Knuth's interval `(a..b)` | 731 | Parses as `["Range", "a", "b"]` |
41+
42+
### Issues Remaining
43+
44+
| Test | Line | Expected | Actual | Notes |
45+
| ------------------------------------- | ------- | ---------------------- | ------------------------------------- | ----------------------------------- |
46+
| `\textcolor{red}{=}` | 11-12 | Styled equals | `Error('expected-closing-delimiter')` | Cannot style delimiters |
47+
| Double integral parsing | 27-31 | Nested integrals | Complex nested structure | Parses but N() times out |
48+
| `\mathrm{x+\alpha}` | 188 | Symbol with operators | `"xplusalpha"` (loses plus) | Characters merged |
49+
| `\mathrm{\oplus}` | 189 | Valid symbol | Error: invalid-first-char | Cannot use operators in mathrm |
50+
| `\gamma(2, 1)` | 430 | `Gamma(2, 1)` function | `"EulerGamma" * (2, 1)` | Should be incomplete gamma |
51+
| `\sin\left(x\right.` | 645 | Graceful handling | Multiple errors | Missing matchfix handling |
52+
53+
---
54+
55+
## Simplification
56+
57+
### Working Correctly
58+
59+
| Test | Line | Input | Result |
60+
| ---------------------------- | ------- | ---------------------- | -------------- |
61+
| Arithmetic | 277 | `-1234 - 5678` | `-6912` |
62+
| Sqrt addition | 278 | `2\sqrt{3}+\sqrt{1+2}` | `3sqrt(3)` |
63+
| cos(30°) | 281 | `\cos(30\degree)` | `sqrt(3)/2` |
64+
| Abs of pi | 295-298 | `\|-\pi\|` | `pi` |
65+
| Nested abs | 269 | `\|\|a\| + 3\|` | `\|a\| + 3` |
66+
| Multiply simplify | 517 | `2\times3xxx` | `6x^3` |
67+
| Trig identity | 529 | `1+4\sin(\pi/10)` | `sqrt(5)` |
68+
| Sqrt division | 650-658 | `\sqrt{15}/\sqrt{3}` | `sqrt(5)` |
69+
| `x+x` | 244 | `x+x` | `2x` |
70+
| `e^x e^{-x}` | 301-304 | `e^x e^{-x}` | `1` |
71+
| `\log_4(x^2)` | 307-310 | `\log_4(x^2)` | `2 log_4(x)` |
72+
| `\log_4(x^{7/4})` | 319-322 | `\log_4(x^{7/4})` | `7/4 log_4(x)` |
73+
| `\sin(\infty)` | 313-316 | `\sin(\infty)` | `NaN` |
74+
| `\tanh(\infty)` | 402-404 | `\tanh(\infty)` | `1` |
75+
| `\sqrt{x^2}` with `x > 0` | 257-259 | `x` | `x` |
76+
| `\sqrt[4]{x^4}` with `x > 0` | 257-259 | `x` | `x` |
77+
| `\cos(5\pi+k)` | 588-590 | `-cos(k)` | `-cos(k)` |
78+
| `a \times (c+d)` expand | 561-565 | `ac + ad` | `ac + ad` |
79+
80+
### Notes
81+
82+
- **Distribution/Expansion**: `simplify()` does NOT automatically distribute. Use
83+
`.expand()` to distribute: `a*(c+d)``ac + ad`. This is by design.
84+
- **Assumptions**: Simplifications that depend on assumptions (like `x > 0`) now
85+
work correctly. Use `ce.assume()` before simplifying.
86+
87+
---
88+
89+
## Evaluation
90+
91+
### Working Correctly
92+
93+
| Test | Line | Input | Result |
94+
| ------------------- | ------- | ---------------------------- | --------------------------------- |
95+
| Precision 30 digits | 69-73 | `\pi.N()` | `3.14159265358979323846264338328` |
96+
| Type of fraction | 75 | `3/4` type | `finite_rational` |
97+
| Replace | 58-66 | Note: use `.subs()` instead | Returns `3` (by design) |
98+
| `\sin(\pi^2)` | 181 | Evaluate | `-0.430301217...` |
99+
| Solve `x^2-1=0` | 201-206 | Solutions | `['1', '-1']` |
100+
| Variance functions | 208-210 | Statistics | Correct values |
101+
| Cube root `-1` | 228 | `(-1)^{1/3}` | `-1` |
102+
| Polynomial expand | 431-434 | `(2x^2+3x+1)(2x+1)` | `4x^3 + 8x^2 + 5x + 1` |
103+
| Floating point | 437-438 | `(0+1.1-1.1)(0+1/4-1/4)` | `0` |
104+
| Definite integral | 513 | `\int_0^1 x^2 dx` | `1/3` |
105+
| Bigint sqrt | 505-507 | Large number | `3513640562152.025...` |
106+
| Numerical integral | 44 | `\int_0^1 \sin(x) dx` `.N()` | `0.4598 ± 0.0001` |
107+
| Subscript fn call | 222-223 | `f_\text{a}(5)` with assign | `6` (evaluates correctly) |
108+
| Symbolic factorial | 342 | `(n-1)!` | `(n - 1)!` (stays symbolic) |
109+
110+
### Issues Remaining
111+
112+
| Test | Line | Expected | Actual | Notes |
113+
| -------------------- | ------- | --------------- | ------ | --------------------------------- |
114+
| `.replace()` bug | 61-72 | `2*x + b` | `2` | Single-char symbols auto-wildcard |
115+
116+
**Note:** `.replace({match: 'a', replace: 2})` on `a*x + b` returns `2` instead of `2*x + b`.
117+
The bug is in `parseRulePart` (rules.ts:350) which auto-converts all single-character symbols
118+
to wildcards. So `'a'` becomes `'_a'`, matching ANY expression rather than the literal symbol `a`.
119+
See TODO.md #23.
120+
121+
### Expected Behavior (Not Bugs)
122+
123+
| Test | Line | Behavior | Workaround |
124+
| ------------------------------- | ------- | --------------------------- | -------------------------------- |
125+
| `D(\sin(x), x)` via LaTeX | 52-56 | Parses `D` as user symbol | Use `ce.box(['D', ...])` instead |
126+
| Power `.value` | 229-230 | `undefined` before evaluate | Use `.evaluate().value` instead |
127+
128+
**Notes:**
129+
- In LaTeX, `D` is parsed as a predicate/symbol. For derivatives, use `ce.box(['D', expr, var])`
130+
or LaTeX notation like `\frac{d}{dx}`.
131+
- `.value` returns the numeric value only for already-evaluated expressions. For symbolic
132+
expressions like `Power(2, 3)`, call `.evaluate()` first.
133+
- For simple variable substitution, use `.subs()` not `.replace()`. The `.replace()` method
134+
is for pattern-based rule replacement.
135+
136+
---
137+
138+
## Solve
139+
140+
### Working Correctly
141+
142+
| Test | Line | Equation | Solutions |
143+
| -------------- | ------- | --------- | --------- |
144+
| `2x=\sqrt{5x}` | 156-158 | Quadratic | `5/4, 0` |
145+
| `x^2-1=0` | 201-206 | Quadratic | `1, -1` |
146+
| `5x=0` | 497-500 | Linear | `0` |
147+
| `x=\sqrt{5}` | 502-504 | Identity | `sqrt(5)` |
148+
149+
### Issues Remaining
150+
151+
| Test | Line | Expected | Actual | Notes |
152+
| ------------------------- | ---- | -------- | ------- | ---------------------------------------- |
153+
| `2x+1=0` isEqual `x=-1/2` || `true` | `false` | `isEqual` should recognize equivalent equations |
154+
155+
**Note:** `isEqual` is for mathematical equality (vs `isSame` for structural equality).
156+
For equations, `isEqual` should check if `(LHS1-RHS1)/(LHS2-RHS2)` simplifies to a
157+
non-zero constant, indicating the same solution set. See TODO.md #22.
158+
159+
---
160+
161+
## Matrix Operations
162+
163+
### Working Correctly
164+
165+
| Test | Line | Input | Result |
166+
| ----------------------- | ------- | ---------------------------------- | ------------------- |
167+
| `Shape(A)` | 532-534 | 2x2 numeric matrix | `(2, 2)` |
168+
| `Rank(A)` | 532-534 | 2x2 numeric matrix | `2` |
169+
| `Flatten(A)` | 532-534 | `[[1,2],[3,4]]` | `[1,2,3,4]` |
170+
| `Transpose(A)` | 532-534 | `[[1,2],[3,4]]` | `[[1,3],[2,4]]` |
171+
| `Determinant(A)` | 532-534 | Numeric matrix | `-2` |
172+
| `Determinant(X)` symbolic | 536-549 | `[[a,b],[c,d]]` | `-b*c + a*d` |
173+
174+
### Notes
175+
176+
All matrix operations now work correctly, including symbolic matrices assigned via `ce.assign()`.
177+
178+
---
179+
180+
## Pattern Matching
181+
182+
### Issues Remaining
183+
184+
| Test | Line | Expected | Actual | Notes |
185+
| --------------------- | ------- | ------------------ | ------ | --------------------------- |
186+
| Match with variation | 135-148 | Substitution found | `null` | Match `0` against `_a*x` with `a=0` variation |
187+
| Complex pattern match | 153-165 | Substitution | `null` | Match `2x-√5√x` against complex Add pattern |
188+
189+
**Note:** Pattern matching with `useVariations: true` has known limitations. The system
190+
doesn't fully handle all algebraic variations (like matching `0` as `0*x`).
191+
192+
---
193+
194+
## Formatting / Serialization
195+
196+
### Working Correctly
197+
198+
| Test | Line | Input | Result |
199+
| ------------------------- | ------- | -------------------------------- | ------------------------------ |
200+
| Scientific notation | 40-44 | `1000` | `1\cdot10^{3}` |
201+
| Fraction canonical | 298 | `\frac{2}{-3222233}+\frac{1}{3}` | Uses `Rational` not `Subtract` |
202+
| `1/(2\sqrt{3})` canonical | 617 | Rationalized | `\frac{\sqrt{3}}{6}` |
203+
204+
### Expected Behavior
205+
206+
| Test | Line | Behavior | Notes |
207+
| -------------------- | ---- | --------------- | --------------------------------------- |
208+
| `3\times3` canonical | 59 | Returns `3 * 3` | `.simplify()` returns `9`. Converting to `3^2` would be a separate optimization. |
209+
210+
---
211+
212+
## Other Features
213+
214+
### Working Correctly
215+
216+
| Test | Line | Feature | Result |
217+
| ------------- | ------- | ---------------------------- | --------------------------------- |
218+
| Filter | 573-577 | `Filter([1,2,3,4,5], IsOdd)` | `[1,3,5]` |
219+
| Expand | 467-475 | `4x(3x+2)-5(5x-4)` | `12x^2 - 17x + 20` |
220+
| Negate i | 405-406 | `-i` | `-i` |
221+
| Hold | 232-234 | `Add(1, Hold(2))` | `1 + Hold(2)` |
222+
223+
### Issues Remaining
224+
225+
| Test | Line | Expected | Actual | Notes |
226+
| ------------------------- | ------- | ---------------- | --------------------------------- | ----------------------------- |
227+
| `List(Filter).evaluate()` | 581-583 | Evaluated filter | Filter not evaluated inside List | Nested evaluation issue |
228+
229+
### Expected Behavior
230+
231+
| Test | Line | Behavior | Notes |
232+
| ----------------- | ------- | ------------------ | -------------------------------------------------- |
233+
| Sum with data | 175-186 | `[50, 130]` | Element-wise multiplication is correct; use explicit indexing for dot product |
234+
| `Floor(Cos(n))` | 271-273 | `floor(cos(n))` | `n` is unknown, so expression stays symbolic |
235+
236+
---
237+
238+
## Parsing Edge Cases (Error Handling)
239+
240+
These tests verify error handling for malformed input:
241+
242+
| Input | Line | Status |
243+
| ------------------------- | ---- | ------------------------------------ |
244+
| `x__+1` | 510 | Returns string `"x__"` + 1 (not At) |
245+
| `\operatorname{a?#_!}` | 160 | Error: invalid-symbol |
246+
| `(a, b; c, d, ;; n ,, m)` | 677 | Parses as nested Tuples with Nothing |
247+
| `\operatorname{$invalid}` | 680 | Error for invalid identifier |
248+
249+
---
250+
251+
## Notes
252+
253+
1. **Double/complex integrals**: The playground includes double integrals and
254+
complex integration tests that timeout during numerical evaluation.
255+
256+
2. **Assumptions**: Simplifications that depend on assumptions (like `x > 0`)
257+
now work correctly. Use `ce.assume(ce.parse('x > 0'))` before simplifying.
258+
259+
3. **Matrix operations**: Matrix-related functions (Shape, Rank, Flatten,
260+
Transpose, Determinant, Inverse, Trace) now work correctly for both numeric
261+
and symbolic matrices.
262+
263+
4. **Pattern matching**: The pattern matching system has issues with variations
264+
and complex patterns.
265+
266+
5. **D operator syntax**: The `D` operator must be used via `ce.box(['D', ...])`,
267+
not LaTeX parsing. In LaTeX, `D` is parsed as a user symbol.
268+
269+
6. **Trig periodicity**: Trigonometric functions now reduce arguments by their
270+
period (e.g., `cos(5π + k)` simplifies to `-cos(k)`).

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
"jest-silent-reporter": "^0.6.0",
8080
"open": "^10.1.2",
8181
"prettier": "^3.6.2",
82-
"prettier-2": "npm:prettier@^2",
82+
"prettier-2": "npm:prettier@^2.8.8",
8383
"serve-http": "^1.0.7",
8484
"ts-jest": "^29.4.0",
8585
"ts-node": "^10.9.2",

0 commit comments

Comments
 (0)