Skip to content

Commit 43abaa6

Browse files
committed
fix #182
1 parent 0771e42 commit 43abaa6

4 files changed

Lines changed: 180 additions & 49 deletions

File tree

CHANGELOG.md

Lines changed: 61 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434

3535
- **Absolute Value Power Simplification**: Fixed simplification of `|x^n|`
3636
expressions with even and rational exponents. Previously, expressions like
37-
`|x²|` and `|x^{2/3}|` were not simplified. Now they correctly simplify
38-
based on the parity of the exponent's numerator. Addresses #181.
37+
`|x²|` and `|x^{2/3}|` were not simplified. Now they correctly simplify based
38+
on the parity of the exponent's numerator. Addresses #181.
3939

4040
```javascript
4141
ce.parse('|x^2|').simplify().latex; // → "x^2" (even exponent)
@@ -65,7 +65,6 @@
6565

6666
- **Non-linear Polynomial Systems**: The `solve()` method now handles certain
6767
non-linear polynomial systems with 2 equations and 2 variables:
68-
6968
- **Product + sum pattern**: Systems like `xy = p, x + y = s` are solved by
7069
recognizing that x and y are roots of the quadratic `t² - st + p = 0`.
7170

@@ -123,13 +122,14 @@
123122
// → [{ x: 0, y: 0 }, { x: 5, y: 0 }, { x: 5, y: 5 }, { x: 0, y: 5 }]
124123
```
125124

126-
Vertices are returned in counterclockwise convex hull order. Returns `null` for
127-
infeasible systems or non-linear constraints.
125+
Vertices are returned in counterclockwise convex hull order. Returns `null`
126+
for infeasible systems or non-linear constraints.
128127

129128
- **Under-determined Systems (Parametric Solutions)**: The `solve()` method now
130129
returns parametric solutions for under-determined linear systems (fewer
131130
equations than variables) instead of returning `null`. Free variables appear
132-
as themselves in the solution, with other variables expressed in terms of them.
131+
as themselves in the solution, with other variables expressed in terms of
132+
them.
133133

