Skip to content

Commit 43b31d8

Browse files
committed
feat: improve absolute value power simplification for even and odd exponents. fix #181
1 parent 03b4d59 commit 43b31d8

3 files changed

Lines changed: 302 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
### Improvements
44

5+
- **Absolute Value Power Simplification**: Fixed simplification of `|x^n|`
6+
expressions with even and rational exponents. Previously, expressions like
7+
`|x²|` and `|x^{2/3}|` were not simplified. Now they correctly simplify
8+
based on the parity of the exponent's numerator. Addresses #181.
9+
10+
```javascript
11+
ce.parse('|x^2|').simplify().latex; // → "x^2" (even exponent)
12+
ce.parse('|x^3|').simplify().latex; // → "|x|^3" (odd exponent)
13+
ce.parse('|x^{2/3}|').simplify().latex; // → "x^{2/3}" (even numerator)
14+
ce.parse('|x^{3/2}|').simplify().latex; // → "|x|^{3/2}" (odd numerator)
15+
```
16+
517
- **Systems of Linear Equations**: The `solve()` method now handles systems of
618
linear equations parsed from LaTeX `\begin{cases}...\end{cases}` environments.
719
Returns an object mapping variable names to their solutions.

requirements/TODO.md

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,259 @@ alternatives.
343343

344344
---
345345

