@@ -262,11 +262,91 @@ export const SIMPLIFY_RULES: Rule[] = [
262262 } ;
263263 } ,
264264 //
265- // Product, Sum
265+ // Sum simplification
266266 //
267267 ( x ) : RuleStep | undefined => {
268- if ( x . operator === 'Max' ) {
268+ if ( x . operator !== 'Sum' ) return undefined ;
269+
270+ const body = x . op1 ;
271+ const limits = x . op2 ;
272+ if ( ! body || ! limits || limits . operator !== 'Limits' ) return undefined ;
273+
274+ const index = limits . op1 ?. symbol ;
275+ const lower = limits . op2 ;
276+ const upper = limits . op3 ;
277+ if ( ! index || ! lower || ! upper ) return undefined ;
278+
279+ const ce = x . engine ;
280+ const bodyUnknowns = new Set ( body . unknowns ) ;
281+
282+ // If body doesn't depend on index: Sum(c, [n, a, b]) → (b - a + 1) * c
283+ if ( ! bodyUnknowns . has ( index ) ) {
284+ const count = upper . sub ( lower ) . add ( ce . One ) . simplify ( ) ;
285+ return {
286+ value : count . mul ( body . simplify ( ) ) ,
287+ because : 'sum of constant' ,
288+ } ;
289+ }
290+
291+ // If body is just the index: Sum(n, [n, 1, b]) → b * (b + 1) / 2
292+ if ( body . symbol === index && lower . is ( 1 ) ) {
293+ // Triangular number formula
294+ const result = upper . mul ( upper . add ( ce . One ) ) . div ( 2 ) ;
295+ return { value : result . simplify ( ) , because : 'triangular number' } ;
296+ }
297+
298+ // If body is index squared: Sum(n^2, [n, 1, b]) → b(b+1)(2b+1)/6
299+ if (
300+ body . operator === 'Power' &&
301+ body . op1 ?. symbol === index &&
302+ body . op2 ?. is ( 2 ) &&
303+ lower . is ( 1 )
304+ ) {
305+ // Sum of squares formula: b(b+1)(2b+1)/6
306+ // Note: Don't simplify() here as the expanded form is more expensive
307+ const b = upper ;
308+ const result = b . mul ( b . add ( ce . One ) ) . mul ( b . mul ( 2 ) . add ( ce . One ) ) . div ( 6 ) ;
309+ return { value : result , because : 'sum of squares' } ;
310+ }
311+
312+ return undefined ;
313+ } ,
314+
315+ //
316+ // Product simplification
317+ //
318+ ( x ) : RuleStep | undefined => {
319+ if ( x . operator !== 'Product' ) return undefined ;
320+
321+ const body = x . op1 ;
322+ const limits = x . op2 ;
323+ if ( ! body || ! limits || limits . operator !== 'Limits' ) return undefined ;
324+
325+ const index = limits . op1 ?. symbol ;
326+ const lower = limits . op2 ;
327+ const upper = limits . op3 ;
328+ if ( ! index || ! lower || ! upper ) return undefined ;
329+
330+ const ce = x . engine ;
331+ const bodyUnknowns = new Set ( body . unknowns ) ;
332+
333+ // If body doesn't depend on index: Product(c, [n, a, b]) → c^(b - a + 1)
334+ if ( ! bodyUnknowns . has ( index ) ) {
335+ const count = upper . sub ( lower ) . add ( ce . One ) . simplify ( ) ;
336+ return {
337+ value : body . simplify ( ) . pow ( count ) ,
338+ because : 'product of constant' ,
339+ } ;
340+ }
341+
342+ // If body is just the index: Product(n, [n, 1, b]) → b!
343+ if ( body . symbol === index && lower . is ( 1 ) ) {
344+ return {
345+ value : ce . function ( 'Factorial' , [ upper ] ) ,
346+ because : 'factorial' ,
347+ } ;
269348 }
349+
270350 return undefined ;
271351 } ,
272352
0 commit comments