134134
```javascript
135135
// Single equation with two variables
@@ -146,8 +146,8 @@
146146
Inconsistent systems still return `null`.
147147

148148
- **Extended Sqrt Equation Solving**: The equation solver now handles sqrt
149-
equations of the form `√(f(x)) = g(x)` by squaring both sides and solving
150-
the resulting polynomial. Extraneous roots are automatically filtered.
149+
equations of the form `√(f(x)) = g(x)` by squaring both sides and solving the
150+
resulting polynomial. Extraneous roots are automatically filtered.
151151

152152
```javascript
153153
ce.parse('\\sqrt{x+1} = x').solve('x'); // → [1.618...] (golden ratio)
@@ -156,9 +156,9 @@
156156
ce.parse('\\sqrt{x} = x').solve('x'); // → [0, 1]
157157
```
158158

159-
- **Two Sqrt Equation Solving**: The equation solver now handles equations
160-
with two sqrt terms of the form `√(f(x)) + √(g(x)) = e` using double squaring.
161-
Both addition and subtraction forms are supported, and extraneous roots are
159+
- **Two Sqrt Equation Solving**: The equation solver now handles equations with
160+
two sqrt terms of the form `√(f(x)) + √(g(x)) = e` using double squaring. Both
161+
addition and subtraction forms are supported, and extraneous roots are
162162
automatically filtered.
163163

164164
```javascript
@@ -168,10 +168,10 @@
168168
ce.parse('\\sqrt{2x+1} + \\sqrt{x-1} = 4').solve('x'); // → [46 - 8√29] ≈ 2.919
169169
```
170170

171-
- **Nested Sqrt Equation Solving**: The equation solver now handles nested
172-
sqrt equations of the form `√(x + √x) = a` using substitution. These patterns
173-
have √x inside the argument of an outer sqrt. The solver uses u = √x
174-
substitution, solves the resulting quadratic, and filters negative u values.
171+
- **Nested Sqrt Equation Solving**: The equation solver now handles nested sqrt
172+
equations of the form `√(x + √x) = a` using substitution. These patterns have
173+
√x inside the argument of an outer sqrt. The solver uses u = √x substitution,
174+
solves the resulting quadratic, and filters negative u values.
175175

176176
```javascript
177177
ce.parse('\\sqrt{x + 2\\sqrt{x}} = 3').solve('x'); // → [11 - 2√10] ≈ 4.675
@@ -180,16 +180,16 @@
180180
```
181181

182182
- **Quadratic Equations Without Constant Term**: Added support for solving
183-
quadratic equations of the form `ax² + bx = 0` (missing constant term).
184-
These are solved by factoring: `x(ax + b) = 0``x = 0` or `x = -b/a`.
183+
quadratic equations of the form `ax² + bx = 0` (missing constant term). These
184+
are solved by factoring: `x(ax + b) = 0``x = 0` or `x = -b/a`.
185185

186186
```javascript
187187
ce.parse('x^2 + 3x = 0').solve('x'); // → [0, -3]
188188
ce.parse('2x^2 - 4x = 0').solve('x'); // → [0, 2]
189189
```
190190

191-
- **Value Resolution from Equality Assumptions**: When an equality assumption
192-
is made via `ce.assume(['Equal', symbol, value])`, the symbol now correctly
191+
- **Value Resolution from Equality Assumptions**: When an equality assumption is
192+
made via `ce.assume(['Equal', symbol, value])`, the symbol now correctly
193193
evaluates to the assumed value. Previously, the symbol would remain unchanged
194194
even after the assumption.
195195

@@ -204,9 +204,9 @@
204204
This also fixes comparison evaluation: `Equal(symbol, assumed_value)` now
205205
correctly evaluates to `True` instead of staying symbolic.
206206

207-
- **Inequality Evaluation Using Assumptions**: When an inequality assumption
208-
is made (e.g., `ce.assume(['Greater', 'x', 4])`), inequality comparisons
209-
can now use transitive reasoning to determine results.
207+
- **Inequality Evaluation Using Assumptions**: When an inequality assumption is
208+
made (e.g., `ce.assume(['Greater', 'x', 4])`), inequality comparisons can now
209+
use transitive reasoning to determine results.
210210

211211
```javascript
212212
ce.assume(ce.box(['Greater', 'x', 4]));
@@ -216,8 +216,8 @@
216216
ce.box('x').isPositive; // → true
217217
```
218218

219-
This works by extracting lower/upper bounds from inequality assumptions
220-
and using them during comparison operations.
219+
This works by extracting lower/upper bounds from inequality assumptions and
220+
using them during comparison operations.
221221

222222
- **Type Inference from Assumptions**: When assumptions are made, symbol types
223223
are now correctly inferred. Inequality assumptions (`>`, `<`, `>=`, `<=`) set
@@ -234,8 +234,8 @@
234234

235235
- **Tautology and Contradiction Detection**: `ce.assume()` now returns
236236
`'tautology'` for redundant assumptions that are already implied by existing
237-
assumptions, and `'contradiction'` for assumptions that conflict with
238-
existing ones.
237+
assumptions, and `'contradiction'` for assumptions that conflict with existing
238+
ones.
239239

240240
```javascript
241241
ce.assume(ce.box(['Greater', 'x', 4]));
@@ -268,8 +268,8 @@
268268
// → 2x + b (was: 2 - incorrectly matched entire expression)
269269
```
270270

271-
- **forget() Now Clears Assumed Values**: Fixed an issue where `ce.forget()` did not
272-
clear values that were set by equality assumptions. After calling
271+
- **forget() Now Clears Assumed Values**: Fixed an issue where `ce.forget()` did
272+
not clear values that were set by equality assumptions. After calling
273273
`ce.assume(['Equal', 'x', 5])` followed by `ce.forget('x')`, the symbol would
274274
incorrectly still evaluate to `5`. Now `forget()` properly clears values from
275275
all evaluation context frames.
@@ -314,9 +314,9 @@
314314

315315
- **Pattern Matching with Repeated Wildcards**: Added comprehensive tests
316316
verifying that the pattern matching system correctly handles wildcards that
317-
appear multiple times in a pattern. When a named wildcard like `_x` appears
318-
in multiple positions, the matcher correctly ensures all occurrences match
319-
the same expression. This works with:
317+
appear multiple times in a pattern. When a named wildcard like `_x` appears in
318+
multiple positions, the matcher correctly ensures all occurrences match the
319+
same expression. This works with:
320320
- Nested function arguments (e.g., `['Multiply', '_x', ['Ln', '_x']]`)
321321
- Multiple nesting levels (3+ levels deep)
322322
- Commutative operators (handles reordering)
@@ -347,9 +347,10 @@
347347
```
348348