346+
## Systems of Equations Enhancements
347+
348+
The following improvements build on the linear system solver implemented for
349+
issue #189.
350+
351+
### 26. Symbolic Coefficients in Linear Systems
352+
353+
**Problem:** The current linear system solver only works with numeric
354+
coefficients. Systems like `ax + by = c, dx + ey = f` where coefficients are
355+
symbols cannot be solved.
356+
357+
**Current behavior:**
358+
359+
```typescript
360+
const e = ce.parse('\\begin{cases}ax+by=c\\\\dx+ey=f\\end{cases}');
361+
e.solve(['x', 'y']); // → null (fails because a, b, c, d, e, f are symbolic)
362+
```
363+
364+
**Expected behavior:**
365+
366+
```typescript
367+
e.solve(['x', 'y']);
368+
// → { x: (ce - bf) / (ae - bd), y: (af - cd) / (ae - bd) }
369+
```
370+
371+
**Implementation approach:**
372+
373+
1. Extend `gaussianElimination()` to handle symbolic pivots
374+
2. Use symbolic division instead of numeric comparison for pivot selection
375+
3. The result will contain expressions in terms of the symbolic coefficients
376+
4. Handle the case where the determinant (ae - bd) might be zero symbolically
377+
378+
**Challenges:**
379+
380+
- Pivot selection can't use numeric comparison; may need to assume non-zero
381+
- Results may need simplification to be useful
382+
- Division by symbolic expressions requires care (domain restrictions)
383+
384+
**Files:**
385+
386+
- `src/compute-engine/boxed-expression/solve-linear-system.ts`
387+
388+
---
389+
390+
### 27. Under-determined Systems (Parametric Solutions)
391+
392+
**Problem:** Systems with fewer equations than unknowns currently return `null`.
393+
These systems have infinitely many solutions that can be expressed parametrically.
394+
395+
**Current behavior:**
396+
397+
```typescript
398+
const e = ce.parse('\\begin{cases}x+y=5\\end{cases}');
399+
e.solve(['x', 'y']); // → null
400+
```
401+
402+
**Expected behavior:**
403+
404+
```typescript
405+
e.solve(['x', 'y']);
406+
// → { x: t, y: 5 - t } where t is a free parameter
407+
// Or: { x: ['Subtract', 5, 'y'], y: 'y' } (express x in terms of y)
408+
```
409+
410+
**Design considerations:**
411+
412+
1. **Free parameter naming:** Use `t`, `s`, `u` or `_t1`, `_t2` for parameters
413+
2. **Multiple free variables:** For `x + y + z = 10` with 2 free parameters
414+
3. **Return format:** Could return:
415+
- Object with parameter expressions: `{ x: expr_in_t, y: expr_in_t, t: 't' }`
416+
- Separate parametric form: `{ solution: {...}, parameters: ['t'] }`
417+
4. **User preference:** May want to specify which variables are free
418+
419+
**Algorithm:**
420+
421+
1. After Gaussian elimination, identify free variables (columns without pivots)
422+
2. Express pivot variables in terms of free variables via back-substitution
423+
3. Return mapping with free variables as themselves or as parameters
424+
425+
**Files:**
426+
427+
- `src/compute-engine/boxed-expression/solve-linear-system.ts`
428+
429+
---
430+
431+
### 28. Non-linear Polynomial Systems
432+
433+
**Problem:** Simple polynomial systems like `xy = 6, x + y = 5` have exact
434+
solutions but currently return `null` because they're not linear.
435+
436+
**Current behavior:**
437+
438+
```typescript
439+
const e = ce.parse('\\begin{cases}xy=6\\\\x+y=5\\end{cases}');
440+
e.solve(['x', 'y']); // → null
441+
```
442+
443+
**Expected behavior:**
444+
445+
```typescript
446+
e.solve(['x', 'y']);
447+
// → [{ x: 2, y: 3 }, { x: 3, y: 2 }] // Two solutions
448+
```
449+
450+
**Solvable patterns:**
451+
452+
1. **Product + sum:** `xy = p, x + y = s` → solve `t² - st + p = 0`
453+
2. **Substitution-reducible:** One equation is linear in one variable
454+
- Solve linear equation for one variable
455+
- Substitute into other equation(s)
456+
- Solve resulting univariate equation
457+
3. **Symmetric systems:** Can use symmetric function substitution
458+
459+
**Algorithm for pattern 1 (product + sum):**
460+
461+
```
462+
Given: xy = p, x + y = s
463+
x and y are roots of: t² - st + p = 0
464+
Solve quadratic: t = (s ± √(s² - 4p)) / 2
465+
Return both (x,y) pairs
466+
```
467+
468+
**Algorithm for substitution:**
469+
470+
```
471+
1. Find an equation linear in some variable, e.g., x + 2y = 5 → x = 5 - 2y
472+
2. Substitute into remaining equations
473+
3. Solve the resulting system (may be univariate)
474+
4. Back-substitute to find other variables
475+
```
476+
477+
**Files:**
478+
479+
- `src/compute-engine/boxed-expression/solve-linear-system.ts` (or new file)
480+
481+
---
482+
483+
### 29. Diagnostic Error Returns
484+
485+
**Problem:** When `solve()` returns `null`, there's no indication of _why_ the
486+
system couldn't be solved.
487+
488+
**Current behavior:**
489+
490+
```typescript
491+
ce.parse('\\begin{cases}x+y=1\\\\x+y=2\\end{cases}').solve(['x', 'y']);
492+
// → null (but why? inconsistent? non-linear? under-determined?)
493+
```
494+
495+
**Expected behavior options:**
496+
497+
**Option A: Error expressions**
498+
499+
```typescript
500+
// Returns a BoxedExpression with error info
501+
// → ["Error", "inconsistent-system", { equations: [...] }]
502+
```
503+
504+
**Option B: Result object with status**
505+
506+
```typescript
507+
// Returns { status: 'inconsistent', reason: 'equations 1 and 2 are parallel' }
508+
// Or: { status: 'under-determined', freeVariables: ['y'] }
509+
// Or: { status: 'non-linear', nonLinearTerms: ['xy'] }
510+
```
511+
512+
**Option C: Separate diagnostic function**
513+
514+
```typescript
515+
ce.diagnoseSystem(equations, variables);
516+
// → { solvable: false, reason: 'inconsistent', details: {...} }
517+
```
518+
519+
**Diagnostic categories:**
520+
521+
- `'solved'` - Unique solution found
522+
- `'inconsistent'` - No solution exists (parallel lines, contradictory equations)
523+
- `'under-determined'` - Infinitely many solutions
524+
- `'over-determined'` - More equations than needed (check consistency)
525+
- `'non-linear'` - Contains non-linear terms
526+
- `'symbolic-coefficients'` - Contains symbolic coefficients (not yet supported)
527+
528+
**Files:**
529+
530+
- `src/compute-engine/boxed-expression/solve-linear-system.ts`
531+
532+
---
533+
534+
### 30. Linear Inequality Systems
535+
536+
**Problem:** No support for systems of linear inequalities, which define feasible
537+
regions rather than point solutions.
538+
539+
**Example:**
540+
541+
```typescript
542+
const e = ce.parse('\\begin{cases}x+y\\leq 10\\\\x\\geq 0\\\\y\\geq 0\\end{cases}');
543+
e.solve(['x', 'y']); // → currently null or error
544+
```
545+
546+
**Possible return formats:**
547+
548+
1. **Vertices of feasible region:** `[(0,0), (10,0), (0,10)]`
549+
2. **Parametric description:** `{ x: [0, 10], y: [0, 10-x] }`
550+
3. **Constraint set:** Keep as symbolic representation
551+
552+
**Use cases:**
553+
554+
- Linear programming feasibility
555+
- Constraint satisfaction
556+
- Geometric computations (polygon vertices)
557+
558+
**Algorithm (2D vertex enumeration):**
559+
560+
1. Convert inequalities to equalities (boundary lines)
561+
2. Find all pairwise intersections
562+
3. Filter to points satisfying all inequalities
563+
4. Order vertices (convex hull)
564+
565+
**Complexity:** This is significantly more complex than equality systems. May
566+
want to start with 2-variable case only.
567+
568+
**Files:**
569+
570+
- `src/compute-engine/boxed-expression/solve-linear-system.ts` (or new file)
571+
572+
---
573+
574+
### ~~31. Exact Rational Arithmetic Throughout~~ ✅ COMPLETED
575+
576+
The linear system solver now uses exact rational arithmetic throughout Gaussian
577+
elimination. Pivot selection uses symbolic comparison via `compareAbsoluteValues()`
578+
which compares using `abs()` and exact numeric values when possible, with
579+
fallback to numeric comparison. Zero checks use `isEffectivelyZero()` which
580+
tries symbolic `.is(0)` first. Systems with fractional coefficients now return
581+
exact rational results.
582+
583+
**Example:**
584+
585+
```typescript
586+
const e = ce.parse('\\begin{cases}x+y=1\\\\x-y=1/2\\end{cases}');
587+
const result = e.solve(['x', 'y']);
588+
console.log(result.x.json); // ["Rational", 3, 4]
589+
console.log(result.y.json); // ["Rational", 1, 4]
590+
```
591+
592+
**Files modified:**
593+
594+
- `src/compute-engine/boxed-expression/solve-linear-system.ts`
595+
- `test/compute-engine/solve.test.ts`
596+
597+
---
598+
346599
## Equation Solving Enhancements
347600

