@@ -259,9 +259,23 @@ ce.declare('fib', {
259259 - ` src/compute-engine/latex-syntax/parse-symbol.ts ` - check symbol type at parse time
260260 - ` src/compute-engine/library/core.ts ` - canonical handler for complex subscripts
261261
262- 3 . ** Phase 3** : Add evaluation support for subscripted symbols
263- - Allow defining subscript evaluation functions
264- - Support sequence definitions
262+ 3 . ** Phase 3** : Add evaluation support for subscripted symbols ✅ IMPLEMENTED
263+ - Added ` subscriptEvaluate ` handler to ` ValueDefinition `
264+ - Supports both simple (` F_5 ` ) and complex (` F_{5} ` ) subscript syntax
265+ - Handler receives evaluated subscript and returns result (or ` undefined ` for symbolic)
266+ - Subscripted expressions with ` subscriptEvaluate ` have type ` number ` for arithmetic
267+ - Changes in:
268+ - ` src/compute-engine/global-types.ts ` - added ` subscriptEvaluate ` to ValueDefinition
269+ - ` src/compute-engine/boxed-expression/boxed-value-definition.ts ` - store handler
270+ - ` src/compute-engine/library/core.ts ` - evaluate and type handlers for Subscript
271+ - ` src/compute-engine/latex-syntax/parse-symbol.ts ` - prevent compound symbol creation
272+ - ` src/compute-engine/latex-syntax/types.ts ` and ` parse.ts ` - parser callback
273+ - ` src/compute-engine/index.ts ` - hasSubscriptEvaluate option
274+
275+ 4 . ** Phase 4** : Declarative sequence definitions (SUB-4) 🔲 PLANNED
276+ - Allow defining sequences using LaTeX recurrence relations
277+ - Support base cases and recurrence rules
278+ - See [ Declarative Sequence Definitions] ( #declarative-sequence-definitions-sub-4 ) below
265279
266280### Open Questions
267281
@@ -327,3 +341,311 @@ disambiguate:
327341The immediate fix is to change the return type of ` Subscript ` from ` 'symbol' ` to
328342` 'unknown' ` for complex subscripts, allowing them to be used in arithmetic
329343contexts. The longer-term solution is full type-aware subscript interpretation.
344+
345+ ---
346+
347+ ## Declarative Sequence Definitions (SUB-4)
348+
349+ ### Motivation
350+
351+ While ` subscriptEvaluate ` allows defining sequences programmatically with
352+ JavaScript, mathematicians often define sequences using recurrence relations:
353+
354+ ``` latex
355+ a_n = a_{n-1} + 1, \quad a_0 = 1
356+ F_n = F_{n-1} + F_{n-2}, \quad F_0 = 0, F_1 = 1
357+ ```
358+
359+ A declarative API would make it easier to define sequences using familiar
360+ mathematical notation.
361+
362+ ### Proposed API
363+
364+ #### Option 1: Object-Based Declaration
365+
366+ ``` typescript
367+ ce .declareSequence (' a' , {
368+ base: { 0 : 1 }, // a_0 = 1
369+ recurrence: ' a_{n-1} + 1' , // a_n = a_{n-1} + 1
370+ });
371+
372+ ce .declareSequence (' F' , {
373+ base: { 0 : 0 , 1 : 1 }, // F_0 = 0, F_1 = 1
374+ recurrence: ' F_{n-1} + F_{n-2}' , // F_n = F_{n-1} + F_{n-2}
375+ });
376+
377+ // Usage
378+ ce .parse (' a_{10}' ).evaluate (); // → 11
379+ ce .parse (' F_{10}' ).evaluate (); // → 55
380+ ```
381+
382+ #### Option 2: LaTeX-Based Declaration
383+
384+ ``` typescript
385+ // Using assignment notation
386+ ce .parse (' a_0 := 1' ).evaluate ();
387+ ce .parse (' a_n := a_{n-1} + 1' ).evaluate ();
388+
389+ // Or using a sequence definition function
390+ ce .parse (' \\ operatorname{DefineSequence}(F, n, F_{n-1} + F_{n-2}, \\ {0: 0, 1: 1\\ })' ).evaluate ();
391+ ```
392+
393+ #### Option 3: Combined Approach (Recommended)
394+
395+ ``` typescript
396+ ce .declareSequence (' a' , {
397+ variable: ' n' , // Index variable (default: 'n')
398+ base: { 0 : 1 }, // Base cases as object
399+ recurrence: ce .parse (' a_{n-1} + 1' ), // Recurrence as expression
400+ // OR
401+ recurrence: ' a_{n-1} + 1' , // Recurrence as LaTeX string
402+ });
403+ ```
404+
405+ ### Implementation Plan
406+
407+ #### 1. Add ` declareSequence ` Method to ComputeEngine
408+
409+ ** File:** ` src/compute-engine/index.ts `
410+
411+ ``` typescript
412+ declareSequence (
413+ name : string ,
414+ options : {
415+ variable?: string ; // Index variable name, default 'n'
416+ base : Record < number , number | BoxedExpression>;
417+ recurrence: string | BoxedExpression ;
418+ memoize ?: boolean ; // Default true
419+ domain ?: { min?: number ; max ?: number }; // Valid index range
420+ }
421+ ): void
422+ ```
423+
424+ #### 2. Parse Recurrence Expression
425+
426+ The recurrence expression needs to:
427+ - Identify self-references (e.g., ` a_{n-1} ` , ` a_{n-2} ` )
428+ - Extract the offset from each self-reference
429+ - Handle multiple self-references (Fibonacci-style)
430+
431+ ``` typescript
432+ function parseRecurrence(
433+ ce : ComputeEngine ,
434+ name : string ,
435+ variable : string ,
436+ expr : BoxedExpression
437+ ): {
438+ offsets: number []; // e.g., [-1, -2] for Fibonacci
439+ evaluate: (n : number , memo : Map <number , number >) => number ;
440+ }
441+ ```
442+
443+ #### 3. Generate subscriptEvaluate Handler
444+
445+ Convert the parsed recurrence into a ` subscriptEvaluate ` handler:
446+
447+ ``` typescript
448+ function createSequenceHandler(
449+ ce : ComputeEngine ,
450+ base : Record <number , number >,
451+ recurrence : BoxedExpression ,
452+ variable : string ,
453+ memoize : boolean
454+ ): SubscriptEvaluateHandler {
455+ const memo = memoize ? new Map <number , BoxedExpression >() : null ;
456+
457+ return (subscript , { engine }) => {
458+ const n = subscript .re ;
459+ if (! Number .isInteger (n )) return undefined ;
460+
461+ // Check base cases
462+ if (n in base ) return engine .number (base [n ]);
463+
464+ // Check memo
465+ if (memo ?.has (n )) return memo .get (n );
466+
467+ // Evaluate recurrence by substituting n
468+ const result = evaluateRecurrence (engine , recurrence , variable , n , memo );
469+
470+ if (memo && result ) memo .set (n , result );
471+ return result ;
472+ };
473+ }
474+ ```
475+
476+ #### 4. Handle Self-References in Evaluation
477+
478+ When evaluating the recurrence, self-references like ` a_{n-1} ` need to
479+ recursively call the sequence:
480+
481+ ``` typescript
482+ function evaluateRecurrence(
483+ ce : ComputeEngine ,
484+ expr : BoxedExpression ,
485+ variable : string ,
486+ n : number ,
487+ memo : Map <number , BoxedExpression > | null
488+ ): BoxedExpression | undefined {
489+ // Substitute the variable with the current index
490+ const substituted = expr .subs ({ [variable ]: ce .number (n ) });
491+
492+ // The substituted expression may contain Subscript(name, n-1) etc.
493+ // These will evaluate via the subscriptEvaluate handler (recursive)
494+ return substituted .evaluate ();
495+ }
496+ ```
497+
498+ ### Edge Cases and Validation
499+
500+ #### 1. Detect Invalid Recurrences
501+
502+ - ** Missing base cases** : If recurrence references ` a_{n-1} ` but no base case
503+ for ` a_0 ` is provided
504+ - ** Circular references** : ` a_n = a_n + 1 ` (infinite loop)
505+ - ** Non-convergent** : Ensure recurrence terminates
506+
507+ ``` typescript
508+ function validateRecurrence(
509+ offsets : number [],
510+ base : Record <number , number >
511+ ): { valid: boolean ; error? : string } {
512+ // Check that base cases cover all needed starting points
513+ const minOffset = Math .min (... offsets );
514+ for (let i = 0 ; i > minOffset ; i -- ) {
515+ if (! (i in base )) {
516+ return { valid: false , error: ` Missing base case for index ${i } ` };
517+ }
518+ }
519+ return { valid: true };
520+ }
521+ ```
522+
523+ #### 2. Handle Non-Integer Subscripts
524+
525+ Return ` undefined ` for non-integer subscripts to keep expression symbolic.
526+
527+ #### 3. Handle Negative Indices
528+
529+ Option to support negative indices for bi-directional sequences:
530+
531+ ``` typescript
532+ ce .declareSequence (' a' , {
533+ base: { 0 : 1 },
534+ recurrence: ' a_{n-1} + 1' ,
535+ domain: { min: 0 }, // Only valid for n >= 0
536+ });
537+ ```
538+
539+ ### Multi-Index Sequences (Matrices, Tensors)
540+
541+ For sequences with multiple indices:
542+
543+ ``` typescript
544+ ce .declareSequence (' P' , {
545+ variables: [' n' , ' k' ], // Pascal's triangle
546+ base: {
547+ ' 0,0' : 1 ,
548+ ' n,0' : 1 , // P_{n,0} = 1 for all n
549+ ' n,n' : 1 , // P_{n,n} = 1 for all n
550+ },
551+ recurrence: ' P_{n-1,k-1} + P_{n-1,k}' ,
552+ });
553+
554+ ce .parse (' P_{5,2}' ).evaluate (); // → 10
555+ ```
556+
557+ This is more complex and may be Phase 5.
558+
559+ ### Closed-Form Detection (Future Enhancement)
560+
561+ For simple recurrences, detect and use closed-form solutions:
562+
563+ | Recurrence | Closed Form |
564+ | ------------| -------------|
565+ | ` a_n = a_{n-1} + d ` | ` a_n = a_0 + n*d ` (arithmetic) |
566+ | ` a_n = r * a_{n-1} ` | ` a_n = a_0 * r^n ` (geometric) |
567+ | ` a_n = a_{n-1} + a_{n-2} ` | Binet's formula (Fibonacci) |
568+
569+ This optimization would avoid recursion for large indices.
570+
571+ ### Test Cases
572+
573+ ``` typescript
574+ describe (' Declarative Sequence Definitions' , () => {
575+ test (' Arithmetic sequence' , () => {
576+ const ce = new ComputeEngine ();
577+ ce .declareSequence (' a' , {
578+ base: { 0 : 1 },
579+ recurrence: ' a_{n-1} + 2' ,
580+ });
581+ expect (ce .parse (' a_{5}' ).evaluate ().re ).toBe (11 ); // 1, 3, 5, 7, 9, 11
582+ });
583+
584+ test (' Fibonacci sequence' , () => {
585+ const ce = new ComputeEngine ();
586+ ce .declareSequence (' F' , {
587+ base: { 0 : 0 , 1 : 1 },
588+ recurrence: ' F_{n-1} + F_{n-2}' ,
589+ });
590+ expect (ce .parse (' F_{10}' ).evaluate ().re ).toBe (55 );
591+ });
592+
593+ test (' Factorial via recurrence' , () => {
594+ const ce = new ComputeEngine ();
595+ ce .declareSequence (' fact' , {
596+ base: { 0 : 1 },
597+ recurrence: ' n * fact_{n-1}' , // Note: uses n directly
598+ });
599+ expect (ce .parse (' fact_{5}' ).evaluate ().re ).toBe (120 );
600+ });
601+
602+ test (' Symbolic subscript stays symbolic' , () => {
603+ const ce = new ComputeEngine ();
604+ ce .declareSequence (' a' , {
605+ base: { 0 : 1 },
606+ recurrence: ' a_{n-1} + 1' ,
607+ });
608+ const result = ce .parse (' a_k' ).evaluate ();
609+ expect (result .operator ).toBe (' Subscript' );
610+ });
611+
612+ test (' Missing base case returns undefined' , () => {
613+ const ce = new ComputeEngine ();
614+ ce .declareSequence (' a' , {
615+ base: { 1 : 1 }, // Missing a_0
616+ recurrence: ' a_{n-1} + 1' ,
617+ });
618+ expect (ce .parse (' a_{0}' ).evaluate ().re ).toBe (NaN );
619+ });
620+
621+ test (' Arithmetic with sequence values' , () => {
622+ const ce = new ComputeEngine ();
623+ ce .declareSequence (' S' , {
624+ base: { 0 : 0 },
625+ recurrence: ' S_{n-1} + n' , // Triangular numbers
626+ });
627+ expect (ce .parse (' S_{5} + S_{3}' ).evaluate ().re ).toBe (21 ); // 15 + 6
628+ });
629+ });
630+ ```
631+
632+ ### Files to Modify
633+
634+ | File | Change |
635+ | ------| --------|
636+ | ` src/compute-engine/global-types.ts ` | Add ` SequenceDefinition ` type |
637+ | ` src/compute-engine/index.ts ` | Add ` declareSequence() ` method |
638+ | ` src/compute-engine/sequence.ts ` | New file for sequence parsing/evaluation |
639+ | ` test/compute-engine/sequences.test.ts ` | New test file |
640+ | ` doc/06-guide-augmenting.md ` | Document ` declareSequence() ` |
641+
642+ ### Summary
643+
644+ Phase 4 adds a declarative way to define sequences using recurrence relations.
645+ The implementation:
646+
647+ 1 . Parses the recurrence expression to identify self-references
648+ 2 . Validates that base cases cover all required starting points
649+ 3 . Generates a ` subscriptEvaluate ` handler with memoization
650+ 4 . Supports symbolic subscripts (returns undefined → stays symbolic)
651+ 5 . Integrates with arithmetic (type is ` number ` )
0 commit comments