349349
Both simple subscripts (`F_5`) and complex subscripts (`F_{5}`) are supported.
350-
When the handler returns `undefined`, the expression stays symbolic. Subscripted
351-
expressions with `subscriptEvaluate` have type `number` and can be used in
352-
arithmetic operations: `ce.parse('F_{5} + F_{3}').evaluate()` works correctly.
350+
When the handler returns `undefined`, the expression stays symbolic.
351+
Subscripted expressions with `subscriptEvaluate` have type `number` and can be
352+
used in arithmetic operations: `ce.parse('F_{5} + F_{3}').evaluate()` works
353+
correctly.
353354

354355
- **Type-Aware Subscript Handling**: Subscripts on symbols declared as
355356
collection types (list, tuple, matrix, etc.) now automatically convert to
@@ -435,7 +436,8 @@
435436
- Domain constraints (min/max valid indices)
436437
- Symbolic subscripts stay symbolic (e.g., `F_k` remains unevaluated)
437438

438-
Alternatively, sequences can be defined using natural LaTeX assignment notation:
439+
Alternatively, sequences can be defined using natural LaTeX assignment
440+
notation:
439441

440442
```javascript
441443
// Arithmetic sequence via LaTeX
@@ -520,7 +522,8 @@
520522
```
521523

522524
- **OEIS Integration**: Look up sequences in the Online Encyclopedia of Integer
523-
Sequences (OEIS) and verify your sequences against known mathematical sequences:
525+
Sequences (OEIS) and verify your sequences against known mathematical
526+
sequences:
524527

525528
```javascript
526529
// Look up a sequence by its terms
@@ -539,8 +542,8 @@
539542

540543
Note: OEIS lookups require network access to oeis.org.
541544

542-
- **Multi-Index Sequences**: Define sequences with multiple indices like Pascal's
543-
triangle P_{n,k} or grid-based recurrences:
545+
- **Multi-Index Sequences**: Define sequences with multiple indices like
546+
Pascal's triangle P\_{n,k} or grid-based recurrences:
544547

545548
```javascript
546549
// Pascal's Triangle: P_{n,k} = P_{n-1,k-1} + P_{n-1,k}
@@ -558,7 +561,8 @@
558561

559562
Features:
560563
- Multiple index variables with `variables: ['n', 'k']`
561-
- Pattern-based base cases: `'n,0'` matches any (n, 0), `'n,n'` matches diagonal
564+
- Pattern-based base cases: `'n,0'` matches any (n, 0), `'n,n'` matches
565+
diagonal
562566
- Per-variable domain constraints
563567
- Constraint expressions (e.g., `'k <= n'`)
564568
- Composite key memoization (e.g., `'5,2'`)
@@ -572,8 +576,9 @@
572576

573577
#### Special Functions
574578