348601
### ~~14. Extraneous Root Filtering for Sqrt Equations~~ ✅ COMPLETED
@@ -558,11 +811,15 @@ See `requirements/DONE.md` for implementation details.
558811
- Simplification: `src/compute-engine/symbolic/simplify-rules.ts`
559812
- Pattern matching: `src/compute-engine/boxed-expression/match.ts`
560813
- Rule application: `src/compute-engine/boxed-expression/rules.ts`
814+
- Univariate solving: `src/compute-engine/boxed-expression/solve.ts`
815+
- Linear system solving: `src/compute-engine/boxed-expression/solve-linear-system.ts`
816+
- Polynomials: `src/compute-engine/boxed-expression/polynomials.ts`
561817
- Library definitions: `src/compute-engine/library/`
562818
- Logic library: `src/compute-engine/library/logic.ts`
563819
- Logic tests: `test/compute-engine/logic.test.ts`
564820
- Logic guide: `doc/16-guide-logic.md`
565821
- Logic reference: `doc/89-reference-logic.md`
822+
- Linear algebra guide: `doc/17-guide-linear-algebra.md`
566823

567824
### Testing commands:
568825

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

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,24 +157,46 @@ export function simplifyAbs(x: BoxedExpression): RuleStep | undefined {
157157
const exp = op.op2;
158158

159159
if (base && exp) {
160-
// |x^n| -> |x|^n when n is odd or irrational
161-
if (exp.isOdd === true || exp.isRational === false) {
160+
// |x^n| -> x^n when n is even integer (x^n is always non-negative)
161+
if (exp.isEven === true) {
162+
return {
163+
value: base.pow(exp),
164+
because: '|x^n| -> x^n when n is even',
165+
};
166+
}
167+
168+
// |x^n| -> |x|^n when n is odd integer
169+
if (exp.isOdd === true) {
170+
return {
171+
value: ce._fn('Abs', [base]).pow(exp),
172+
because: '|x^n| -> |x|^n when n is odd',
173+
};
174+
}
175+
176+
// |x^n| -> |x|^n when n is irrational
177+
if (exp.isRational === false) {
162178
return {
163179
value: ce._fn('Abs', [base]).pow(exp),
164-
because: '|x^n| -> |x|^n when n is odd/irrational',
180+
because: '|x^n| -> |x|^n when n is irrational',
165181
};
166182
}
167183

168-
// |x^(n/m)| patterns
169-
if (exp.operator === 'Divide') {
170-
const n = exp.op1;
171-
const m = exp.op2;
172-
if (n && m) {
173-
// |x^(n/m)| -> |x|^(n/m) when n is odd or m is integer
174-
if (n.isOdd === true || m.isInteger === true) {
184+
// Handle rational (non-integer) exponents via numerator/denominator
185+
// |x^(p/q)| -> x^(p/q) when p is even (x^p is non-negative)
186+
// |x^(p/q)| -> |x|^(p/q) when p is odd
187+
if (exp.isRational === true && exp.isInteger === false) {
188+
const num = exp.numerator;
189+
if (num) {
190+
if (num.isEven === true) {
191+
return {
192+
value: base.pow(exp),
193+
because: '|x^(p/q)| -> x^(p/q) when p is even',
194+
};
195+
}
196+
if (num.isOdd === true) {
175197
return {
176198
value: ce._fn('Abs', [base]).pow(exp),
177-
because: '|x^(n/m)| -> |x|^(n/m)',
199+
because: '|x^(p/q)| -> |x|^(p/q) when p is odd',
178200
};
179201
}
180202
}

0 commit comments

Comments
 (0)