Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,49 @@
This also fixes comparison evaluation: `Equal(symbol, assumed_value)` now
correctly evaluates to `True` instead of staying symbolic.

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

```javascript
ce.assume(ce.box(['Greater', 'x', 4]));
ce.box(['Greater', 'x', 0]).evaluate(); // → True (x > 4 > 0)
ce.box(['Less', 'x', 0]).evaluate(); // → False
ce.box('x').isGreater(0); // → true
ce.box('x').isPositive; // → true
```

This works by extracting lower/upper bounds from inequality assumptions
and using them during comparison operations.

### Bug Fixes

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

```javascript
ce.assume(ce.box(['Equal', 'x', 5]));
ce.box('x').evaluate(); // → 5
ce.forget('x');
ce.box('x').evaluate(); // → 'x' (was: 5)
```

- **Scoped Assumptions Now Clean Up on popScope()**: Fixed an issue where
assumptions made inside a nested scope would persist after `popScope()` was
called. Values set by assumptions are now properly scoped to where the
assumption was made, and are automatically removed when the scope exits.

```javascript
ce.pushScope();
ce.assume(ce.box(['Equal', 'y', 10]));
ce.box('y').evaluate(); // → 10
ce.popScope();
ce.box('y').evaluate(); // → 'y' (was: 10)
```

- **Extraneous Root Filtering for Sqrt Equations**: Fixed an issue where solving
square root equations could return extraneous roots. When solving equations
like `√x = x - 2` or `√x - x + 2 = 0` using the quadratic substitution method
Expand Down
135 changes: 135 additions & 0 deletions requirements/DONE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1049,3 +1049,138 @@ ce.box('one').type.matches('integer') // → true
**Tests enabled:**
- `test/compute-engine/assumptions.test.ts` - Enabled "VALUE RESOLUTION FROM
EQUALITY ASSUMPTIONS" describe block (6 tests)

---

### 19. Inequality Evaluation Using Assumptions ✅

**IMPLEMENTED:** When inequality assumptions are made via `ce.assume(['Greater', symbol, value])`,
comparisons can now use transitive reasoning to determine results.

**Problem:** When `x > 4` was assumed, evaluating `['Greater', 'x', 0]` would return the expression
unchanged instead of `True` (since x > 4 implies x > 0).

**Solution:** Added a new function `getInequalityBoundsFromAssumptions` that extracts lower/upper
bounds for a symbol from inequality assumptions. The bounds are then used in the `cmp` function
to determine comparison results.

**Key insight:** Assumptions are normalized to forms like `Less(Add(Negate(x), k), 0)` (meaning
`k - x < 0`, i.e., `x > k`). The implementation parses these normalized forms to extract bounds.

**Examples that now work:**
```typescript
ce.assume(ce.box(['Greater', 'x', 4]));
ce.box(['Greater', 'x', 0]).evaluate(); // → True (x > 4 > 0)
ce.box(['Less', 'x', 0]).evaluate(); // → False
ce.box('x').isGreater(0); // → true
ce.box('x').isGreater(4); // → true (strict inequality)
ce.box('x').isGreater(5); // → undefined (can't determine)
ce.box('x').isPositive; // → true

ce.assume(ce.box(['Greater', 't', 0]));
ce.box(['Greater', 't', 0]).evaluate(); // → True
ce.box('t').isGreater(-1); // → true
```

**Files modified:**
- `src/compute-engine/assume.ts` - Added `getInequalityBoundsFromAssumptions()` function
- `src/compute-engine/boxed-expression/compare.ts` - Modified `cmp()` to use bounds from assumptions

**Tests enabled:**
- `test/compute-engine/assumptions.test.ts` - Enabled "INEQUALITY EVALUATION USING ASSUMPTIONS"
describe block (6 tests)

---

### 24. BUG FIX: forget() Now Clears Assumed Values ✅

**FIXED:** The `forget()` function now properly clears values from the evaluation
context when a symbol is forgotten.

**Problem:** When `ce.assume(['Equal', 'x', 5])` was called followed by `ce.forget('x')`,
the value `5` would persist in the evaluation context, causing `ce.box('x').evaluate()`
to still return `5` instead of the symbol `'x'`.

**Root cause:** When task #18 was implemented, values were stored in the evaluation
context via `ce._setSymbolValue()`. However, `forget()` only removed assumptions from
`ce.context.assumptions` - it didn't clear the value from the evaluation context.

**Solution:** Added code to `forget()` to iterate through all evaluation context frames
and delete the symbol's value:

```typescript
// In forget() function, after removing assumptions:
for (const ctx of this._evalContextStack) {
if (symbol in ctx.values) {
delete ctx.values[symbol];
}
}
```

**Examples that now work:**
```typescript
const ce = new ComputeEngine();
ce.assume(ce.box(['Equal', 'x', 5]));
ce.box('x').evaluate().json; // → 5

ce.forget('x');
ce.box('x').evaluate().json; // → 'x' (was: 5)
```

