Skip to content

Commit 6114f8e

Browse files
committed
feat: implement type inference from assumptions (#21)
When assumptions are made, symbol types are now correctly inferred: - Inequality assumptions (>, <, >=, <=) set the symbol's type to 'real' - Equality assumptions infer the type from the value: - Equal to integer → type 'integer' - Equal to rational → type 'real' - Equal to real → type 'real' - Equal to complex → type 'number' Implementation: - Added inferTypeFromValue() function to promote specific types (e.g., finite_integer → integer) - Updated assumeEquality() to use inferTypeFromValue when updating types - Updated assumeInequality() to set type 'real' even for auto-declared symbols Examples: ce.assume(ce.box(['Greater', 'x', 4])); ce.box('x').type.toString(); // → 'real' (was: 'unknown') ce.assume(ce.box(['Equal', 'one', 1])); ce.box('one').type.toString(); // → 'integer' (was: 'unknown') Files modified: - src/compute-engine/assume.ts - Added inferTypeFromValue(), updated type inference - test/compute-engine/assumptions.test.ts - Enabled 6 tests - CHANGELOG.md, requirements/TODO.md, requirements/DONE.md - Documentation https://claude.ai/code/session_01Xx26gPU9ThyiypRqdLcmZQ
1 parent e9769a2 commit 6114f8e

5 files changed

Lines changed: 111 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@
3333
This works by extracting lower/upper bounds from inequality assumptions
3434
and using them during comparison operations.
3535

36+
- **Type Inference from Assumptions**: When assumptions are made, symbol types
37+
are now correctly inferred. Inequality assumptions (`>`, `<`, `>=`, `<=`) set
38+
the symbol's type to `real`, and equality assumptions infer the type from the
39+
value (e.g., equal to an integer means type `integer`).
40+
41+
```javascript
42+
ce.assume(ce.box(['Greater', 'x', 4]));
43+
ce.box('x').type.toString(); // → 'real' (was: 'unknown')
44+
45+
ce.assume(ce.box(['Equal', 'one', 1]));
46+
ce.box('one').type.toString(); // → 'integer' (was: 'unknown')
47+
```
48+
3649
### Bug Fixes
3750

3851
- **forget() Now Clears Assumed Values**: Fixed an issue where `ce.forget()` did not

requirements/DONE.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,3 +1184,52 @@ ce.box('y').evaluate().json; // → 'y' (was: 10)
11841184

11851185
**Tests added:**
11861186
- `test/compute-engine/bug-fixes.test.ts` - Test for scoped assumption cleanup
1187+
1188+
---
1189+
1190+
### 21. Type Inference from Assumptions ✅
1191+
1192+
**IMPLEMENTED:** When assumptions are made, symbol types are now correctly inferred
1193+
based on the assumption.
1194+
1195+
**Problem:** When `ce.assume(['Greater', 'x', 4])` was called, the symbol's type
1196+
remained 'unknown'. Similarly, when `ce.assume(['Equal', 'one', 1])` was called,
1197+
the symbol's type was 'finite_integer' instead of 'integer'.
1198+
1199+
**Solution:** Modified the assumption processing to update symbol types:
1200+
1201+
1. **For equality assumptions**: Added `inferTypeFromValue()` function that promotes
1202+
specific types to more general ones (e.g., `finite_integer``integer`,
1203+
`finite_real_number``real`). This is used when updating the type of a symbol
1204+
with an inferred type.
1205+
1206+
2. **For inequality assumptions**: Updated `assumeInequality()` to set the symbol's
1207+
type to 'real' even when the symbol was already auto-declared with an inferred type.
1208+
1209+
**Type promotion rules:**
1210+
- `finite_integer`, `integer``integer`
1211+
- `rational``real`
1212+
- `finite_real_number`, `real``real`
1213+
- `complex`, `imaginary``number`
1214+
1215+
**Examples that now work:**
1216+
```typescript
1217+
ce.assume(ce.box(['Greater', 'x', 4]));
1218+
ce.box('x').type.toString(); // → 'real' (was: 'unknown')
1219+
1220+
ce.assume(ce.box(['Equal', 'one', 1]));
1221+
ce.box('one').type.toString(); // → 'integer' (was: 'finite_integer' or 'unknown')
1222+
1223+
ce.assume(ce.box(['Greater', 't', 0]));
1224+
ce.box('t').type.toString(); // → 'real'
1225+
1226+
ce.assume(ce.box(['Equal', 'p', 11]));
1227+
ce.box('p').type.toString(); // → 'integer'
1228+
```
1229+
1230+
**Files modified:**
1231+
- `src/compute-engine/assume.ts` - Added `inferTypeFromValue()`, updated type inference
1232+
1233+
**Tests enabled:**
1234+
- `test/compute-engine/assumptions.test.ts` - Enabled "TYPE INFERENCE FROM ASSUMPTIONS"
1235+
describe block (6 tests)

requirements/TODO.md

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -824,46 +824,9 @@ CONTRADICTION DETECTION"
824824

825825
---
826826

827-
### 21. Type Inference from Assumptions
827+
### ~~21. Type Inference from Assumptions~~ ✅ COMPLETED
828828

829-
**Problem:** When assumptions are made, symbol types should be inferred. For
830-
example, `x > 4` implies `x` is real, and `one = 1` implies `one` is an integer.
831-
832-
**Current behavior:**
833-
834-
```typescript
835-
ce.assume(ce.box(['Greater', 'x', 4]));
836-
ce.box('x').type.toString() // Returns: "unknown"
837-
838-
ce.assume(ce.box(['Equal', 'one', 1]));
839-
ce.box('one').type.toString() // Returns: "unknown"
840-
```
841-
842-
**Expected behavior:**
843-
844-
```typescript
845-
ce.assume(ce.box(['Greater', 'x', 4]));
846-
ce.box('x').type.toString() // Should return: "real" (inequalities imply real numbers)
847-
848-
ce.assume(ce.box(['Equal', 'one', 1]));
849-
ce.box('one').type.toString() // Should return: "integer" (equal to integer literal)
850-
```
851-
852-
**Implementation notes:**
853-
854-
- Inequality assumptions (`>`, `<`, `>=`, `<=`) imply the symbol is `real`
855-
- Equality to an integer implies the symbol is `integer`
856-
- Equality to a rational implies the symbol is `rational`
857-
- This might be handled by updating the symbol's type when an assumption is made
858-
- Alternatively, the type getter could query assumptions
859-
860-
**Files to investigate:**
861-
862-
- `src/compute-engine/assume.ts` - Assumption processing
863-
- `src/compute-engine/boxed-expression/boxed-symbol.ts` - Symbol type resolution
864-
865-
**Tests:** `test/compute-engine/assumptions.test.ts` - "TYPE INFERENCE FROM
866-
ASSUMPTIONS"
829+
See `requirements/DONE.md` for implementation details.
867830

868831
---
869832

src/compute-engine/assume.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,34 @@ import {
1919
import { MathJsonSymbol } from '../math-json';
2020
import { isInequalityOperator } from './latex-syntax/utils';
2121

22+
/**
23+
* Infer a promoted type from a value expression.
24+
* This promotes specific types to more general ones suitable for symbols:
25+
* - finite_integer -> integer
26+
* - rational -> real
27+
* - finite_real_number -> real
28+
* - complex/imaginary -> number
29+
*/
30+
function inferTypeFromValue(ce: ComputeEngine, value: BoxedExpression): BoxedType {
31+
if (value.type.matches('integer')) {
32+
// finite_integer, integer, etc. -> integer
33+
return ce.type('integer');
34+
}
35+
if (value.type.matches('rational')) {
36+
// rational -> real
37+
return ce.type('real');
38+
}
39+
if (value.type.matches('real')) {
40+
// finite_real_number, real -> real
41+
return ce.type('real');
42+
}
43+
if (value.type.matches('complex')) {
44+
// complex, imaginary -> number
45+
return ce.type('number');
46+
}
47+
return value.type;
48+
}
49+
2250
/**
2351
* An assumption is a predicate that is added to the current context.
2452
*
@@ -239,8 +267,9 @@ function assumeEquality(proposition: BoxedExpression): AssumeResult {
239267
// Use _setCurrentContextValue so the value is scoped to the current context
240268
// and will be automatically removed when the scope is popped.
241269
ce._setCurrentContextValue(lhs, val);
242-
// If the type was inferred, update it based on the value
243-
if (def.value.inferredType) def.value.type = val.type;
270+
// If the type was inferred, update it based on the value.
271+
// Use inferTypeFromValue to promote specific types (e.g., finite_integer -> integer)
272+
if (def.value.inferredType) def.value.type = inferTypeFromValue(ce, val);
244273
return 'ok';
245274
}
246275

@@ -270,8 +299,9 @@ function assumeEquality(proposition: BoxedExpression): AssumeResult {
270299
// Use _setCurrentContextValue so the value is scoped to the current context
271300
// and will be automatically removed when the scope is popped.
272301
ce._setCurrentContextValue(lhs, val);
273-
// If the type was inferred, update it based on the value
274-
if (def.value.inferredType) def.value.type = val.type;
302+
// If the type was inferred, update it based on the value.
303+
// Use inferTypeFromValue to promote specific types (e.g., finite_integer -> integer)
304+
if (def.value.inferredType) def.value.type = inferTypeFromValue(ce, val);
275305
return 'ok';
276306
}
277307

@@ -364,10 +394,18 @@ function assumeInequality(proposition: BoxedExpression): AssumeResult {
364394
const unknowns = result.unknowns;
365395
if (unknowns.length === 0) return 'not-a-predicate';
366396

367-
// Case 3
397+
// Case 3: single unknown - ensure the symbol has type 'real'
398+
// (inequalities imply the symbol is a real number)
368399
if (unknowns.length === 1) {
369-
if (!ce.lookupDefinition(unknowns[0]))
370-
ce.declare(unknowns[0], { type: 'real' });
400+
const symbol = unknowns[0];
401+
const def = ce.lookupDefinition(symbol);
402+
if (!def) {
403+
// Symbol not defined yet - declare with type 'real'
404+
ce.declare(symbol, { type: 'real' });
405+
} else if (isValueDef(def) && def.value.inferredType) {
406+
// Symbol was auto-declared with inferred type - update to 'real'
407+
def.value.type = ce.type('real');
408+
}
371409
}
372410

373411
// Case 3, 4

test/compute-engine/assumptions.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@ describe.skip('TAUTOLOGY AND CONTRADICTION DETECTION', () => {
9595
});
9696
});
9797

98-
// TODO #21: Type Inference from Assumptions
98+
// #21: Type Inference from Assumptions - IMPLEMENTED
9999
// When assumptions are made, symbol types should be inferred
100-
describe.skip('TYPE INFERENCE FROM ASSUMPTIONS', () => {
100+
describe('TYPE INFERENCE FROM ASSUMPTIONS', () => {
101101
test(`x should have type real (x > 4 assumed)`, () => {
102102
expect(ce.box('x').type.toString()).toBe('real');
103103
});

0 commit comments

Comments
 (0)