575-
- **Special Function Definitions**: Added type signatures for special mathematical
576-
functions, enabling them to be used in expressions without type errors:
579+
- **Special Function Definitions**: Added type signatures for special
580+
mathematical functions, enabling them to be used in expressions without type
581+
errors:
577582
- `Zeta` - Riemann zeta function ζ(s)
578583
- `Beta` - Euler beta function B(a,b) = Γ(a)Γ(b)/Γ(a+b)
579584
- `LambertW` - Lambert W function (product logarithm)
@@ -596,6 +601,7 @@
596601

597602
- **Bessel Function Derivatives**: Added derivative support for all four Bessel
598603
function types using order-dependent recurrence relations:
604+
599605
```javascript
600606
ce.box(['D', ['BesselJ', 'n', 'x'], 'x']).evaluate();
601607
// → 1/2 * BesselJ(n-1, x) - 1/2 * BesselJ(n+1, x)
@@ -606,15 +612,17 @@
606612
ce.box(['D', ['BesselK', 'n', 'x'], 'x']).evaluate();
607613
// → -1/2 * BesselK(n-1, x) - 1/2 * BesselK(n+1, x)
608614
```
615+
609616
Chain rule is automatically applied for composite arguments.
610617

611618
- **Multi-Argument Function Derivatives**: Added derivative support for:
612-
613619
- **Log(x, base)** - Logarithm with custom base:
620+
614621
```javascript
615622
ce.box(['D', ['Log', 'x', 2], 'x']).evaluate(); // → 1/(x·ln(2))
616623
ce.box(['D', ['Log', 'x', 'a'], 'x']).evaluate(); // → 1/(x·ln(a))
617624
```
625+
618626
Also handles cases where both x and base depend on the variable by applying
619627
the quotient rule to ln(x)/ln(base).
620628

@@ -626,18 +634,21 @@
626634
```
627635

628636
- **Integration of `1/(x·ln(x))` Pattern**: Added support for integrating
629-
expressions where the denominator is a product and one factor is the derivative
630-
of another:
637+
expressions where the denominator is a product and one factor is the
638+
derivative of another:
639+
631640
```javascript
632641
ce.parse('\\int \\frac{1}{x\\ln x} dx').evaluate(); // → ln(|ln(x)|)
633642
ce.parse('\\int \\frac{3}{x\\ln x} dx').evaluate(); // → 3·ln(|ln(x)|)
634643
```
644+
635645
This uses u-substitution: since `1/x = d/dx(ln(x))`, the integral becomes
636646
`∫ h'(x)/h(x) dx = ln|h(x)|`.
637647

638648
- **Cyclic Integration for e^x with Trigonometric Functions**: Added support for
639649
integrating products of exponentials and trigonometric functions that require
640650
the "solve for the integral" technique:
651+
641652
```javascript
642653
ce.parse('\\int e^x \\sin x dx').evaluate();
643654
// → -1/2·cos(x)·e^x + 1/2·sin(x)·e^x
@@ -652,6 +663,7 @@
652663
ce.parse('\\int e^x \\cos(2x) dx').evaluate();
653664
// → 1/5·cos(2x)·e^x + 2/5·sin(2x)·e^x
654665
```
666+
655667
These patterns cannot be solved by standard integration by parts (which would
656668
lead to infinite recursion) and instead use direct formulas:
657669
- `∫ e^x·sin(ax+b) dx = (e^x/(a²+1))·(sin(ax+b) - a·cos(ax+b))`
@@ -669,6 +681,7 @@
669681
- **Double negation**: `¬¬A → A`
670682