**Files modified:**
- `src/compute-engine/index.ts` - Added value cleanup in `forget()` function

**Tests added:**
- `test/compute-engine/bug-fixes.test.ts` - Test for forget() value clearing

---

### 25. BUG FIX: Scoped Assumptions Now Clean Up on popScope() ✅

**FIXED:** Assumptions made inside a scope via `pushScope()`/`popScope()` now properly
clean up when the scope is exited.

**Problem:** When assumptions were made inside a nested scope, the values set via
`ce._setSymbolValue()` would persist after `popScope()` was called, breaking scope
isolation.

**Root cause:** The `_setSymbolValue()` function stores values in the context where
the symbol was declared (which might be a parent scope), not necessarily the current
scope. When `popScope()` was called, only the current scope's context was removed,
but the value remained in the parent context.

**Solution:** Created a new internal method `_setCurrentContextValue()` that stores
values directly in the current context's values map. Modified `assumeEquality()` to
use this method instead of `_setSymbolValue()`, ensuring that assumption values are
scoped to where the assumption was made.

```typescript
// New method in ComputeEngine:
_setCurrentContextValue(id, value): void {
this._evalContextStack[this._evalContextStack.length - 1].values[id] = value;
}

// In assumeEquality(), changed from:
ce._setSymbolValue(lhs, val);
// to:
ce._setCurrentContextValue(lhs, val);
```

**Examples that now work:**
```typescript
const ce = new ComputeEngine();
ce.pushScope();
ce.assume(ce.box(['Equal', 'y', 10]));
ce.box('y').evaluate().json; // → 10

ce.popScope();
ce.box('y').evaluate().json; // → 'y' (was: 10)
```

**Files modified:**
- `src/compute-engine/index.ts` - Added `_setCurrentContextValue()` method
- `src/compute-engine/global-types.ts` - Added method signature
- `src/compute-engine/assume.ts` - Changed to use `_setCurrentContextValue()`

**Tests added:**
- `test/compute-engine/bug-fixes.test.ts` - Test for scoped assumption cleanup
61 changes: 14 additions & 47 deletions requirements/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -772,54 +772,9 @@ See `requirements/DONE.md` for implementation details.

---

### 19. Inequality Evaluation Using Assumptions
### ~~19. Inequality Evaluation Using Assumptions~~ ✅ COMPLETED

**Problem:** When `x > 4` is assumed, evaluating `['Greater', 'x', 0]` should
return `True` (since x > 4 implies x > 0), but currently it returns the
expression unchanged.

**Current behavior:**

```typescript
ce.assume(ce.box(['Greater', 'x', 4]));
ce.box(['Greater', 'x', 0]).evaluate().json // Returns: ["Less", 0, "x"] (unchanged)
ce.box(['Less', 'x', 0]).evaluate().json // Returns: ["Less", "x", 0] (unchanged)
```

**Expected behavior:**

```typescript
ce.assume(ce.box(['Greater', 'x', 4]));
ce.box(['Greater', 'x', 0]).evaluate().json // Should return: "True"
ce.box(['Less', 'x', 0]).evaluate().json // Should return: "False"
ce.box(['Greater', 'x', 10]).evaluate().json // Should return: undefined (can't determine)
```

**Implementation notes:**

- Requires reasoning about transitive inequality relationships
- `x > 4` implies `x > 0`, `x > 1`, `x > 2`, etc.
- `x > 4` implies `x >= 0`, `x >= 4`
- Combined with equality assumptions: `one = 1` should make `one > 0` evaluate
to True
- This is related to but distinct from sign-based simplification (which already
works)

**Algorithm sketch:**

1. When evaluating `Greater(x, c)`, check if there's an assumption
`Greater(x, k)` where `k >= c`
2. When evaluating `Less(x, c)`, check if there's an assumption `Greater(x, k)`
where `k >= c` (returns False)
3. Handle all inequality operators: Greater, GreaterEqual, Less, LessEqual

**Files to investigate:**

- `src/compute-engine/library/relational-operator.ts` - Inequality evaluation
- `src/compute-engine/assume.ts` - Assumption querying

**Tests:** `test/compute-engine/assumptions.test.ts` - "INEQUALITY EVALUATION
USING ASSUMPTIONS"
See `requirements/DONE.md` for implementation details.

---

Expand Down Expand Up @@ -912,6 +867,18 @@ ASSUMPTIONS"

---

### ~~24. BUG: forget() Doesn't Clear Assumed Values~~ ✅ FIXED

See `requirements/DONE.md` for implementation details.

---

### ~~25. BUG: Scoped Assumptions Don't Clean Up on popScope()~~ ✅ FIXED

See `requirements/DONE.md` for implementation details.

---

## Future Improvements (Not Yet Detailed)

### Trigonometric Simplification
Expand Down
Loading
Loading