Skip to content

Commit 73b6870

Browse files
arnogclaude
andauthored
fix: forget() and scoped assumptions now properly clear values (#280)
* fix: implement value resolution from equality assumptions (#18) When ce.assume(['Equal', symbol, value]) is called, the symbol now correctly evaluates to the assumed value. Previously, the symbol would remain unevaluated even after the assumption was made. Changes: - Fixed assumeEquality to set the symbol value when it already has a definition (which happens when accessed via .unknowns) - Updated BoxedSymbol.N() to check the evaluation context value for non-constant symbols, enabling correct comparison evaluation Examples that now work: - ce.box('one').evaluate().json → 1 (was: "one") - ce.box(['Equal', 'one', 1]).evaluate() → True (was: unchanged) - ce.box(['Equal', 'one', 0]).evaluate() → False https://claude.ai/code/session_01Xx26gPU9ThyiypRqdLcmZQ * feat: implement inequality evaluation using assumptions (#19) When inequality assumptions like `ce.assume(['Greater', 'x', 4])` are made, comparisons can now use transitive reasoning to determine results. For example, `x > 4` implies `x > 0`, so `['Greater', 'x', 0]` evaluates to True instead of remaining symbolic. Implementation: - Added `getInequalityBoundsFromAssumptions()` to extract lower/upper bounds from normalized assumption forms like `Less(Add(Negate(x), k), 0)` - Modified `cmp()` in compare.ts to use these bounds when comparing symbols Examples that now work: - ce.box(['Greater', 'x', 0]).evaluate() → True (when x > 4 assumed) - ce.box(['Less', 'x', 0]).evaluate() → False - ce.box('x').isGreater(0) → true - ce.box('x').isPositive → true https://claude.ai/code/session_01Xx26gPU9ThyiypRqdLcmZQ * fix: forget() and scoped assumptions now properly clear values (#24, #25) Bug #24: forget() now clears assumed values from evaluation context - After ce.assume(['Equal', 'x', 5]) and ce.forget('x'), x now correctly evaluates to 'x' instead of 5 - Added cleanup loop in forget() to delete values from all context frames Bug #25: Scoped assumptions clean up on popScope() - Assumptions made inside pushScope()/popScope() now properly clean up - Added _setCurrentContextValue() method to store values in current context - Modified assumeEquality() to use scoped value storage - Values set by assumptions are automatically removed when scope exits Files modified: - src/compute-engine/index.ts - Added _setCurrentContextValue(), forget() cleanup - src/compute-engine/global-types.ts - Added method signature - src/compute-engine/assume.ts - Use scoped value storage - test/compute-engine/bug-fixes.test.ts - Tests for both bugs - CHANGELOG.md, requirements/TODO.md, requirements/DONE.md - Documentation https://claude.ai/code/session_01Xx26gPU9ThyiypRqdLcmZQ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent eb634db commit 73b6870

9 files changed

Lines changed: 558 additions & 59 deletions

File tree

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,49 @@
1818
This also fixes comparison evaluation: `Equal(symbol, assumed_value)` now
1919
correctly evaluates to `True` instead of staying symbolic.
2020

21+
- **Inequality Evaluation Using Assumptions**: When an inequality assumption
22+
is made (e.g., `ce.assume(['Greater', 'x', 4])`), inequality comparisons
23+
can now use transitive reasoning to determine results.
24+
25+
```javascript
26+
ce.assume(ce.box(['Greater', 'x', 4]));
27+
ce.box(['Greater', 'x', 0]).evaluate(); // → True (x > 4 > 0)
28+
ce.box(['Less', 'x', 0]).evaluate(); // → False
29+
ce.box('x').isGreater(0); // → true
30+
ce.box('x').isPositive; // → true
31+
```
32+
33+
This works by extracting lower/upper bounds from inequality assumptions
34+
and using them during comparison operations.
35+
2136
### Bug Fixes
2237

38+
- **forget() Now Clears Assumed Values**: Fixed an issue where `ce.forget()` did not
39+
clear values that were set by equality assumptions. After calling
40+
`ce.assume(['Equal', 'x', 5])` followed by `ce.forget('x')`, the symbol would
41+
incorrectly still evaluate to `5`. Now `forget()` properly clears values from
42+
all evaluation context frames.
43+
44+
```javascript
45+
ce.assume(ce.box(['Equal', 'x', 5]));
46+
ce.box('x').evaluate(); // → 5
47+
ce.forget('x');
48+
ce.box('x').evaluate(); // → 'x' (was: 5)
49+
```
50+
51+
- **Scoped Assumptions Now Clean Up on popScope()**: Fixed an issue where
52+
assumptions made inside a nested scope would persist after `popScope()` was
53+
called. Values set by assumptions are now properly scoped to where the
54+
assumption was made, and are automatically removed when the scope exits.
55+
56+
```javascript
57+
ce.pushScope();
58+
ce.assume(ce.box(['Equal', 'y', 10]));
59+
ce.box('y').evaluate(); // → 10
60+
ce.popScope();
61+
ce.box('y').evaluate(); // → 'y' (was: 10)
62+
```
63+
2364
- **Extraneous Root Filtering for Sqrt Equations**: Fixed an issue where solving
2465
square root equations could return extraneous roots. When solving equations
2566
like `√x = x - 2` or `√x - x + 2 = 0` using the quadratic substitution method

requirements/DONE.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,3 +1049,138 @@ ce.box('one').type.matches('integer') // → true
10491049
**Tests enabled:**
10501050
- `test/compute-engine/assumptions.test.ts` - Enabled "VALUE RESOLUTION FROM
10511051
EQUALITY ASSUMPTIONS" describe block (6 tests)
1052+
1053+
---
1054+
1055+
### 19. Inequality Evaluation Using Assumptions ✅
1056+
1057+
**IMPLEMENTED:** When inequality assumptions are made via `ce.assume(['Greater', symbol, value])`,
1058+
comparisons can now use transitive reasoning to determine results.
1059+
1060+
**Problem:** When `x > 4` was assumed, evaluating `['Greater', 'x', 0]` would return the expression
1061+
unchanged instead of `True` (since x > 4 implies x > 0).
1062+
1063+
**Solution:** Added a new function `getInequalityBoundsFromAssumptions` that extracts lower/upper
1064+
bounds for a symbol from inequality assumptions. The bounds are then used in the `cmp` function
1065+
to determine comparison results.
1066+
1067+
**Key insight:** Assumptions are normalized to forms like `Less(Add(Negate(x), k), 0)` (meaning
1068+
`k - x < 0`, i.e., `x > k`). The implementation parses these normalized forms to extract bounds.
1069+
1070+
**Examples that now work:**
1071+
```typescript
1072+
ce.assume(ce.box(['Greater', 'x', 4]));
1073+
ce.box(['Greater', 'x', 0]).evaluate(); // → True (x > 4 > 0)
1074+
ce.box(['Less', 'x', 0]).evaluate(); // → False
1075+
ce.box('x').isGreater(0); // → true
1076+
ce.box('x').isGreater(4); // → true (strict inequality)
1077+
ce.box('x').isGreater(5); // → undefined (can't determine)
1078+
ce.box('x').isPositive; // → true
1079+
1080+
ce.assume(ce.box(['Greater', 't', 0]));
1081+
ce.box(['Greater', 't', 0]).evaluate(); // → True
1082+
ce.box('t').isGreater(-1); // → true
1083+
```
1084+
1085+
**Files modified:**
1086+
- `src/compute-engine/assume.ts` - Added `getInequalityBoundsFromAssumptions()` function
1087+
- `src/compute-engine/boxed-expression/compare.ts` - Modified `cmp()` to use bounds from assumptions
1088+
1089+
**Tests enabled:**
1090+
- `test/compute-engine/assumptions.test.ts` - Enabled "INEQUALITY EVALUATION USING ASSUMPTIONS"
1091+
describe block (6 tests)
1092+
1093+
---
1094+
1095+
### 24. BUG FIX: forget() Now Clears Assumed Values ✅
1096+
1097+
**FIXED:** The `forget()` function now properly clears values from the evaluation
1098+
context when a symbol is forgotten.
1099+
1100+
**Problem:** When `ce.assume(['Equal', 'x', 5])` was called followed by `ce.forget('x')`,
1101+
the value `5` would persist in the evaluation context, causing `ce.box('x').evaluate()`
1102+
to still return `5` instead of the symbol `'x'`.
1103+
1104+
**Root cause:** When task #18 was implemented, values were stored in the evaluation
1105+
context via `ce._setSymbolValue()`. However, `forget()` only removed assumptions from
1106+
`ce.context.assumptions` - it didn't clear the value from the evaluation context.
1107+
1108+
**Solution:** Added code to `forget()` to iterate through all evaluation context frames
1109+
and delete the symbol's value:
1110+
1111+
```typescript
1112+
// In forget() function, after removing assumptions:
1113+
for (const ctx of this._evalContextStack) {
1114+
if (symbol in ctx.values) {
1115+
delete ctx.values[symbol];
1116+
}
1117+
}
1118+
```
1119+
1120+
**Examples that now work:**
1121+
```typescript
1122+
const ce = new ComputeEngine();
1123+
ce.assume(ce.box(['Equal', 'x', 5]));
1124+
ce.box('x').evaluate().json; // → 5
1125+
1126+
ce.forget('x');
1127+
ce.box('x').evaluate().json; // → 'x' (was: 5)
1128+
```
1129+
1130+
**Files modified:**
1131+
- `src/compute-engine/index.ts` - Added value cleanup in `forget()` function
1132+
1133+
**Tests added:**
1134+
- `test/compute-engine/bug-fixes.test.ts` - Test for forget() value clearing
1135+
1136+
---
1137+
1138+
### 25. BUG FIX: Scoped Assumptions Now Clean Up on popScope() ✅
1139+
1140+
**FIXED:** Assumptions made inside a scope via `pushScope()`/`popScope()` now properly
1141+
clean up when the scope is exited.
1142+
1143+
**Problem:** When assumptions were made inside a nested scope, the values set via
1144+
`ce._setSymbolValue()` would persist after `popScope()` was called, breaking scope
1145+
isolation.
1146+
1147+
**Root cause:** The `_setSymbolValue()` function stores values in the context where
1148+
the symbol was declared (which might be a parent scope), not necessarily the current
1149+
scope. When `popScope()` was called, only the current scope's context was removed,
1150+
but the value remained in the parent context.
1151+
1152+
**Solution:** Created a new internal method `_setCurrentContextValue()` that stores
1153+
values directly in the current context's values map. Modified `assumeEquality()` to
1154+
use this method instead of `_setSymbolValue()`, ensuring that assumption values are
1155+
scoped to where the assumption was made.
1156+
1157+
```typescript
1158+
// New method in ComputeEngine:
1159+
_setCurrentContextValue(id, value): void {
1160+
this._evalContextStack[this._evalContextStack.length - 1].values[id] = value;
1161+
}
1162+
1163+
// In assumeEquality(), changed from:
1164+
ce._setSymbolValue(lhs, val);
1165+
// to:
1166+
ce._setCurrentContextValue(lhs, val);
1167+
```
1168+
1169+
**Examples that now work:**
1170+
```typescript
1171+
const ce = new ComputeEngine();
1172+
ce.pushScope();
1173+
ce.assume(ce.box(['Equal', 'y', 10]));
1174+
ce.box('y').evaluate().json; // → 10
1175+
1176+
ce.popScope();
1177+
ce.box('y').evaluate().json; // → 'y' (was: 10)
1178+
```
1179+
1180+
**Files modified:**
1181+
- `src/compute-engine/index.ts` - Added `_setCurrentContextValue()` method
1182+
- `src/compute-engine/global-types.ts` - Added method signature
1183+
- `src/compute-engine/assume.ts` - Changed to use `_setCurrentContextValue()`
1184+
1185+
**Tests added:**
1186+
- `test/compute-engine/bug-fixes.test.ts` - Test for scoped assumption cleanup

requirements/TODO.md

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -772,54 +772,9 @@ See `requirements/DONE.md` for implementation details.
772772

773773
---
774774

775-
### 19. Inequality Evaluation Using Assumptions
775+
### ~~19. Inequality Evaluation Using Assumptions~~ ✅ COMPLETED
776776

777-
**Problem:** When `x > 4` is assumed, evaluating `['Greater', 'x', 0]` should
778-
return `True` (since x > 4 implies x > 0), but currently it returns the
779-
expression unchanged.
780-
781-
**Current behavior:**
782-
783-
```typescript
784-
ce.assume(ce.box(['Greater', 'x', 4]));
785-
ce.box(['Greater', 'x', 0]).evaluate().json // Returns: ["Less", 0, "x"] (unchanged)
786-
ce.box(['Less', 'x', 0]).evaluate().json // Returns: ["Less", "x", 0] (unchanged)
787-
```
788-
789-
**Expected behavior:**
790-
791-
```typescript
792-
ce.assume(ce.box(['Greater', 'x', 4]));
793-
ce.box(['Greater', 'x', 0]).evaluate().json // Should return: "True"
794-
ce.box(['Less', 'x', 0]).evaluate().json // Should return: "False"
795-
ce.box(['Greater', 'x', 10]).evaluate().json // Should return: undefined (can't determine)
796-
```
797-
798-
**Implementation notes:**
799-
800-
- Requires reasoning about transitive inequality relationships
801-
- `x > 4` implies `x > 0`, `x > 1`, `x > 2`, etc.
802-
- `x > 4` implies `x >= 0`, `x >= 4`
803-
- Combined with equality assumptions: `one = 1` should make `one > 0` evaluate
804-
to True
805-
- This is related to but distinct from sign-based simplification (which already
806-
works)
807-
808-
**Algorithm sketch:**
809-
810-
1. When evaluating `Greater(x, c)`, check if there's an assumption
811-
`Greater(x, k)` where `k >= c`
812-
2. When evaluating `Less(x, c)`, check if there's an assumption `Greater(x, k)`
813-
where `k >= c` (returns False)
814-
3. Handle all inequality operators: Greater, GreaterEqual, Less, LessEqual
815-
816-
**Files to investigate:**
817-
818-
- `src/compute-engine/library/relational-operator.ts` - Inequality evaluation
819-
- `src/compute-engine/assume.ts` - Assumption querying
820-
821-
**Tests:** `test/compute-engine/assumptions.test.ts` - "INEQUALITY EVALUATION
822-
USING ASSUMPTIONS"
777+
See `requirements/DONE.md` for implementation details.
823778

824779
---
825780

@@ -912,6 +867,18 @@ ASSUMPTIONS"
912867

913868
---
914869

870+
### ~~24. BUG: forget() Doesn't Clear Assumed Values~~ ✅ FIXED
871+
872+
See `requirements/DONE.md` for implementation details.
873+
874+
---
875+
876+
### ~~25. BUG: Scoped Assumptions Don't Clean Up on popScope()~~ ✅ FIXED
877+
878+
See `requirements/DONE.md` for implementation details.
879+
880+
---
881+
915882
## Future Improvements (Not Yet Detailed)
916883

917884
### Trigonometric Simplification

0 commit comments

Comments
 (0)