671683
These rules are applied automatically during simplification:
684+
672685
```javascript
673686
ce.box(['And', 'A', ['Or', 'A', 'B']]).simplify(); // → A
674687
ce.box(['Or', 'A', ['And', 'A', 'B']]).simplify(); // → A
@@ -703,8 +716,10 @@
703716
- **Matrix Decompositions**: Added four matrix decomposition functions for
704717
numerical linear algebra:
705718
- `LUDecomposition(A)``[P, L, U]` - LU factorization with partial pivoting
706-
- `QRDecomposition(A)``[Q, R]` - QR factorization using Householder reflections
707-
- `CholeskyDecomposition(A)``L` - Cholesky factorization for positive definite matrices
719+
- `QRDecomposition(A)``[Q, R]` - QR factorization using Householder
720+
reflections
721+
- `CholeskyDecomposition(A)``L` - Cholesky factorization for positive
722+
definite matrices
708723
- `SVD(A)``[U, Σ, V]` - Singular Value Decomposition
709724

710725
```javascript

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ export function canonicalPower(
168168
if (b.isPositive) {
169169
// (note: 0^∞ = 0, 1^∞ = NaN, covered prior)
170170

171+
// e^∞ = ∞ (handle explicitly before general case)
172+
if (a.symbol === 'ExponentialE') return ce.PositiveInfinity;
173+
171174
// (-1)^∞ = NaN
172175
// Because of oscillations in the limit.
173176
if (a.is(-1)) return ce.NaN;
@@ -190,6 +193,9 @@ export function canonicalPower(
190193

191194
// x^-oo
192195
if (b.isNegative) {
196+
// e^(-∞) = 0 (handle explicitly before general case)
197+
if (a.symbol === 'ExponentialE') return ce.Zero;
198+
193199
if (a.is(-1)) return ce.NaN;
194200
//Same result for all infinity types...
195201
if (a.isInfinity) return ce.Zero;
@@ -210,8 +216,41 @@ export function canonicalPower(
210216
return ce.NaN;
211217
}
212218

213-
//'AnyInfinity^{~oo}' (i.e. ComplexInfinity)
219+
//'AnyInfinity^b'
214220
if (a.isNumberLiteral && a.isInfinity) {
221+
// Special handling for NegativeInfinity with integer/rational exponents
222+
if (a.isNegative) {
223+
// (-inf)^n for integer n
224+
if (b.isInteger === true) {
225+
if (b.isEven === true) return ce.PositiveInfinity; // (-inf)^(even) -> +inf
226+
if (b.isOdd === true) return ce.NegativeInfinity; // (-inf)^(odd) -> -inf
227+
}
228+
229+
// (-inf)^(n/m) for rational n/m
230+
if (b.isRational === true) {
231+
const [numExpr, denomExpr] = b.numeratorDenominator;
232+
const num = numExpr.re;
233+
const denom = denomExpr.re;
234+
235+
if (
236+
typeof num === 'number' &&
237+
typeof denom === 'number' &&
238+
Number.isInteger(num) &&
239+
Number.isInteger(denom)
240+
) {
241+
const numIsEven = num % 2 === 0;
242+
const numIsOdd = num % 2 !== 0;
243+
const denomIsOdd = denom % 2 !== 0;
244+
245+
// n even, m odd -> +inf
246+
if (numIsEven && denomIsOdd) return ce.PositiveInfinity;
247+
248+
// n odd, m odd -> -inf (real interpretation)
249+
if (numIsOdd && denomIsOdd) return ce.NegativeInfinity;
250+
}
251+
}
252+
}
253+
215254
// If the exponent is pure imaginary, the result is NaN
216255
//(↓fix?:ensure both these cases narrow down to 'b' being a num./symbol literal)
217256
if (b.type.matches('imaginary')) return ce.NaN;

src/compute-engine/symbolic/simplify-hyperbolic.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,9 @@ export function simplifyHyperbolic(x: BoxedExpression): RuleStep | undefined {
207207
}
208208

209209
if (op === 'Arcsch') {
210-
// arcsch(±inf) -> NaN
210+
// arcsch(±inf) -> 0
211211
if (arg.isInfinity === true) {
212-
return { value: ce.NaN, because: 'arcsch(±inf) -> NaN' };
212+
return { value: ce.Zero, because: 'arcsch(±inf) -> 0' };
213213
}
214214
}
215215
}

0 commit comments

Comments
 (0)