1use num_bigint::BigInt;
40use num_rational::Ratio;
41use num_traits::{One, Signed, ToPrimitive, Zero};
42
43use crate::base::arena::Arena;
44use crate::base::bernoulli::bernoulli;
45use crate::base::node::{ExprId, ExprNode};
46use crate::base::walk;
47use crate::calculus::gosper;
48use crate::poly::Poly;
49use crate::poly::polybridge;
50use crate::transforms::{apart, eval, subs};
51
52type Rat = Ratio<BigInt>;
53
54pub const MAX_ENUMERATION_TERMS: i64 = 1000;
57
58const MAX_TELESCOPE_SHIFT: i64 = 200;
60
61const MAX_GAMMA_SHIFT: i64 = 60;
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub(crate) enum SumOutcome {
72 Closed(ExprId),
75 Divergent(Option<ExprId>),
78 Unevaluated,
80}
81
82fn rat_i(n: i64) -> Rat {
87 Ratio::from_integer(BigInt::from(n))
88}
89
90fn rat_expr(arena: &mut Arena, r: Rat) -> ExprId {
91 let nid = arena.intern_num(r);
92 arena.intern(ExprNode::Num(nid))
93}
94
95fn depends_on(arena: &Arena, e: ExprId, var: ExprId) -> bool {
96 walk::contains(arena, e, var)
97}
98
99fn as_rat(arena: &Arena, e: ExprId) -> Option<Rat> {
100 arena.as_num(e).cloned()
101}
102
103fn as_i64(arena: &Arena, e: ExprId) -> Option<i64> {
104 arena.as_num(e).and_then(|r| {
105 if r.is_integer() {
106 r.numer().to_i64()
107 } else {
108 None
109 }
110 })
111}
112
113fn eval_rat(arena: &mut Arena, e: ExprId) -> Option<Rat> {
114 let v = eval::eval(arena, e);
115 as_rat(arena, v)
116}
117
118fn mul_all(arena: &mut Arena, factors: &[ExprId]) -> ExprId {
119 match factors.len() {
120 0 => arena.one,
121 1 => factors[0],
122 _ => arena.mul(factors),
123 }
124}
125
126fn add_all(arena: &mut Arena, terms: &[ExprId]) -> ExprId {
127 match terms.len() {
128 0 => arena.zero,
129 1 => terms[0],
130 _ => arena.add(terms),
131 }
132}
133
134fn add_terms(arena: &Arena, e: ExprId) -> Vec<ExprId> {
135 match arena.node(e) {
136 ExprNode::Add(ch) => ch.to_vec(),
137 _ => vec![e],
138 }
139}
140
141fn mul_factors(arena: &Arena, e: ExprId) -> Vec<ExprId> {
142 match arena.node(e) {
143 ExprNode::Mul(ch) => ch.to_vec(),
144 _ => vec![e],
145 }
146}
147
148fn pow_rat(arena: &mut Arena, base: ExprId, r: &Rat) -> ExprId {
150 if r.is_zero() {
151 return arena.one;
152 }
153 if r.is_one() {
154 return base;
155 }
156 let e = rat_expr(arena, r.clone());
157 arena.pow(base, e)
158}
159
160fn rat_pow_i(r: &Rat, n: i64) -> Rat {
162 let mut acc = Rat::one();
163 let base = if n < 0 { Rat::one() / r } else { r.clone() };
164 for _ in 0..n.unsigned_abs() {
165 acc *= &base;
166 }
167 acc
168}
169
170fn factorial_big(n: u64) -> BigInt {
171 let mut acc = BigInt::one();
172 for i in 2..=n {
173 acc *= BigInt::from(i);
174 }
175 acc
176}
177
178fn binomial_big(n: u64, k: u64) -> BigInt {
179 if k > n {
180 return BigInt::zero();
181 }
182 let mut acc = BigInt::one();
183 for i in 0..k {
184 acc = acc * BigInt::from(n - i) / BigInt::from(i + 1);
185 }
186 acc
187}
188
189fn combine_fractions(arena: &mut Arena, e: ExprId) -> ExprId {
191 if matches!(arena.node(e), ExprNode::Add(_)) {
192 let t = polybridge::together(arena, e);
193 eval::eval(arena, t)
194 } else {
195 e
196 }
197}
198
199fn frac_part(r: &Rat) -> Rat {
201 r - r.floor()
202}
203
204fn add_rat(arena: &mut Arena, x: ExprId, c: &Rat) -> ExprId {
206 if c.is_zero() {
207 return x;
208 }
209 let ce = rat_expr(arena, c.clone());
210 arena.add(&[x, ce])
211}
212
213fn const_value(arena: &mut Arena, e: ExprId) -> Option<Rat> {
215 eval_rat(arena, e)
216}
217
218fn range_count(arena: &mut Arena, lo: ExprId, hi: ExprId) -> ExprId {
220 let d = arena.sub(hi, lo);
221 let one = arena.one;
222 arena.add(&[d, one])
223}
224
225fn linear_in(arena: &Arena, e: ExprId, var: ExprId) -> Option<(Rat, Rat)> {
227 let p = polybridge::expr_to_poly(arena, e, var)?;
228 match p.degree() {
229 Some(1) => Some((p.coeff(1), p.coeff(0))),
230 _ => None,
231 }
232}
233
234fn is_zero_expr(arena: &mut Arena, e: ExprId) -> bool {
237 let ex = arena.expand_expr(e);
238 let ev = eval::eval(arena, ex);
239 if arena.is_zero_structural(ev) {
240 return true;
241 }
242 if let Some(r) = as_rat(arena, ev) {
243 return r.is_zero();
244 }
245 let simp = crate::simplify::simplify_engine::unified_simplify(
246 arena,
247 ev,
248 &crate::simplify::simplify_engine::SimplifyOpts::single_pass(),
249 );
250 arena.is_zero_structural(simp.expr)
251}
252
253fn const_sign(arena: &mut Arena, e: ExprId) -> Option<bool> {
256 if let Some(r) = const_value(arena, e) {
257 if r.is_positive() {
258 return Some(true);
259 }
260 if r.is_negative() {
261 return Some(false);
262 }
263 return None;
264 }
265 let f = crate::transforms::evalf::eval_const_f64(arena, e)?;
266 if f > 0.0 {
267 Some(true)
268 } else if f < 0.0 {
269 Some(false)
270 } else {
271 None
272 }
273}
274
275fn abs_less_than_one(arena: &mut Arena, e: ExprId) -> Option<bool> {
281 if let Some(r) = const_value(arena, e) {
282 return Some(r.abs() < Rat::one());
283 }
284 let f = crate::transforms::evalf::eval_const_f64(arena, e)?;
285 if !f.is_finite() {
286 return Some(false);
287 }
288 let a = f.abs();
289 if a < 1.0 - 1e-9 {
290 Some(true)
291 } else if a > 1.0 + 1e-9 {
292 Some(false)
293 } else {
294 None
295 }
296}
297
298fn infinity_of_sign(arena: &Arena, positive: Option<bool>) -> Option<ExprId> {
299 match positive {
300 Some(true) => Some(arena.infinity),
301 Some(false) => Some(arena.neg_infinity),
302 None => None,
303 }
304}
305
306enum Bound {
311 Finite(ExprId),
312 PosInf,
313 NegInf,
314}
315
316fn classify_bound(arena: &Arena, b: ExprId) -> Bound {
317 match arena.node(b) {
318 ExprNode::Infinity => Bound::PosInf,
319 ExprNode::NegInfinity => Bound::NegInf,
320 _ => Bound::Finite(b),
321 }
322}
323
324pub(crate) fn summation(
333 arena: &mut Arena,
334 body: ExprId,
335 var: ExprId,
336 lower: ExprId,
337 upper: ExprId,
338) -> SumOutcome {
339 if !matches!(arena.node(var), ExprNode::Symbol(_)) {
340 return SumOutcome::Unevaluated;
341 }
342 tracing::debug!("summation: dispatching");
343 match (classify_bound(arena, lower), classify_bound(arena, upper)) {
344 (Bound::Finite(lo), Bound::Finite(hi)) => finite_sum(arena, body, var, lo, hi),
345 (Bound::Finite(lo), Bound::PosInf) => infinite_sum(arena, body, var, lo),
346 (Bound::NegInf, Bound::Finite(hi)) => {
347 let neg_var = arena.neg(var);
349 let reflected = subs::subs(arena, body, var, neg_var);
350 let lo2 = arena.neg(hi);
351 let lo2 = eval::eval(arena, lo2);
352 infinite_sum(arena, reflected, var, lo2)
353 }
354 (Bound::NegInf, Bound::PosInf) => {
355 let zero = arena.zero;
356 let one = arena.one;
357 let right = infinite_sum(arena, body, var, zero);
358 let neg_var = arena.neg(var);
359 let reflected = subs::subs(arena, body, var, neg_var);
360 let left = infinite_sum(arena, reflected, var, one);
361 combine_outcomes(arena, right, left)
362 }
363 _ => SumOutcome::Unevaluated,
364 }
365}
366
367fn combine_outcomes(arena: &mut Arena, a: SumOutcome, b: SumOutcome) -> SumOutcome {
369 use SumOutcome::*;
370 match (a, b) {
371 (Closed(x), Closed(y)) => {
372 let s = arena.add(&[x, y]);
373 Closed(eval::eval(arena, s))
374 }
375 (Divergent(d), Closed(_)) | (Closed(_), Divergent(d)) => Divergent(d),
376 (Divergent(Some(x)), Divergent(Some(y))) if x == y => Divergent(Some(x)),
377 (Divergent(_), Divergent(_)) => Divergent(None),
378 _ => Unevaluated,
379 }
380}
381
382fn finite_sum(arena: &mut Arena, body: ExprId, var: ExprId, lo: ExprId, hi: ExprId) -> SumOutcome {
387 if let (Some(a), Some(b)) = (as_i64(arena, lo), as_i64(arena, hi)) {
388 if b < a {
389 return SumOutcome::Closed(arena.zero);
390 }
391 if b - a < MAX_ENUMERATION_TERMS {
392 tracing::debug!("summation: enumerating {} terms", b - a + 1);
393 return SumOutcome::Closed(enumerate_sum(arena, body, var, a, b));
394 }
395 }
396 match finite_closed(arena, body, var, lo, hi) {
397 Some(id) => SumOutcome::Closed(eval::eval(arena, id)),
398 None => SumOutcome::Unevaluated,
399 }
400}
401
402fn enumerate_sum(arena: &mut Arena, body: ExprId, var: ExprId, a: i64, b: i64) -> ExprId {
403 let mut terms = Vec::with_capacity((b - a + 1) as usize);
404 for k in a..=b {
405 let kk = arena.int(k);
406 let t = subs::subs(arena, body, var, kk);
407 terms.push(eval::eval(arena, t));
408 }
409 let s = add_all(arena, &terms);
410 eval::eval(arena, s)
411}
412
413fn enumerate_product(arena: &mut Arena, body: ExprId, var: ExprId, a: i64, b: i64) -> ExprId {
414 let mut factors = Vec::with_capacity((b - a + 1) as usize);
415 for k in a..=b {
416 let kk = arena.int(k);
417 let t = subs::subs(arena, body, var, kk);
418 factors.push(eval::eval(arena, t));
419 }
420 let p = mul_all(arena, &factors);
421 eval::eval(arena, p)
422}
423
424fn finite_closed(
426 arena: &mut Arena,
427 body: ExprId,
428 var: ExprId,
429 lo: ExprId,
430 hi: ExprId,
431) -> Option<ExprId> {
432 if !depends_on(arena, body, var) {
434 let n = range_count(arena, lo, hi);
435 return Some(arena.mul(&[body, n]));
436 }
437
438 let node = arena.node(body).clone();
439
440 if let ExprNode::Add(ref terms) = node {
442 let terms: Vec<ExprId> = terms.to_vec();
443 if let Some(r) = rational_sum_finite(arena, body, var, lo, hi) {
444 return Some(r);
445 }
446 if let Some(r) = telescoping_finite(arena, &terms, var, lo, hi) {
447 return Some(r);
448 }
449 if let Some(p) = sym_poly_in(arena, body, var) {
450 return Some(faulhaber_sym(arena, &p, lo, hi));
451 }
452 let mut done = Vec::new();
453 let mut failed = Vec::new();
454 for &t in &terms {
455 match finite_closed(arena, t, var, lo, hi) {
456 Some(v) => done.push(v),
457 None => failed.push(t),
458 }
459 }
460 if done.is_empty() {
461 return None;
462 }
463 if !failed.is_empty() {
464 let rest = add_all(arena, &failed);
465 done.push(arena.intern(ExprNode::Sum(rest, var, lo, hi)));
466 }
467 return Some(add_all(arena, &done));
468 }
469
470 if let ExprNode::Mul(ref factors) = node {
472 let factors: Vec<ExprId> = factors.to_vec();
473 let (consts, varf): (Vec<ExprId>, Vec<ExprId>) = factors
474 .iter()
475 .copied()
476 .partition(|&f| !depends_on(arena, f, var));
477 if !consts.is_empty() && !varf.is_empty() {
478 let inner = mul_all(arena, &varf);
479 let s = finite_closed(arena, inner, var, lo, hi)?;
480 let mut all = consts;
481 all.push(s);
482 return Some(arena.mul(&all));
483 }
484 }
485
486 if let Some(p) = sym_poly_in(arena, body, var) {
488 return Some(faulhaber_sym(arena, &p, lo, hi));
489 }
490
491 if let Some(r) = rational_sum_finite(arena, body, var, lo, hi) {
493 return Some(r);
494 }
495
496 if let Some(r) = binomial_sum(arena, body, var, lo, hi) {
498 return Some(r);
499 }
500
501 if let Some(r) = geometric_poly_finite(arena, body, var, lo, hi) {
503 return Some(r);
504 }
505
506 if let Some(r) = gosper::gosper_sum(arena, body, var, lo, hi) {
508 tracing::debug!("summation: Gosper succeeded");
509 return Some(r);
510 }
511
512 None
513}
514
515pub(crate) fn sym_poly_in(
522 arena: &mut Arena,
523 expr: ExprId,
524 var: ExprId,
525) -> Option<Vec<(usize, ExprId)>> {
526 let expanded = arena.expand_expr(expr);
527 let terms = add_terms(arena, expanded);
528 let mut acc: Vec<(usize, Vec<ExprId>)> = Vec::new();
529 for t in terms {
530 let (power, coeff) = monomial_in(arena, t, var)?;
531 if let Some(slot) = acc.iter_mut().find(|(p, _)| *p == power) {
532 slot.1.push(coeff);
533 } else {
534 acc.push((power, vec![coeff]));
535 }
536 }
537 acc.sort_by_key(|(p, _)| *p);
538 let mut out = Vec::with_capacity(acc.len());
539 for (p, coeffs) in acc {
540 let c = add_all(arena, &coeffs);
541 let c = eval::eval(arena, c);
542 if !arena.is_zero_structural(c) {
543 out.push((p, c));
544 }
545 }
546 Some(out)
547}
548
549fn monomial_in(arena: &mut Arena, t: ExprId, var: ExprId) -> Option<(usize, ExprId)> {
551 if !depends_on(arena, t, var) {
552 return Some((0, t));
553 }
554 if t == var {
555 return Some((1, arena.one));
556 }
557 match arena.node(t).clone() {
558 ExprNode::Pow(base, exp) if base == var => {
559 let n = as_i64(arena, exp)?;
560 if n < 0 {
561 return None;
562 }
563 Some((n as usize, arena.one))
564 }
565 ExprNode::Mul(ref factors) => {
566 let mut power = 0usize;
567 let mut consts = Vec::new();
568 let mut seen_var = false;
569 for &f in factors.iter() {
570 if !depends_on(arena, f, var) {
571 consts.push(f);
572 } else if f == var {
573 if seen_var {
574 return None;
575 }
576 seen_var = true;
577 power = 1;
578 } else if let ExprNode::Pow(base, exp) = arena.node(f).clone()
579 && base == var
580 {
581 let n = as_i64(arena, exp)?;
582 if n < 0 || seen_var {
583 return None;
584 }
585 seen_var = true;
586 power = n as usize;
587 } else {
588 return None;
589 }
590 }
591 let c = mul_all(arena, &consts);
592 Some((power, c))
593 }
594 _ => None,
595 }
596}
597
598pub(crate) fn faulhaber_coefficients(p: usize) -> Poly {
607 let mut coeffs = vec![Rat::zero(); p + 2];
608 let inv = Rat::one() / rat_i(p as i64 + 1);
609 for j in 0..=p {
610 let mut bj = bernoulli(j);
611 if j == 1 {
612 bj = -bj; }
614 if bj.is_zero() {
615 continue;
616 }
617 let c = Rat::from_integer(binomial_big((p + 1) as u64, j as u64));
618 let power = p + 1 - j;
619 coeffs[power] += &inv * c * bj;
620 }
621 Poly::from_coeffs(coeffs)
622}
623
624pub(crate) fn faulhaber_from_one(arena: &mut Arena, p: usize, n: ExprId) -> ExprId {
626 let poly = faulhaber_coefficients(p);
627 poly_at(arena, &poly, n)
628}
629
630fn poly_at(arena: &mut Arena, poly: &Poly, x: ExprId) -> ExprId {
632 let mut terms = Vec::new();
633 for (i, c) in poly.coeffs().iter().enumerate() {
634 if c.is_zero() {
635 continue;
636 }
637 let ce = rat_expr(arena, c.clone());
638 let t = if i == 0 {
639 ce
640 } else {
641 let xi = pow_rat(arena, x, &rat_i(i as i64));
642 arena.mul(&[ce, xi])
643 };
644 terms.push(t);
645 }
646 let s = add_all(arena, &terms);
647 eval::eval(arena, s)
648}
649
650fn faulhaber_poly(arena: &mut Arena, poly: &Poly, lo: ExprId, hi: ExprId) -> ExprId {
652 let sym: Vec<(usize, ExprId)> = poly
653 .coeffs()
654 .iter()
655 .enumerate()
656 .filter(|(_, c)| !c.is_zero())
657 .map(|(i, c)| (i, rat_expr(arena, c.clone())))
658 .collect();
659 faulhaber_sym(arena, &sym, lo, hi)
660}
661
662fn faulhaber_sym(arena: &mut Arena, poly: &[(usize, ExprId)], lo: ExprId, hi: ExprId) -> ExprId {
664 let lo_is_one = as_i64(arena, lo) == Some(1);
665 let one = arena.one;
666 let lo_m1 = arena.sub(lo, one);
667 let lo_m1 = eval::eval(arena, lo_m1);
668 let mut terms = Vec::new();
669 for &(p, c) in poly {
670 let s_hi = faulhaber_from_one(arena, p, hi);
671 let s = if lo_is_one {
672 s_hi
673 } else {
674 let s_lo = faulhaber_from_one(arena, p, lo_m1);
675 arena.sub(s_hi, s_lo)
676 };
677 terms.push(arena.mul(&[c, s]));
678 }
679 let total = add_all(arena, &terms);
680 let expanded = arena.expand_expr(total);
681 eval::eval(arena, expanded)
682}
683
684#[derive(Debug, Clone)]
690struct PoleTerm {
691 c: Rat,
692 beta: Rat,
693 m: u32,
694}
695
696fn rational_decompose(
701 arena: &mut Arena,
702 body: ExprId,
703 var: ExprId,
704) -> Option<(Poly, Vec<PoleTerm>)> {
705 let body = combine_fractions(arena, body);
706 let (n, d) = polybridge::as_numer_denom(arena, body);
707 let np = polybridge::expr_to_poly(arena, n, var)?;
708 let dp = polybridge::expr_to_poly(arena, d, var)?;
709 if dp.is_zero() || dp.is_constant() || np.is_zero() {
710 return None;
711 }
712 let decomposed = apart::apart(arena, body, var);
713 let terms = add_terms(arena, decomposed);
714 let mut poly_part = Poly::zero();
715 let mut poles = Vec::new();
716 for t in terms {
717 if let Some(p) = polybridge::expr_to_poly(arena, t, var) {
718 poly_part = &poly_part + &p;
719 continue;
720 }
721 poles.push(as_pole_term(arena, t, var)?);
722 }
723 Some((poly_part, poles))
724}
725
726fn as_pole_term(arena: &mut Arena, t: ExprId, var: ExprId) -> Option<PoleTerm> {
727 let (c, rest) = arena.as_coeff_term(t);
728 let ExprNode::Pow(base, exp) = arena.node(rest).clone() else {
729 return None;
730 };
731 let e = as_i64(arena, exp)?;
732 if e >= 0 {
733 return None;
734 }
735 let m = (-e) as u32;
736 let (alpha, beta0) = linear_in(arena, base, var)?;
737 let coeff = c * rat_pow_i(&alpha, -(m as i64));
739 Some(PoleTerm {
740 c: coeff,
741 beta: beta0 / alpha,
742 m,
743 })
744}
745
746fn group_poles(poles: &[PoleTerm]) -> Vec<Vec<PoleTerm>> {
748 let mut groups: Vec<Vec<PoleTerm>> = Vec::new();
749 for p in poles {
750 let fp = frac_part(&p.beta);
751 if let Some(g) = groups
752 .iter_mut()
753 .find(|g| g[0].m == p.m && frac_part(&g[0].beta) == fp)
754 {
755 g.push(p.clone());
756 } else {
757 groups.push(vec![p.clone()]);
758 }
759 }
760 for g in &mut groups {
761 g.sort_by(|a, b| a.beta.cmp(&b.beta));
762 }
763 groups
764}
765
766fn partial_sum_fn(arena: &mut Arena, x: ExprId, integer_offsets: bool) -> ExprId {
769 if integer_offsets {
770 arena.harmonic(x)
771 } else {
772 let one = arena.one;
773 let x1 = arena.add(&[x, one]);
774 arena.digamma(x1)
775 }
776}
777
778fn pole_group_partial(arena: &mut Arena, group: &[PoleTerm], n_expr: ExprId) -> Option<ExprId> {
787 let m = group[0].m;
788 let b0 = group[0].beta.clone();
789 let integer_offsets = b0.is_integer();
790 let mut terms = Vec::new();
791 let mut csum = Rat::zero();
792 let mut far = Vec::new();
793 for p in group {
794 let d = (&p.beta - &b0).to_integer().to_i64()?;
795 if d > MAX_TELESCOPE_SHIFT {
796 far.push(p);
797 continue;
798 }
799 csum += &p.c;
800 for j in 1..=d {
801 let shift = &b0 + rat_i(j);
802 let x = add_rat(arena, n_expr, &shift);
803 let inv = pow_rat(arena, x, &rat_i(-(m as i64)));
804 let ce = rat_expr(arena, p.c.clone());
805 terms.push(arena.mul(&[ce, inv]));
806 }
807 }
808 if !csum.is_zero() {
809 if m != 1 {
810 return None;
811 }
812 let x = add_rat(arena, n_expr, &b0);
813 let g = partial_sum_fn(arena, x, integer_offsets);
814 let ce = rat_expr(arena, csum);
815 terms.push(arena.mul(&[ce, g]));
816 }
817 for p in far {
818 if m != 1 {
819 return None;
820 }
821 let x = add_rat(arena, n_expr, &p.beta);
822 let g = partial_sum_fn(arena, x, integer_offsets);
823 let ce = rat_expr(arena, p.c.clone());
824 terms.push(arena.mul(&[ce, g]));
825 }
826 Some(add_all(arena, &terms))
827}
828
829fn rational_sum_finite(
830 arena: &mut Arena,
831 body: ExprId,
832 var: ExprId,
833 lo: ExprId,
834 hi: ExprId,
835) -> Option<ExprId> {
836 let (poly_part, poles) = rational_decompose(arena, body, var)?;
837 let mut parts = Vec::new();
838 if !poly_part.is_zero() {
839 parts.push(faulhaber_poly(arena, &poly_part, lo, hi));
840 }
841 let one = arena.one;
842 let lo_m1 = arena.sub(lo, one);
843 let lo_m1 = eval::eval(arena, lo_m1);
844 for group in group_poles(&poles) {
845 let t_hi = pole_group_partial(arena, &group, hi)?;
846 let t_lo = pole_group_partial(arena, &group, lo_m1)?;
847 parts.push(arena.sub(t_hi, t_lo));
848 }
849 let total = add_all(arena, &parts);
850 Some(eval::eval(arena, total))
851}
852
853fn rational_sum_infinite(
855 arena: &mut Arena,
856 body: ExprId,
857 var: ExprId,
858 lo: ExprId,
859) -> Option<SumOutcome> {
860 let (poly_part, poles) = rational_decompose(arena, body, var)?;
861 if !poly_part.is_zero() {
862 let lc = poly_part.leading_coeff().cloned().unwrap_or_else(Rat::zero);
863 return Some(SumOutcome::Divergent(infinity_of_sign(
864 arena,
865 Some(lc.is_positive()),
866 )));
867 }
868 let simple_total: Rat = poles.iter().filter(|p| p.m == 1).map(|p| p.c.clone()).sum();
870 if !simple_total.is_zero() {
871 return Some(SumOutcome::Divergent(infinity_of_sign(
872 arena,
873 Some(simple_total.is_positive()),
874 )));
875 }
876 let int_simple_total: Rat = poles
879 .iter()
880 .filter(|p| p.m == 1 && p.beta.is_integer())
881 .map(|p| p.c.clone())
882 .sum();
883 let use_harmonic = int_simple_total.is_zero();
884
885 let mut parts = Vec::new();
886 for group in group_poles(&poles) {
887 let m = group[0].m;
888 let b0 = group[0].beta.clone();
889 let csum: Rat = group.iter().map(|p| p.c.clone()).sum();
890 if m == 1 {
891 let x0 = add_rat(arena, lo, &b0);
896 let psi = |arena: &mut Arena, x: ExprId, integer: bool| -> ExprId {
897 if use_harmonic && integer {
898 let one = arena.one;
900 let xm1 = arena.sub(x, one);
901 let xm1 = eval::eval(arena, xm1);
902 arena.harmonic(xm1)
903 } else {
904 arena.digamma(x)
905 }
906 };
907 let mut near_sum = Rat::zero();
908 for p in &group {
909 let d = (&p.beta - &b0).to_integer().to_i64()?;
910 if d > MAX_TELESCOPE_SHIFT {
911 let x = add_rat(arena, lo, &p.beta);
912 let g = psi(arena, x, p.beta.is_integer());
913 let ce = rat_expr(arena, -p.c.clone());
914 parts.push(arena.mul(&[ce, g]));
915 continue;
916 }
917 near_sum += &p.c;
918 for j in 0..d {
919 let x = add_rat(arena, x0, &rat_i(j));
920 let inv = pow_rat(arena, x, &(-Rat::one()));
921 let ce = rat_expr(arena, -p.c.clone());
922 parts.push(arena.mul(&[ce, inv]));
923 }
924 }
925 if !near_sum.is_zero() {
926 let g = psi(arena, x0, b0.is_integer());
927 let ce = rat_expr(arena, -near_sum);
928 parts.push(arena.mul(&[ce, g]));
929 }
930 } else if csum.is_zero() {
931 for p in &group {
933 let d = (&p.beta - &b0).to_integer().to_i64()?;
934 if d > MAX_TELESCOPE_SHIFT {
935 return None;
936 }
937 for j in 0..d {
938 let shift = &b0 + rat_i(j);
939 let x = add_rat(arena, lo, &shift);
940 let inv = pow_rat(arena, x, &rat_i(-(m as i64)));
941 let ce = rat_expr(arena, -p.c.clone());
942 parts.push(arena.mul(&[ce, inv]));
943 }
944 }
945 } else {
946 for p in &group {
948 let x = add_rat(arena, lo, &p.beta);
949 let q = eval::eval(arena, x);
950 let qr = as_rat(arena, q)?;
951 let z = hurwitz_zeta_closed(arena, m as usize, &qr)?;
952 let ce = rat_expr(arena, p.c.clone());
953 parts.push(arena.mul(&[ce, z]));
954 }
955 }
956 }
957 let total = add_all(arena, &parts);
958 Some(SumOutcome::Closed(eval::eval(arena, total)))
959}
960
961pub(crate) fn zeta_even_rational(m: usize) -> Rat {
968 let two_m = 2 * m;
969 let b = bernoulli(two_m);
970 let sign = if m % 2 == 1 { Rat::one() } else { -Rat::one() };
971 let two_pow = rat_pow_i(&rat_i(2), two_m as i64);
972 let denom = Rat::from_integer(factorial_big(two_m as u64)) * rat_i(2);
973 sign * b * two_pow / denom
974}
975
976pub(crate) fn euler_number(n: usize) -> BigInt {
978 let mut e = vec![BigInt::one()];
980 for nn in 1..=n {
981 let mut acc = BigInt::zero();
982 for (k, ek) in e.iter().enumerate() {
983 acc += binomial_big(2 * nn as u64, 2 * k as u64) * ek;
984 }
985 e.push(-acc);
986 }
987 e[n].clone()
988}
989
990pub(crate) fn zeta_value(arena: &mut Arena, p: usize) -> Option<ExprId> {
997 if p < 2 {
998 return None;
999 }
1000 if p % 2 == 1 {
1001 let pe = arena.int(p as i64);
1002 return Some(arena.zeta(pe));
1003 }
1004 let r = zeta_even_rational(p / 2);
1005 let re = rat_expr(arena, r);
1006 let pi = arena.pi;
1007 let pip = pow_rat(arena, pi, &rat_i(p as i64));
1008 let v = arena.mul(&[re, pip]);
1009 Some(eval::eval(arena, v))
1010}
1011
1012fn eta_value(arena: &mut Arena, p: usize) -> Option<ExprId> {
1014 if p == 1 {
1015 let two = arena.int(2);
1016 return Some(arena.ln(two));
1017 }
1018 let z = zeta_value(arena, p)?;
1019 let factor = Rat::one() - rat_pow_i(&rat_i(2), 1 - p as i64);
1020 let fe = rat_expr(arena, factor);
1021 let v = arena.mul(&[fe, z]);
1022 Some(eval::eval(arena, v))
1023}
1024
1025fn dirichlet_beta_value(arena: &mut Arena, p: usize) -> Option<ExprId> {
1029 if p == 2 {
1030 return Some(arena.catalan);
1031 }
1032 if p.is_multiple_of(2) {
1033 return None;
1034 }
1035 let m = (p - 1) / 2;
1036 let e = euler_number(m);
1037 let sign = if m.is_multiple_of(2) {
1038 BigInt::one()
1039 } else {
1040 -BigInt::one()
1041 };
1042 let denom = rat_pow_i(&rat_i(4), m as i64 + 1) * Rat::from_integer(factorial_big(2 * m as u64));
1043 let r = Rat::from_integer(sign * e) / denom;
1044 let re = rat_expr(arena, r);
1045 let pi = arena.pi;
1046 let pip = pow_rat(arena, pi, &rat_i(p as i64));
1047 let v = arena.mul(&[re, pip]);
1048 Some(eval::eval(arena, v))
1049}
1050
1051fn hurwitz_zeta_closed(arena: &mut Arena, m: usize, q: &Rat) -> Option<ExprId> {
1055 if m < 2 || !q.is_positive() {
1056 return None;
1057 }
1058 if q.is_integer() {
1059 let qi = q.to_integer().to_i64()?;
1060 if qi > MAX_TELESCOPE_SHIFT {
1061 return None;
1062 }
1063 let z = zeta_value(arena, m)?;
1064 let mut acc = Rat::zero();
1065 for j in 1..qi {
1066 acc += rat_pow_i(&rat_i(j), -(m as i64));
1067 }
1068 let ce = rat_expr(arena, -acc);
1069 let v = arena.add(&[z, ce]);
1070 return Some(eval::eval(arena, v));
1071 }
1072 let two_q = q * rat_i(2);
1074 if two_q.is_integer() {
1075 let n = ((q - Rat::new(BigInt::one(), BigInt::from(2))).to_integer()).to_i64()?;
1076 if !(0..=MAX_TELESCOPE_SHIFT).contains(&n) {
1077 return None;
1078 }
1079 let z = zeta_value(arena, m)?;
1080 let factor = rat_pow_i(&rat_i(2), m as i64) - Rat::one();
1081 let mut acc = Rat::zero();
1082 for j in 0..n {
1083 let x = rat_i(j) + Rat::new(BigInt::one(), BigInt::from(2));
1084 acc += rat_pow_i(&x, -(m as i64));
1085 }
1086 let fe = rat_expr(arena, factor);
1087 let ce = rat_expr(arena, -acc);
1088 let fz = arena.mul(&[fe, z]);
1089 let v = arena.add(&[fz, ce]);
1090 return Some(eval::eval(arena, v));
1091 }
1092 None
1093}
1094
1095fn telescoping_form(arena: &mut Arena, terms: &[ExprId], var: ExprId) -> Option<(ExprId, i64)> {
1102 if terms.len() != 2 {
1103 return None;
1104 }
1105 let (a, b) = (terms[0], terms[1]);
1106 for d in 1..=3i64 {
1107 let de = arena.int(d);
1108 let shifted_var = arena.add(&[var, de]);
1109 let b_shift = subs::subs(arena, b, var, shifted_var);
1110 let test = arena.add(&[a, b_shift]);
1111 if is_zero_expr(arena, test) {
1112 return Some((b, d));
1113 }
1114 let a_shift = subs::subs(arena, a, var, shifted_var);
1115 let test = arena.add(&[b, a_shift]);
1116 if is_zero_expr(arena, test) {
1117 return Some((a, d));
1118 }
1119 }
1120 None
1121}
1122
1123fn telescoping_finite(
1124 arena: &mut Arena,
1125 terms: &[ExprId],
1126 var: ExprId,
1127 lo: ExprId,
1128 hi: ExprId,
1129) -> Option<ExprId> {
1130 let (g, d) = telescoping_form(arena, terms, var)?;
1131 let mut parts = Vec::new();
1133 for j in 0..d {
1134 let je = arena.int(j);
1135 let lo_j = arena.add(&[lo, je]);
1136 let hi_j = arena.int(j + 1);
1137 let hi_j = arena.add(&[hi, hi_j]);
1138 let g_lo = subs::subs(arena, g, var, lo_j);
1139 let g_hi = subs::subs(arena, g, var, hi_j);
1140 parts.push(arena.sub(g_lo, g_hi));
1141 }
1142 let total = add_all(arena, &parts);
1143 Some(eval::eval(arena, total))
1144}
1145
1146fn telescoping_infinite(
1149 arena: &mut Arena,
1150 terms: &[ExprId],
1151 var: ExprId,
1152 lo: ExprId,
1153) -> Option<SumOutcome> {
1154 let (g, d) = telescoping_form(arena, terms, var)?;
1155 let limit = limit_at_infinity(arena, g, var)?;
1156 let mut parts = Vec::new();
1157 for j in 0..d {
1158 let je = arena.int(j);
1159 let lo_j = arena.add(&[lo, je]);
1160 let g_lo = subs::subs(arena, g, var, lo_j);
1161 parts.push(arena.sub(g_lo, limit));
1162 }
1163 let total = add_all(arena, &parts);
1164 Some(SumOutcome::Closed(eval::eval(arena, total)))
1165}
1166
1167pub(crate) fn limit_at_infinity(arena: &mut Arena, f: ExprId, var: ExprId) -> Option<ExprId> {
1173 if !depends_on(arena, f, var) {
1174 return Some(f);
1175 }
1176 if let Some(l) = rational_limit_at_infinity(arena, f, var) {
1177 return l.map(|r| rat_expr(arena, r));
1178 }
1179 if let ExprNode::Add(ref terms) = arena.node(f).clone() {
1180 let terms: Vec<ExprId> = terms.to_vec();
1181 let mut limits = Vec::with_capacity(terms.len());
1182 for t in terms {
1183 limits.push(limit_at_infinity(arena, t, var)?);
1184 }
1185 let s = add_all(arena, &limits);
1186 return Some(eval::eval(arena, s));
1187 }
1188 if let ExprNode::Mul(ref factors) = arena.node(f).clone() {
1190 let factors: Vec<ExprId> = factors.to_vec();
1191 let (consts, varf): (Vec<ExprId>, Vec<ExprId>) = factors
1192 .iter()
1193 .copied()
1194 .partition(|&g| !depends_on(arena, g, var));
1195 if !consts.is_empty() && !varf.is_empty() {
1196 let inner = mul_all(arena, &varf);
1197 let l = limit_at_infinity(arena, inner, var)?;
1198 let mut all = consts;
1199 all.push(l);
1200 let p = arena.mul(&all);
1201 return Some(eval::eval(arena, p));
1202 }
1203 }
1204 let dominant = dominant_factor_form(arena, f, var);
1208 if crate::calculus::convergence::growth_exponents(arena, dominant, var)
1209 .and_then(|g| g.tends_to_zero())
1210 == Some(true)
1211 {
1212 return Some(arena.zero);
1213 }
1214 None
1215}
1216
1217fn dominant_factor_form(arena: &mut Arena, f: ExprId, var: ExprId) -> ExprId {
1220 let factors = mul_factors(arena, f);
1221 let mut out = Vec::with_capacity(factors.len());
1222 let mut changed = false;
1223 for g in factors {
1224 let replaced = match arena.node(g).clone() {
1225 ExprNode::Add(_) => polybridge::expr_to_poly(arena, g, var).and_then(|p| {
1226 let d = p.degree()?;
1227 let lc = p.leading_coeff()?.clone();
1228 let ce = rat_expr(arena, lc);
1229 let kd = pow_rat(arena, var, &rat_i(d as i64));
1230 Some(arena.mul(&[ce, kd]))
1231 }),
1232 ExprNode::Pow(base, exp)
1233 if matches!(arena.node(base), ExprNode::Add(_)) && !depends_on(arena, exp, var) =>
1234 {
1235 polybridge::expr_to_poly(arena, base, var).and_then(|p| {
1236 let d = p.degree()?;
1237 let lc = p.leading_coeff()?.clone();
1238 let ce = rat_expr(arena, lc);
1239 let kd = pow_rat(arena, var, &rat_i(d as i64));
1240 let mono = arena.mul(&[ce, kd]);
1241 Some(arena.pow(mono, exp))
1242 })
1243 }
1244 _ => None,
1245 };
1246 match replaced {
1247 Some(r) => {
1248 changed = true;
1249 out.push(r);
1250 }
1251 None => out.push(g),
1252 }
1253 }
1254 if changed {
1255 let p = mul_all(arena, &out);
1256 eval::eval(arena, p)
1257 } else {
1258 f
1259 }
1260}
1261
1262fn rational_limit_at_infinity(arena: &mut Arena, f: ExprId, var: ExprId) -> Option<Option<Rat>> {
1265 let (n, d) = polybridge::as_numer_denom(arena, f);
1266 let np = polybridge::expr_to_poly(arena, n, var)?;
1267 let dp = polybridge::expr_to_poly(arena, d, var)?;
1268 if dp.is_zero() {
1269 return None;
1270 }
1271 if np.is_zero() {
1272 return Some(Some(Rat::zero()));
1273 }
1274 let dn = np.degree()?;
1275 let dd = dp.degree()?;
1276 if dn < dd {
1277 Some(Some(Rat::zero()))
1278 } else if dn == dd {
1279 Some(Some(np.coeff(dn) / dp.coeff(dd)))
1280 } else {
1281 Some(None)
1282 }
1283}
1284
1285#[derive(Debug, Clone)]
1299pub(crate) struct TermShape {
1300 pub(crate) constant: ExprId,
1302 pub(crate) alternating: bool,
1304 pub(crate) numeric_base: Rat,
1306 pub(crate) bases: Vec<(ExprId, Rat)>,
1308 pub(crate) lin_pows: Vec<(Rat, Rat)>,
1310 pub(crate) facts: Vec<(Rat, Rat, i64)>,
1312 pub(crate) binomials: Vec<(ExprId, i64)>,
1314}
1315
1316impl TermShape {
1317 fn is_pure_lin_pows(&self) -> bool {
1318 self.bases.is_empty()
1319 && self.numeric_base.is_one()
1320 && self.facts.is_empty()
1321 && self.binomials.is_empty()
1322 }
1323
1324 pub(crate) fn geometric_base(&self, arena: &mut Arena) -> Option<ExprId> {
1327 if self.bases.is_empty() && self.numeric_base.is_one() && !self.alternating {
1328 return None;
1329 }
1330 let mut factors = Vec::new();
1331 let mut num = self.numeric_base.clone();
1332 if self.alternating {
1333 num = -num;
1334 }
1335 if !num.is_one() {
1336 factors.push(rat_expr(arena, num));
1337 }
1338 for (b, a) in &self.bases {
1339 factors.push(pow_rat(arena, *b, a));
1340 }
1341 let y = mul_all(arena, &factors);
1342 Some(eval::eval(arena, y))
1343 }
1344}
1345
1346pub(crate) fn term_shape(arena: &mut Arena, body: ExprId, var: ExprId) -> Option<TermShape> {
1348 let mut shape = TermShape {
1349 constant: arena.one,
1350 alternating: false,
1351 numeric_base: Rat::one(),
1352 bases: Vec::new(),
1353 lin_pows: Vec::new(),
1354 facts: Vec::new(),
1355 binomials: Vec::new(),
1356 };
1357 let mut consts: Vec<ExprId> = Vec::new();
1358 let factors = mul_factors(arena, body);
1359 for f in factors {
1360 shape_factor(arena, f, var, &Rat::one(), &mut shape, &mut consts)?;
1361 }
1362 shape.constant = mul_all(arena, &consts);
1363 shape.constant = eval::eval(arena, shape.constant);
1364 shape.lin_pows = merge_pairs(shape.lin_pows);
1366 shape.facts.sort_by_key(|a| (a.0.clone(), a.1.clone()));
1367 let mut merged_facts: Vec<(Rat, Rat, i64)> = Vec::new();
1368 for (a, b, e) in shape.facts {
1369 if let Some(last) = merged_facts.last_mut()
1370 && last.0 == a
1371 && last.1 == b
1372 {
1373 last.2 += e;
1374 } else {
1375 merged_facts.push((a, b, e));
1376 }
1377 }
1378 merged_facts.retain(|f| f.2 != 0);
1379 shape.facts = merged_facts;
1380 let mut merged_bases: Vec<(ExprId, Rat)> = Vec::new();
1381 for (b, a) in shape.bases {
1382 if let Some(slot) = merged_bases.iter_mut().find(|(bb, _)| *bb == b) {
1383 slot.1 += a;
1384 } else {
1385 merged_bases.push((b, a));
1386 }
1387 }
1388 merged_bases.retain(|(_, a)| !a.is_zero());
1389 merged_bases.sort_by_key(|(b, _)| b.0);
1390 shape.bases = merged_bases;
1391 let mut merged_bin: Vec<(ExprId, i64)> = Vec::new();
1392 for (n, e) in shape.binomials {
1393 if let Some(slot) = merged_bin.iter_mut().find(|(nn, _)| *nn == n) {
1394 slot.1 += e;
1395 } else {
1396 merged_bin.push((n, e));
1397 }
1398 }
1399 merged_bin.retain(|(_, e)| *e != 0);
1400 shape.binomials = merged_bin;
1401 Some(shape)
1402}
1403
1404fn merge_pairs(mut v: Vec<(Rat, Rat)>) -> Vec<(Rat, Rat)> {
1405 v.sort_by(|a, b| a.0.cmp(&b.0));
1406 let mut out: Vec<(Rat, Rat)> = Vec::new();
1407 for (b, p) in v {
1408 if let Some(last) = out.last_mut()
1409 && last.0 == b
1410 {
1411 last.1 += p;
1412 } else {
1413 out.push((b, p));
1414 }
1415 }
1416 out.retain(|(_, p)| !p.is_zero());
1417 out
1418}
1419
1420fn shape_factor(
1422 arena: &mut Arena,
1423 f: ExprId,
1424 var: ExprId,
1425 outer: &Rat,
1426 shape: &mut TermShape,
1427 consts: &mut Vec<ExprId>,
1428) -> Option<()> {
1429 if !depends_on(arena, f, var) {
1430 let c = pow_rat(arena, f, outer);
1431 consts.push(c);
1432 return Some(());
1433 }
1434 if f == var {
1435 shape.lin_pows.push((Rat::zero(), outer.clone()));
1436 return Some(());
1437 }
1438 match arena.node(f).clone() {
1439 ExprNode::Pow(base, exp) => {
1440 if !depends_on(arena, exp, var) {
1441 let p = as_rat(arena, exp)?;
1442 let total = outer * p;
1443 shape_factor(arena, base, var, &total, shape, consts)
1444 } else {
1445 if depends_on(arena, base, var) {
1446 return None;
1447 }
1448 let (a, b) = linear_in(arena, exp, var)?;
1449 let a = a * outer;
1450 let b = b * outer;
1451 shape_geometric(arena, base, &a, &b, shape, consts)
1452 }
1453 }
1454 ExprNode::Exp(arg) => {
1455 let (a, b) = linear_in(arena, arg, var)?;
1456 let e = arena.e_const;
1457 let a = a * outer;
1458 let b = b * outer;
1459 shape_geometric(arena, e, &a, &b, shape, consts)
1460 }
1461 ExprNode::Factorial(arg) => {
1462 let e = outer.to_integer();
1463 if !outer.is_integer() {
1464 return None;
1465 }
1466 let (alpha, beta) = linear_in(arena, arg, var)?;
1467 shape.facts.push((alpha, beta, e.to_i64()?));
1468 Some(())
1469 }
1470 ExprNode::Gamma(arg) => {
1471 if !outer.is_integer() {
1472 return None;
1473 }
1474 let (alpha, beta) = linear_in(arena, arg, var)?;
1475 shape
1476 .facts
1477 .push((alpha, beta - Rat::one(), outer.to_integer().to_i64()?));
1478 Some(())
1479 }
1480 ExprNode::Binomial(n, kk) => {
1481 if !outer.is_integer() {
1482 return None;
1483 }
1484 let e = outer.to_integer().to_i64()?;
1485 if kk == var && !depends_on(arena, n, var) {
1486 shape.binomials.push((n, e));
1487 return Some(());
1488 }
1489 if kk == var
1491 && let Some((a, b)) = linear_in(arena, n, var)
1492 {
1493 shape.facts.push((a, b, e));
1494 shape.facts.push((Rat::one(), Rat::zero(), -e));
1495 let nm = arena.sub(n, var);
1496 let (a2, b2) = linear_in(arena, nm, var)?;
1497 shape.facts.push((a2, b2, -e));
1498 return Some(());
1499 }
1500 None
1501 }
1502 ExprNode::Mul(ref inner) => {
1503 let inner: Vec<ExprId> = inner.to_vec();
1504 for g in inner {
1505 shape_factor(arena, g, var, outer, shape, consts)?;
1506 }
1507 Some(())
1508 }
1509 ExprNode::Add(_) => {
1510 let p = polybridge::expr_to_poly(arena, f, var)?;
1512 shape_polynomial(arena, &p, outer, shape, consts)
1513 }
1514 ExprNode::Neg(inner) => {
1515 let m1 = arena.neg_one;
1516 let c = pow_rat(arena, m1, outer);
1517 consts.push(c);
1518 shape_factor(arena, inner, var, outer, shape, consts)
1519 }
1520 _ => None,
1521 }
1522}
1523
1524fn shape_geometric(
1526 arena: &mut Arena,
1527 base: ExprId,
1528 a: &Rat,
1529 b: &Rat,
1530 shape: &mut TermShape,
1531 consts: &mut Vec<ExprId>,
1532) -> Option<()> {
1533 if a.is_zero() {
1534 let c = pow_rat(arena, base, b);
1535 consts.push(c);
1536 return Some(());
1537 }
1538 if let Some(r) = as_rat(arena, base) {
1539 if r.is_zero() {
1540 return None;
1541 }
1542 if a.is_integer() {
1543 let ai = a.to_integer().to_i64()?;
1544 if r == -Rat::one() {
1545 if ai.rem_euclid(2) == 1 {
1546 shape.alternating = !shape.alternating;
1547 }
1548 } else {
1549 shape.numeric_base *= rat_pow_i(&r, ai);
1550 }
1551 if !b.is_zero() {
1552 let c = pow_rat(arena, base, b);
1553 consts.push(c);
1554 }
1555 return Some(());
1556 }
1557 shape.bases.push((base, a.clone()));
1559 if !b.is_zero() {
1560 let c = pow_rat(arena, base, b);
1561 consts.push(c);
1562 }
1563 return Some(());
1564 }
1565 shape.bases.push((base, a.clone()));
1566 if !b.is_zero() {
1567 let c = pow_rat(arena, base, b);
1568 consts.push(c);
1569 }
1570 Some(())
1571}
1572
1573fn shape_polynomial(
1576 arena: &mut Arena,
1577 p: &Poly,
1578 outer: &Rat,
1579 shape: &mut TermShape,
1580 consts: &mut Vec<ExprId>,
1581) -> Option<()> {
1582 let (content, factors) = p.factor_over_z();
1583 if content.is_zero() {
1584 return None;
1585 }
1586 if !content.is_one() {
1587 let c = rat_expr(arena, content);
1588 let c = pow_rat(arena, c, outer);
1589 consts.push(c);
1590 }
1591 for (fac, mult) in factors {
1592 if fac.degree() != Some(1) {
1593 return None;
1594 }
1595 let alpha = fac.coeff(1);
1596 let beta = fac.coeff(0);
1597 let e = outer * rat_i(mult as i64);
1598 if !alpha.is_one() {
1599 if alpha.is_negative() && !e.is_integer() {
1600 return None;
1601 }
1602 let ae = rat_expr(arena, alpha.clone());
1603 let c = pow_rat(arena, ae, &e);
1604 consts.push(c);
1605 }
1606 shape.lin_pows.push((beta / alpha, e));
1607 }
1608 Some(())
1609}
1610
1611fn stirling_second_row(m: usize) -> Vec<Rat> {
1617 let mut row = vec![Rat::one()];
1619 for _ in 0..m {
1620 let mut next = vec![Rat::zero(); row.len() + 1];
1621 for (j, s) in row.iter().enumerate() {
1622 next[j] += rat_i(j as i64) * s;
1623 next[j + 1] += s;
1624 }
1625 row = next;
1626 }
1627 row
1628}
1629
1630fn binomial_poly_sum(
1635 arena: &mut Arena,
1636 body: ExprId,
1637 var: ExprId,
1638 lo: ExprId,
1639 hi: ExprId,
1640) -> Option<ExprId> {
1641 if as_i64(arena, lo) != Some(0) {
1642 return None;
1643 }
1644 let mut consts = Vec::new();
1645 let mut poly_factors = Vec::new();
1646 let mut binomial_n: Option<ExprId> = None;
1647 let mut x_parts = Vec::new();
1648 for f in mul_factors(arena, body) {
1649 if !depends_on(arena, f, var) {
1650 consts.push(f);
1651 continue;
1652 }
1653 match arena.node(f).clone() {
1654 ExprNode::Binomial(n, kk) if kk == var && !depends_on(arena, n, var) => {
1655 if binomial_n.is_some() {
1656 return None;
1657 }
1658 binomial_n = Some(n);
1659 }
1660 ExprNode::Pow(base, exp)
1661 if !depends_on(arena, base, var) && depends_on(arena, exp, var) =>
1662 {
1663 let (a, b) = linear_in(arena, exp, var)?;
1664 x_parts.push(pow_rat(arena, base, &a));
1665 if !b.is_zero() {
1666 consts.push(pow_rat(arena, base, &b));
1667 }
1668 }
1669 _ => {
1670 sym_poly_in(arena, f, var)?;
1671 poly_factors.push(f);
1672 }
1673 }
1674 }
1675 let n = binomial_n?;
1676 if poly_factors.is_empty() {
1677 return None; }
1679 let diff = arena.sub(hi, n);
1680 let diff = eval::eval(arena, diff);
1681 if !arena.is_zero_structural(diff) {
1682 return None;
1683 }
1684 let p_expr = mul_all(arena, &poly_factors);
1685 let monomials = sym_poly_in(arena, p_expr, var)?;
1686 let deg = monomials.iter().map(|(d, _)| *d).max()?;
1687 let x = if x_parts.is_empty() {
1689 arena.one
1690 } else {
1691 let xx = mul_all(arena, &x_parts);
1692 eval::eval(arena, xx)
1693 };
1694 if as_rat(arena, x) == Some(-Rat::one()) {
1695 return None; }
1697 let one = arena.one;
1698 let one_plus_x = arena.add(&[one, x]);
1699 let one_plus_x = eval::eval(arena, one_plus_x);
1700 let mut s: Vec<Vec<ExprId>> = vec![Vec::new(); deg + 1];
1702 for (m, c) in &monomials {
1703 let row = stirling_second_row(*m);
1704 for (j, st) in row.iter().enumerate() {
1705 if st.is_zero() {
1706 continue;
1707 }
1708 let se = rat_expr(arena, st.clone());
1709 s[j].push(arena.mul(&[se, *c]));
1710 }
1711 }
1712 let mut terms = Vec::new();
1713 let mut falling = arena.one; for (j, parts) in s.iter().enumerate() {
1715 if j > 0 {
1716 let shift = arena.int(-(j as i64 - 1));
1717 let factor = arena.add(&[n, shift]);
1718 falling = arena.mul(&[falling, factor]);
1719 }
1720 if parts.is_empty() {
1721 continue;
1722 }
1723 let sj = add_all(arena, parts);
1724 let xj = pow_rat(arena, x, &rat_i(j as i64));
1725 let nj = arena.int(-(j as i64));
1726 let n_minus_j = arena.add(&[n, nj]);
1727 let tail = arena.pow(one_plus_x, n_minus_j);
1728 terms.push(arena.mul(&[sj, falling, xj, tail]));
1729 }
1730 let total = add_all(arena, &terms);
1731 consts.push(total);
1732 let result = mul_all(arena, &consts);
1733 Some(eval::eval(arena, result))
1734}
1735
1736fn binomial_sum(
1737 arena: &mut Arena,
1738 body: ExprId,
1739 var: ExprId,
1740 lo: ExprId,
1741 hi: ExprId,
1742) -> Option<ExprId> {
1743 if let Some(r) = binomial_poly_sum(arena, body, var, lo, hi) {
1744 return Some(r);
1745 }
1746 let shape = term_shape(arena, body, var)?;
1747 if shape.binomials.len() != 1 || !shape.facts.is_empty() {
1748 return None;
1749 }
1750 let (n, e) = shape.binomials[0];
1751 if as_i64(arena, lo) != Some(0) {
1753 return None;
1754 }
1755 let diff = arena.sub(hi, n);
1756 let diff = eval::eval(arena, diff);
1757 if !arena.is_zero_structural(diff) {
1758 return None;
1759 }
1760 let one = arena.one;
1761 let two = arena.int(2);
1762 let n_m1 = arena.sub(n, one);
1763 let n_p1 = arena.add(&[n, one]);
1764 let x = shape.geometric_base(arena); let result = match (e, shape.lin_pows.as_slice(), x) {
1766 (1, [], None) => arena.pow(two, n),
1768 (1, [(b, p)], None) if b.is_zero() && p.is_one() => {
1770 let t = arena.pow(two, n_m1);
1771 arena.mul(&[n, t])
1772 }
1773 (1, [(b, p)], None) if b.is_zero() && *p == rat_i(2) => {
1775 let n_m2 = arena.int(-2);
1776 let n_m2 = arena.add(&[n, n_m2]);
1777 let t = arena.pow(two, n_m2);
1778 arena.mul(&[n, n_p1, t])
1779 }
1780 (1, [(b, p)], None) if b.is_one() && *p == -Rat::one() => {
1782 let t = arena.pow(two, n_p1);
1783 let num = arena.sub(t, one);
1784 arena.div(num, n_p1)
1785 }
1786 (2, [], None) => {
1788 let two_n = arena.mul(&[two, n]);
1789 arena.binomial(two_n, n)
1790 }
1791 (1, [], Some(x)) => {
1793 if as_rat(arena, x) == Some(-Rat::one()) {
1794 let zero = arena.zero;
1795 let cond = arena.eq_(n, zero);
1796 let ncond = arena.ne_(n, zero);
1797 arena.piecewise(&[(one, cond), (zero, ncond)])
1798 } else {
1799 let base = arena.add(&[one, x]);
1800 arena.pow(base, n)
1801 }
1802 }
1803 (1, [(b, p)], Some(x)) if b.is_zero() && p.is_one() => {
1805 let base = arena.add(&[one, x]);
1806 let t = arena.pow(base, n_m1);
1807 arena.mul(&[n, x, t])
1808 }
1809 _ => return None,
1810 };
1811 let total = arena.mul(&[shape.constant, result]);
1812 Some(eval::eval(arena, total))
1813}
1814
1815type GeometricSplit = (Vec<(usize, ExprId)>, ExprId, ExprId);
1821
1822fn geometric_split(arena: &mut Arena, body: ExprId, var: ExprId) -> Option<GeometricSplit> {
1826 let factors = mul_factors(arena, body);
1827 let mut y_parts = Vec::new();
1828 let mut c_parts = Vec::new();
1829 let mut p_parts = Vec::new();
1830 for f in factors {
1831 if !depends_on(arena, f, var) {
1832 c_parts.push(f);
1833 continue;
1834 }
1835 match arena.node(f).clone() {
1836 ExprNode::Pow(base, exp)
1837 if depends_on(arena, exp, var) && !depends_on(arena, base, var) =>
1838 {
1839 let (a, b) = linear_in(arena, exp, var)?;
1840 y_parts.push(pow_rat(arena, base, &a));
1841 if !b.is_zero() {
1842 c_parts.push(pow_rat(arena, base, &b));
1843 }
1844 }
1845 ExprNode::Exp(arg) => {
1846 let (a, b) = linear_in(arena, arg, var)?;
1847 let e = arena.e_const;
1848 y_parts.push(pow_rat(arena, e, &a));
1849 if !b.is_zero() {
1850 c_parts.push(pow_rat(arena, e, &b));
1851 }
1852 }
1853 _ => p_parts.push(f),
1854 }
1855 }
1856 if y_parts.is_empty() {
1857 return None;
1858 }
1859 let y = mul_all(arena, &y_parts);
1860 let y = eval::eval(arena, y);
1861 let p_expr = mul_all(arena, &p_parts);
1862 let poly = sym_poly_in(arena, p_expr, var)?;
1863 if poly.is_empty() {
1864 return None;
1865 }
1866 let c = mul_all(arena, &c_parts);
1867 Some((poly, y, c))
1868}
1869
1870fn apply_euler_operator(
1872 arena: &mut Arena,
1873 poly: &[(usize, ExprId)],
1874 s0: ExprId,
1875 r: ExprId,
1876 y: ExprId,
1877) -> ExprId {
1878 let max_p = poly.iter().map(|(p, _)| *p).max().unwrap_or(0);
1879 let mut derivs = Vec::with_capacity(max_p + 1);
1880 derivs.push(s0);
1881 for _ in 0..max_p {
1882 let prev = *derivs.last().unwrap_or(&s0);
1883 let d = crate::transforms::diff::diff(arena, prev, r);
1884 let rd = arena.mul(&[r, d]);
1885 let rd = eval::eval(arena, rd);
1886 derivs.push(rd);
1887 }
1888 let mut terms = Vec::new();
1889 for &(p, c) in poly {
1890 terms.push(arena.mul(&[c, derivs[p]]));
1891 }
1892 let total = add_all(arena, &terms);
1893 let at_y = subs::subs(arena, total, r, y);
1894 let at_y = eval::eval(arena, at_y);
1895 let together = arena.together_expr(at_y);
1896 eval::eval(arena, together)
1897}
1898
1899fn geometric_poly_finite(
1900 arena: &mut Arena,
1901 body: ExprId,
1902 var: ExprId,
1903 lo: ExprId,
1904 hi: ExprId,
1905) -> Option<ExprId> {
1906 let (poly, y, c) = geometric_split(arena, body, var)?;
1907 if as_rat(arena, y) == Some(Rat::one()) {
1908 return None; }
1910 if as_rat(arena, y).is_some()
1912 && let Some(g) = gosper::gosper_sum(arena, body, var, lo, hi)
1913 {
1914 return Some(g);
1915 }
1916 let r = arena.symbol("_r");
1917 let one = arena.one;
1918 let r_lo = arena.pow(r, lo);
1919 let hi1 = arena.add(&[hi, one]);
1920 let r_hi1 = arena.pow(r, hi1);
1921 let num = arena.sub(r_lo, r_hi1);
1922 let den = arena.sub(one, r);
1923 let s0 = arena.div(num, den);
1924 let formula = apply_euler_operator(arena, &poly, s0, r, y);
1925 let formula = arena.mul(&[c, formula]);
1926 if as_rat(arena, y).is_some() || const_value(arena, y).is_some() {
1927 return Some(formula);
1928 }
1929 let poly_sum = faulhaber_sym(arena, &poly, lo, hi);
1932 let poly_sum = arena.mul(&[c, poly_sum]);
1933 let cond = arena.eq_(y, one);
1934 let ncond = arena.ne_(y, one);
1935 Some(arena.piecewise(&[(poly_sum, cond), (formula, ncond)]))
1936}
1937
1938fn geometric_poly_infinite(
1939 arena: &mut Arena,
1940 body: ExprId,
1941 var: ExprId,
1942 lo: ExprId,
1943) -> Option<SumOutcome> {
1944 let (poly, y, c) = geometric_split(arena, body, var)?;
1945 match abs_less_than_one(arena, y) {
1946 Some(true) => {}
1947 Some(false) => {
1948 let y_pos = const_sign(arena, y) == Some(true);
1950 let lead = poly.last().map(|(_, c)| *c);
1951 let sign = if y_pos {
1952 lead.and_then(|l| {
1953 let lc = arena.mul(&[c, l]);
1954 const_sign(arena, lc)
1955 })
1956 } else {
1957 None
1958 };
1959 return Some(SumOutcome::Divergent(infinity_of_sign(arena, sign)));
1960 }
1961 None => return Some(SumOutcome::Unevaluated),
1962 }
1963 let r = arena.symbol("_r");
1964 let one = arena.one;
1965 let r_lo = arena.pow(r, lo);
1966 let den = arena.sub(one, r);
1967 let s0 = arena.div(r_lo, den);
1968 let formula = apply_euler_operator(arena, &poly, s0, r, y);
1969 let total = arena.mul(&[c, formula]);
1970 Some(SumOutcome::Closed(eval::eval(arena, total)))
1971}
1972
1973fn infinite_sum(arena: &mut Arena, body: ExprId, var: ExprId, lo: ExprId) -> SumOutcome {
1978 if !depends_on(arena, body, var) {
1980 let v = eval::eval(arena, body);
1981 if arena.is_zero_structural(v) {
1982 return SumOutcome::Closed(v);
1983 }
1984 let sign = const_sign(arena, v);
1985 return SumOutcome::Divergent(infinity_of_sign(arena, sign));
1986 }
1987 let node = arena.node(body).clone();
1988
1989 if let Some(r) = rational_sum_infinite(arena, body, var, lo) {
1991 return r;
1992 }
1993
1994 if let ExprNode::Add(ref terms) = node {
1996 let terms: Vec<ExprId> = terms.to_vec();
1997 if let Some(r) = telescoping_infinite(arena, &terms, var, lo) {
1998 return r;
1999 }
2000 let mut closed = Vec::new();
2001 let mut divergent: Vec<Option<ExprId>> = Vec::new();
2002 let mut unknown = 0usize;
2003 for &t in &terms {
2004 match infinite_sum(arena, t, var, lo) {
2005 SumOutcome::Closed(v) => closed.push(v),
2006 SumOutcome::Divergent(d) => divergent.push(d),
2007 SumOutcome::Unevaluated => unknown += 1,
2008 }
2009 }
2010 if unknown == 0 && divergent.is_empty() {
2011 let s = add_all(arena, &closed);
2012 return SumOutcome::Closed(eval::eval(arena, s));
2013 }
2014 if unknown == 0 && divergent.len() == 1 {
2015 return SumOutcome::Divergent(divergent[0]);
2016 }
2017 if unknown == 0 && !divergent.is_empty() {
2018 let first = divergent[0];
2020 if first.is_some() && divergent.iter().all(|d| *d == first) {
2021 return SumOutcome::Divergent(first);
2022 }
2023 }
2024 return SumOutcome::Unevaluated;
2025 }
2026
2027 if let ExprNode::Mul(ref factors) = node {
2029 let factors: Vec<ExprId> = factors.to_vec();
2030 let (consts, varf): (Vec<ExprId>, Vec<ExprId>) = factors
2031 .iter()
2032 .copied()
2033 .partition(|&f| !depends_on(arena, f, var));
2034 if !consts.is_empty() && !varf.is_empty() {
2035 let c = mul_all(arena, &consts);
2036 let c = eval::eval(arena, c);
2037 if arena.is_zero_structural(c) {
2038 return SumOutcome::Closed(c);
2039 }
2040 let inner = mul_all(arena, &varf);
2041 return match infinite_sum(arena, inner, var, lo) {
2042 SumOutcome::Closed(v) => {
2043 let s = arena.mul(&[c, v]);
2044 SumOutcome::Closed(eval::eval(arena, s))
2045 }
2046 SumOutcome::Divergent(Some(inf)) => match const_sign(arena, c) {
2047 Some(true) => SumOutcome::Divergent(Some(inf)),
2048 Some(false) => {
2049 let flipped = if inf == arena.infinity {
2050 arena.neg_infinity
2051 } else {
2052 arena.infinity
2053 };
2054 SumOutcome::Divergent(Some(flipped))
2055 }
2056 None => SumOutcome::Divergent(None),
2057 },
2058 other => other,
2059 };
2060 }
2061 }
2062
2063 let shape = term_shape(arena, body, var);
2066 if let Some(shape) = &shape
2067 && let Some(r) = p_series_infinite(arena, shape, lo)
2068 {
2069 return r;
2070 }
2071 if let Some(r) = geometric_poly_infinite(arena, body, var, lo) {
2072 return r;
2073 }
2074 if let Some(shape) = &shape
2075 && let Some(r) = power_series_infinite(arena, shape, var, lo)
2076 {
2077 return r;
2078 }
2079
2080 if let Some(r) = gosper_infinite(arena, body, var, lo) {
2082 return r;
2083 }
2084
2085 if crate::calculus::convergence::is_convergent(arena, body, var) == Some(false) {
2087 let sign = eventual_sign(arena, body, var);
2088 return SumOutcome::Divergent(infinity_of_sign(arena, sign));
2089 }
2090
2091 SumOutcome::Unevaluated
2092}
2093
2094fn eventual_sign(arena: &mut Arena, body: ExprId, var: ExprId) -> Option<bool> {
2098 let shape = term_shape(arena, body, var)?;
2099 if shape.alternating {
2100 return None;
2101 }
2102 for (b, a) in &shape.bases {
2103 if a.is_integer() && a.to_integer().to_i64()? % 2 == 0 {
2104 continue;
2105 }
2106 if const_sign(arena, *b) != Some(true) {
2107 return None;
2108 }
2109 }
2110 if shape.numeric_base.is_negative() {
2111 return None;
2112 }
2113 const_sign(arena, shape.constant)
2114}
2115
2116fn gosper_infinite(arena: &mut Arena, body: ExprId, var: ExprId, lo: ExprId) -> Option<SumOutcome> {
2120 let n = arena.symbol("_N");
2121 let g = gosper::gosper_sum(arena, body, var, lo, n)?;
2122 let limit = limit_at_infinity(arena, g, n)?;
2123 Some(SumOutcome::Closed(limit))
2124}
2125
2126fn p_series_infinite(arena: &mut Arena, shape: &TermShape, lo: ExprId) -> Option<SumOutcome> {
2130 if !shape.is_pure_lin_pows() || shape.lin_pows.len() != 1 {
2131 return None;
2132 }
2133 let (beta, exp) = shape.lin_pows[0].clone();
2134 let c = shape.constant;
2135 if exp >= -Rat::one() {
2136 if shape.alternating {
2138 if exp >= Rat::zero() {
2139 return Some(SumOutcome::Divergent(None));
2140 }
2141 } else {
2144 let sign = const_sign(arena, c);
2145 return Some(SumOutcome::Divergent(infinity_of_sign(arena, sign)));
2146 }
2147 }
2148 if !exp.is_integer() {
2149 return None;
2150 }
2151 let p = (-exp.to_integer().to_i64()?) as usize;
2152 let lo_r = const_value(arena, lo)?;
2153 let q = &lo_r + β let value = if !shape.alternating {
2155 if beta.is_integer() || (&beta * rat_i(2)).is_integer() {
2157 hurwitz_zeta_closed(arena, p, &q)?
2158 } else {
2159 return None;
2160 }
2161 } else if beta.is_integer() {
2162 if !q.is_positive() {
2164 return None;
2165 }
2166 let qi = q.to_integer().to_i64()?;
2167 if qi > MAX_TELESCOPE_SHIFT {
2168 return None;
2169 }
2170 let eta = eta_value(arena, p)?;
2171 let mut acc = Rat::zero();
2172 for j in 1..qi {
2173 let s = if j % 2 == 0 { Rat::one() } else { -Rat::one() };
2174 acc += s * rat_pow_i(&rat_i(j), -(p as i64));
2175 }
2176 let neg_eta = arena.neg(eta);
2177 let ce = rat_expr(arena, -acc);
2178 let inner = arena.add(&[neg_eta, ce]);
2179 let sign_beta = if beta.to_integer().to_i64()?.rem_euclid(2) == 0 {
2180 arena.one
2181 } else {
2182 arena.neg_one
2183 };
2184 let v = arena.mul(&[sign_beta, inner]);
2185 eval::eval(arena, v)
2186 } else if (&beta * rat_i(2)).is_integer() {
2187 let half = Rat::new(BigInt::one(), BigInt::from(2));
2191 let n = &beta - ½
2192 let s = &q - ½
2193 if !s.is_integer() || s.is_negative() {
2194 return None;
2195 }
2196 let si = s.to_integer().to_i64()?;
2197 if si > MAX_TELESCOPE_SHIFT {
2198 return None;
2199 }
2200 let ni = n.to_integer().to_i64()?;
2201 let beta_p = dirichlet_beta_value(arena, p)?;
2202 let mut acc = Rat::zero();
2203 for j in 0..si {
2204 let sg = if j % 2 == 0 { Rat::one() } else { -Rat::one() };
2205 acc += sg * rat_pow_i(&rat_i(2 * j + 1), -(p as i64));
2206 }
2207 let ce = rat_expr(arena, -acc);
2208 let inner = arena.add(&[beta_p, ce]);
2209 let scale = rat_pow_i(&rat_i(2), p as i64)
2210 * if ni.rem_euclid(2) == 0 {
2211 Rat::one()
2212 } else {
2213 -Rat::one()
2214 };
2215 let se = rat_expr(arena, scale);
2216 let v = arena.mul(&[se, inner]);
2217 eval::eval(arena, v)
2218 } else {
2219 return None;
2220 };
2221 let total = arena.mul(&[c, value]);
2222 Some(SumOutcome::Closed(eval::eval(arena, total)))
2223}
2224
2225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2229enum Domain {
2230 Everywhere,
2232 OpenUnit,
2234 ClosedUnit,
2236 OpenUnitOrMinusOne,
2238}
2239
2240struct SeriesEntry {
2248 name: &'static str,
2249 alternating: bool,
2250 base_const: Rat,
2251 lin_pows: &'static [(i64, i64, i64)], facts: &'static [(i64, i64, i64)], a: i64,
2254 b: i64,
2255 k0: i64,
2256 table_const: Rat,
2257 domain: Domain,
2258 build: fn(&mut Arena, ExprId) -> ExprId,
2259}
2260
2261fn series_table() -> Vec<SeriesEntry> {
2262 let one = Rat::one;
2263 let half = || Rat::new(BigInt::one(), BigInt::from(2));
2264 vec![
2265 SeriesEntry {
2266 name: "exp",
2267 alternating: false,
2268 base_const: one(),
2269 lin_pows: &[],
2270 facts: &[(1, 0, -1)],
2271 a: 1,
2272 b: 0,
2273 k0: 0,
2274 table_const: one(),
2275 domain: Domain::Everywhere,
2276 build: |a, x| a.exp(x),
2277 },
2278 SeriesEntry {
2279 name: "sin",
2280 alternating: true,
2281 base_const: one(),
2282 lin_pows: &[],
2283 facts: &[(2, 1, -1)],
2284 a: 2,
2285 b: 1,
2286 k0: 0,
2287 table_const: one(),
2288 domain: Domain::Everywhere,
2289 build: |a, x| a.sin(x),
2290 },
2291 SeriesEntry {
2292 name: "cos",
2293 alternating: true,
2294 base_const: one(),
2295 lin_pows: &[],
2296 facts: &[(2, 0, -1)],
2297 a: 2,
2298 b: 0,
2299 k0: 0,
2300 table_const: one(),
2301 domain: Domain::Everywhere,
2302 build: |a, x| a.cos(x),
2303 },
2304 SeriesEntry {
2305 name: "sinh",
2306 alternating: false,
2307 base_const: one(),
2308 lin_pows: &[],
2309 facts: &[(2, 1, -1)],
2310 a: 2,
2311 b: 1,
2312 k0: 0,
2313 table_const: one(),
2314 domain: Domain::Everywhere,
2315 build: |a, x| a.sinh(x),
2316 },
2317 SeriesEntry {
2318 name: "cosh",
2319 alternating: false,
2320 base_const: one(),
2321 lin_pows: &[],
2322 facts: &[(2, 0, -1)],
2323 a: 2,
2324 b: 0,
2325 k0: 0,
2326 table_const: one(),
2327 domain: Domain::Everywhere,
2328 build: |a, x| a.cosh(x),
2329 },
2330 SeriesEntry {
2331 name: "-ln(1-x)",
2332 alternating: false,
2333 base_const: one(),
2334 lin_pows: &[(0, 1, -1)],
2335 facts: &[],
2336 a: 1,
2337 b: 0,
2338 k0: 1,
2339 table_const: one(),
2340 domain: Domain::OpenUnitOrMinusOne,
2341 build: |a, x| {
2342 let one = a.one;
2343 let omx = a.sub(one, x);
2344 let omx = eval::eval(a, omx);
2345 if let Some(r) = as_rat(a, omx)
2347 && r.is_positive()
2348 {
2349 let inv = rat_expr(a, Rat::one() / r);
2350 return a.ln(inv);
2351 }
2352 let l = a.ln(omx);
2353 a.neg(l)
2354 },
2355 },
2356 SeriesEntry {
2357 name: "atan",
2358 alternating: true,
2359 base_const: one(),
2360 lin_pows: &[(1, 2, -1)],
2361 facts: &[],
2362 a: 2,
2363 b: 1,
2364 k0: 0,
2365 table_const: half(),
2366 domain: Domain::ClosedUnit,
2367 build: |a, x| a.atan(x),
2368 },
2369 SeriesEntry {
2370 name: "atanh",
2371 alternating: false,
2372 base_const: one(),
2373 lin_pows: &[(1, 2, -1)],
2374 facts: &[],
2375 a: 2,
2376 b: 1,
2377 k0: 0,
2378 table_const: half(),
2379 domain: Domain::OpenUnit,
2380 build: |a, x| a.atanh(x),
2381 },
2382 SeriesEntry {
2383 name: "1/(1-x)",
2384 alternating: false,
2385 base_const: one(),
2386 lin_pows: &[],
2387 facts: &[],
2388 a: 1,
2389 b: 0,
2390 k0: 0,
2391 table_const: one(),
2392 domain: Domain::OpenUnit,
2393 build: |a, x| {
2394 let one = a.one;
2395 let omx = a.sub(one, x);
2396 a.div(one, omx)
2397 },
2398 },
2399 SeriesEntry {
2401 name: "asin",
2402 alternating: false,
2403 base_const: Rat::new(BigInt::one(), BigInt::from(4)),
2404 lin_pows: &[(1, 2, -1)],
2405 facts: &[(1, 0, -2), (2, 0, 1)],
2406 a: 2,
2407 b: 1,
2408 k0: 0,
2409 table_const: half(),
2410 domain: Domain::ClosedUnit,
2411 build: |a, x| a.asin(x),
2412 },
2413 SeriesEntry {
2414 name: "asinh",
2415 alternating: true,
2416 base_const: Rat::new(BigInt::one(), BigInt::from(4)),
2417 lin_pows: &[(1, 2, -1)],
2418 facts: &[(1, 0, -2), (2, 0, 1)],
2419 a: 2,
2420 b: 1,
2421 k0: 0,
2422 table_const: half(),
2423 domain: Domain::ClosedUnit,
2424 build: |a, x| a.asinh(x),
2425 },
2426 SeriesEntry {
2428 name: "erf",
2429 alternating: true,
2430 base_const: one(),
2431 lin_pows: &[(1, 2, -1)],
2432 facts: &[(1, 0, -1)],
2433 a: 2,
2434 b: 1,
2435 k0: 0,
2436 table_const: half(),
2437 domain: Domain::Everywhere,
2438 build: |a, x| {
2439 let e = a.erf(x);
2440 let pi = a.pi;
2441 let sp = a.sqrt(pi);
2442 let two = a.int(2);
2443 let half_sqrt_pi = a.div(sp, two);
2444 a.mul(&[half_sqrt_pi, e])
2445 },
2446 },
2447 SeriesEntry {
2449 name: "besselj0",
2450 alternating: true,
2451 base_const: Rat::new(BigInt::one(), BigInt::from(4)),
2452 lin_pows: &[],
2453 facts: &[(1, 0, -2)],
2454 a: 2,
2455 b: 0,
2456 k0: 0,
2457 table_const: one(),
2458 domain: Domain::Everywhere,
2459 build: |a, x| {
2460 let z = a.zero;
2461 a.besselj(z, x)
2462 },
2463 },
2464 SeriesEntry {
2465 name: "besseli0",
2466 alternating: false,
2467 base_const: Rat::new(BigInt::one(), BigInt::from(4)),
2468 lin_pows: &[],
2469 facts: &[(1, 0, -2)],
2470 a: 2,
2471 b: 0,
2472 k0: 0,
2473 table_const: one(),
2474 domain: Domain::Everywhere,
2475 build: |a, x| {
2476 let z = a.zero;
2477 a.besseli(z, x)
2478 },
2479 },
2480 ]
2481}
2482
2483fn entry_lin_pows(e: &SeriesEntry) -> Vec<(Rat, Rat)> {
2484 e.lin_pows
2485 .iter()
2486 .map(|&(bn, bd, p)| (Rat::new(BigInt::from(bn), BigInt::from(bd)), rat_i(p)))
2487 .collect()
2488}
2489
2490fn entry_facts(e: &SeriesEntry) -> Vec<(Rat, Rat, i64)> {
2491 let mut v: Vec<(Rat, Rat, i64)> = e
2492 .facts
2493 .iter()
2494 .map(|&(a, b, ee)| (rat_i(a), rat_i(b), ee))
2495 .collect();
2496 v.sort_by_key(|a| (a.0.clone(), a.1.clone()));
2497 v
2498}
2499
2500fn in_domain(arena: &mut Arena, x: ExprId, domain: Domain) -> Option<bool> {
2502 if domain == Domain::Everywhere {
2503 return Some(true);
2504 }
2505 if let Some(r) = const_value(arena, x) {
2506 let a = r.abs();
2507 let one = Rat::one();
2508 return Some(match domain {
2509 Domain::Everywhere => true,
2510 Domain::OpenUnit => a < one,
2511 Domain::ClosedUnit => a <= one,
2512 Domain::OpenUnitOrMinusOne => a < one || r == -one,
2513 });
2514 }
2515 abs_less_than_one(arena, x)
2519}
2520
2521fn power_series_infinite(
2523 arena: &mut Arena,
2524 shape: &TermShape,
2525 var: ExprId,
2526 lo: ExprId,
2527) -> Option<SumOutcome> {
2528 if !shape.binomials.is_empty() {
2529 return None;
2530 }
2531 let lo_i = as_i64(arena, lo)?;
2532 let mut extra_power: i64 = 0;
2534 let mut lin = shape.lin_pows.clone();
2535 if let Some(pos) = lin
2536 .iter()
2537 .position(|(b, p)| b.is_zero() && p.is_integer() && p.is_positive())
2538 {
2539 let (_, p) = lin.remove(pos);
2540 extra_power = p.to_integer().to_i64()?;
2541 if extra_power > 6 {
2542 return None;
2543 }
2544 }
2545 let _ = var;
2546 for entry in series_table() {
2547 if entry_lin_pows(&entry) != lin || entry_facts(&entry) != shape.facts {
2549 continue;
2550 }
2551 let mut flip_x = false;
2553 if entry.alternating != shape.alternating {
2554 if entry.a % 2 == 1 {
2555 flip_x = true;
2556 } else {
2557 continue;
2558 }
2559 }
2560 let ratio = &shape.numeric_base / &entry.base_const;
2562 let a_rat = rat_i(entry.a);
2563 let mut x_factors = Vec::new();
2564 if !ratio.is_one() {
2565 if ratio.is_negative() && entry.a % 2 == 0 {
2566 continue;
2567 }
2568 let re = rat_expr(arena, ratio.clone());
2569 let inv_a = Rat::one() / &a_rat;
2570 x_factors.push(pow_rat(arena, re, &inv_a));
2571 }
2572 let mut ok = true;
2573 for (base, a) in &shape.bases {
2574 let q = a / &a_rat;
2575 if !q.is_integer() {
2576 ok = false;
2577 break;
2578 }
2579 x_factors.push(pow_rat(arena, *base, &q));
2580 }
2581 if !ok {
2582 continue;
2583 }
2584 let x = mul_all(arena, &x_factors);
2585 let x = eval::eval(arena, x);
2586 let x = if flip_x {
2587 let nx = arena.neg(x);
2588 eval::eval(arena, nx)
2589 } else {
2590 x
2591 };
2592 let xb = pow_rat(arena, x, &rat_i(entry.b));
2594 let tc = rat_expr(arena, entry.table_const.clone());
2595 let mut denom_factors = vec![tc, xb];
2596 if flip_x && entry.b % 2 == 1 {
2597 denom_factors.push(arena.neg_one);
2598 }
2599 let denom = arena.mul(&denom_factors);
2600 let coeff = arena.div(shape.constant, denom);
2601 let coeff = eval::eval(arena, coeff);
2602 match in_domain(arena, x, entry.domain) {
2604 Some(true) => {}
2605 Some(false) => {
2606 let sign = if !shape.alternating && const_sign(arena, x) == Some(true) {
2607 const_sign(arena, coeff)
2608 } else {
2609 None
2610 };
2611 return Some(SumOutcome::Divergent(infinity_of_sign(arena, sign)));
2612 }
2613 None => return Some(SumOutcome::Unevaluated),
2614 }
2615 if lo_i < entry.k0 {
2616 return None;
2617 }
2618 tracing::debug!("summation: power-series table hit `{}`", entry.name);
2619 let f = if extra_power > 0 {
2620 let xs = arena.symbol("_x");
2623 let mut g = (entry.build)(arena, xs);
2624 for _ in 0..extra_power {
2625 let d = crate::transforms::diff::diff(arena, g, xs);
2626 let xd = arena.mul(&[xs, d]);
2627 let be = arena.int(entry.b);
2628 let bg = arena.mul(&[be, g]);
2629 let num = arena.sub(xd, bg);
2630 let ae = arena.int(entry.a);
2631 g = arena.div(num, ae);
2632 g = eval::eval(arena, g);
2633 }
2634 let at_x = subs::subs(arena, g, xs, x);
2635 eval::eval(arena, at_x)
2636 } else {
2637 (entry.build)(arena, x)
2638 };
2639 let mut skipped = Vec::new();
2642 for k in entry.k0..lo_i {
2643 let ke = arena.int(k);
2644 let mut fs = vec![tc];
2646 if entry.alternating && k % 2 != 0 {
2647 fs.push(arena.neg_one);
2648 }
2649 if !entry.base_const.is_one() {
2650 fs.push(rat_expr(arena, rat_pow_i(&entry.base_const, k)));
2651 }
2652 fs.push(pow_rat(arena, x, &rat_i(entry.a * k + entry.b)));
2653 for (beta, p) in entry_lin_pows(&entry) {
2654 let kb = rat_i(k) + beta;
2655 if kb.is_zero() && p.is_negative() {
2656 return None;
2657 }
2658 fs.push(rat_expr(arena, rat_pow_i(&kb, p.to_integer().to_i64()?)));
2659 }
2660 for (al, be, e) in entry_facts(&entry) {
2661 let arg = (al * rat_i(k) + be).to_integer().to_i64()?;
2662 if arg < 0 {
2663 return None;
2664 }
2665 let fv = Rat::from_integer(factorial_big(arg as u64));
2666 fs.push(rat_expr(arena, rat_pow_i(&fv, e)));
2667 }
2668 if extra_power > 0 {
2669 fs.push(pow_rat(arena, ke, &rat_i(extra_power)));
2670 }
2671 skipped.push(arena.mul(&fs));
2672 }
2673 let mut total_terms = vec![f];
2674 for s in skipped {
2675 total_terms.push(arena.neg(s));
2676 }
2677 let inner = add_all(arena, &total_terms);
2678 let total = arena.mul(&[coeff, inner]);
2679 return Some(SumOutcome::Closed(eval::eval(arena, total)));
2680 }
2681 None
2682}
2683
2684pub(crate) fn product(
2690 arena: &mut Arena,
2691 body: ExprId,
2692 var: ExprId,
2693 lower: ExprId,
2694 upper: ExprId,
2695) -> SumOutcome {
2696 if !matches!(arena.node(var), ExprNode::Symbol(_)) {
2697 return SumOutcome::Unevaluated;
2698 }
2699 match (classify_bound(arena, lower), classify_bound(arena, upper)) {
2700 (Bound::Finite(lo), Bound::Finite(hi)) => finite_product(arena, body, var, lo, hi),
2701 (Bound::Finite(lo), Bound::PosInf) => infinite_product(arena, body, var, lo),
2702 _ => SumOutcome::Unevaluated,
2703 }
2704}
2705
2706fn finite_product(
2707 arena: &mut Arena,
2708 body: ExprId,
2709 var: ExprId,
2710 lo: ExprId,
2711 hi: ExprId,
2712) -> SumOutcome {
2713 if let (Some(a), Some(b)) = (as_i64(arena, lo), as_i64(arena, hi)) {
2714 if b < a {
2715 return SumOutcome::Closed(arena.one);
2716 }
2717 if b - a < MAX_ENUMERATION_TERMS {
2718 return SumOutcome::Closed(enumerate_product(arena, body, var, a, b));
2719 }
2720 }
2721 match product_closed(arena, body, var, lo, hi) {
2722 Some(id) => {
2723 let n = gamma_ratio_normalize(arena, id);
2724 let n = gamma_to_factorial(arena, n);
2725 let n = flatten_nested_pows(arena, n);
2726 SumOutcome::Closed(eval::eval(arena, n))
2727 }
2728 None => SumOutcome::Unevaluated,
2729 }
2730}
2731
2732fn arena_neg_one(arena: &Arena) -> ExprId {
2733 arena.neg_one
2734}
2735
2736fn flatten_nested_pows(arena: &mut Arena, expr: ExprId) -> ExprId {
2739 let factors = mul_factors(arena, expr);
2740 let mut out: Vec<ExprId> = Vec::with_capacity(factors.len());
2741 let mut numeric_pows: Vec<(ExprId, Vec<ExprId>)> = Vec::new();
2743 let mut changed = false;
2744 for f in factors {
2745 if let ExprNode::Pow(base, exp) = arena.node(f).clone() {
2746 if let ExprNode::Pow(b2, e2) = arena.node(base).clone()
2749 && let Some(q) = as_rat(arena, exp)
2750 && (q.is_integer() || as_rat(arena, e2).is_some())
2751 {
2752 let qe = rat_expr(arena, q);
2753 let pq = arena.mul(&[e2, qe]);
2754 let pq = eval::eval(arena, pq);
2755 let flat = arena.pow(b2, pq);
2756 if let ExprNode::Pow(nb, ne) = arena.node(flat).clone()
2758 && arena.as_num(nb).is_some()
2759 && arena.as_num(ne).is_none()
2760 {
2761 if let Some(slot) = numeric_pows.iter_mut().find(|(b, _)| *b == nb) {
2762 slot.1.push(ne);
2763 } else {
2764 numeric_pows.push((nb, vec![ne]));
2765 }
2766 } else {
2767 out.push(flat);
2768 }
2769 changed = true;
2770 continue;
2771 }
2772 if arena.as_num(base).is_some() && arena.as_num(exp).is_none() {
2773 if let Some(slot) = numeric_pows.iter_mut().find(|(b, _)| *b == base) {
2774 slot.1.push(exp);
2775 changed = true;
2776 } else {
2777 numeric_pows.push((base, vec![exp]));
2778 }
2779 continue;
2780 }
2781 }
2782 out.push(f);
2783 }
2784 for (base, exps) in numeric_pows {
2785 let e = add_all(arena, &exps);
2786 let e = eval::eval(arena, e);
2787 out.push(arena.pow(base, e));
2788 }
2789 if changed {
2790 let r = mul_all(arena, &out);
2791 eval::eval(arena, r)
2792 } else {
2793 expr
2794 }
2795}
2796
2797fn product_closed(
2798 arena: &mut Arena,
2799 body: ExprId,
2800 var: ExprId,
2801 lo: ExprId,
2802 hi: ExprId,
2803) -> Option<ExprId> {
2804 if !depends_on(arena, body, var) {
2805 let n = range_count(arena, lo, hi);
2806 return Some(arena.pow(body, n));
2807 }
2808 match arena.node(body).clone() {
2809 ExprNode::Mul(ref factors) => {
2810 let factors: Vec<ExprId> = factors.to_vec();
2811 let mut parts = Vec::new();
2812 for f in factors {
2813 parts.push(product_closed(arena, f, var, lo, hi)?);
2814 }
2815 Some(arena.mul(&parts))
2816 }
2817 ExprNode::Neg(inner) => {
2818 let m1 = arena.neg_one;
2819 let n = range_count(arena, lo, hi);
2820 let s = arena.pow(m1, n);
2821 let p = product_closed(arena, inner, var, lo, hi)?;
2822 Some(arena.mul(&[s, p]))
2823 }
2824 ExprNode::Pow(base, exp) if !depends_on(arena, exp, var) => {
2825 let p = product_closed(arena, base, var, lo, hi)?;
2826 Some(arena.pow(p, exp))
2827 }
2828 ExprNode::Pow(base, exp) if !depends_on(arena, base, var) => {
2829 let s = match summation(arena, exp, var, lo, hi) {
2830 SumOutcome::Closed(s) if !walk::has_unevaluated(arena, s) => s,
2831 _ => return None,
2832 };
2833 Some(arena.pow(base, s))
2834 }
2835 ExprNode::Exp(arg) => {
2836 let s = match summation(arena, arg, var, lo, hi) {
2837 SumOutcome::Closed(s) if !walk::has_unevaluated(arena, s) => s,
2838 _ => return None,
2839 };
2840 Some(arena.exp(s))
2841 }
2842 _ => rational_product(arena, body, var, lo, hi)
2843 .or_else(|| linear_symbolic_product(arena, body, var, lo, hi)),
2844 }
2845}
2846
2847fn linear_symbolic_product(
2850 arena: &mut Arena,
2851 body: ExprId,
2852 var: ExprId,
2853 lo: ExprId,
2854 hi: ExprId,
2855) -> Option<ExprId> {
2856 let terms = sym_poly_in(arena, body, var)?;
2857 let alpha_e = terms.iter().find(|(p, _)| *p == 1).map(|(_, c)| *c)?;
2858 if terms.iter().any(|(p, _)| *p > 1) {
2859 return None;
2860 }
2861 let alpha = as_rat(arena, alpha_e)?;
2862 if alpha.is_zero() {
2863 return None;
2864 }
2865 let beta = terms
2866 .iter()
2867 .find(|(p, _)| *p == 0)
2868 .map(|(_, c)| *c)
2869 .unwrap_or(arena.zero);
2870 let count = range_count(arena, lo, hi);
2871 let mut factors = Vec::new();
2872 if !alpha.is_one() {
2873 factors.push(arena.pow(alpha_e, count));
2874 }
2875 let inv_alpha = rat_expr(arena, Rat::one() / &alpha);
2876 let shift = arena.mul(&[beta, inv_alpha]);
2877 let one = arena.one;
2878 let hi1 = arena.add(&[hi, one]);
2879 let top_arg = arena.add(&[hi1, shift]);
2880 let bot_arg = arena.add(&[lo, shift]);
2881 let top = arena.gamma(top_arg);
2882 let bot = arena.gamma(bot_arg);
2883 factors.push(top);
2884 factors.push(pow_rat(arena, bot, &(-Rat::one())));
2885 Some(arena.mul(&factors))
2886}
2887
2888fn rational_product(
2891 arena: &mut Arena,
2892 body: ExprId,
2893 var: ExprId,
2894 lo: ExprId,
2895 hi: ExprId,
2896) -> Option<ExprId> {
2897 let body = combine_fractions(arena, body);
2898 let (n, d) = polybridge::as_numer_denom(arena, body);
2899 let np = polybridge::expr_to_poly(arena, n, var)?;
2900 let dp = polybridge::expr_to_poly(arena, d, var)?;
2901 if np.is_zero() || dp.is_zero() {
2902 return None;
2903 }
2904 let count = range_count(arena, lo, hi);
2905 let mut factors = Vec::new();
2906 for (poly, sign) in [(np, 1i64), (dp, -1i64)] {
2907 let (content, irreducibles) = poly.factor_over_z();
2908 if !content.is_one() {
2909 let ce = rat_expr(arena, content);
2910 let p = pow_rat(arena, ce, &rat_i(sign));
2911 factors.push(arena.pow(p, count));
2912 }
2913 for (fac, mult) in irreducibles {
2914 if fac.degree() != Some(1) {
2915 return None;
2916 }
2917 let alpha = fac.coeff(1);
2918 let beta = fac.coeff(0);
2919 let e = rat_i(sign * mult as i64);
2920 if !alpha.is_one() {
2922 let ae = rat_expr(arena, alpha.clone());
2923 let ap = pow_rat(arena, ae, &e);
2924 factors.push(arena.pow(ap, count));
2925 }
2926 let shift = &beta / α
2927 let one = arena.one;
2928 let hi1 = arena.add(&[hi, one]);
2929 let top_arg = add_rat(arena, hi1, &shift);
2930 let bot_arg = add_rat(arena, lo, &shift);
2931 let top = arena.gamma(top_arg);
2932 let bot = arena.gamma(bot_arg);
2933 factors.push(pow_rat(arena, top, &e));
2934 factors.push(pow_rat(arena, bot, &(-e)));
2935 }
2936 }
2937 Some(arena.mul(&factors))
2938}
2939
2940pub(crate) fn gamma_ratio_normalize(arena: &mut Arena, expr: ExprId) -> ExprId {
2944 let expr = eval::eval(arena, expr);
2945 let factors = mul_factors(arena, expr);
2946 let mut gammas: Vec<(ExprId, Rat)> = Vec::new();
2948 let mut others: Vec<ExprId> = Vec::new();
2949 for f in factors {
2950 match arena.node(f).clone() {
2951 ExprNode::Gamma(arg) => gammas.push((arg, Rat::one())),
2952 ExprNode::Pow(base, exp) => {
2953 if let (ExprNode::Gamma(arg), Some(e)) =
2954 (arena.node(base).clone(), as_rat(arena, exp))
2955 {
2956 gammas.push((arg, e));
2957 } else {
2958 others.push(f);
2959 }
2960 }
2961 _ => others.push(f),
2962 }
2963 }
2964 if gammas.len() < 2 {
2965 return expr;
2966 }
2967 let mut groups: Vec<Vec<(ExprId, Rat, i64)>> = Vec::new(); 'outer: for (arg, e) in gammas {
2970 for g in groups.iter_mut() {
2971 let (ref_arg, _, _) = g[0];
2972 let diff = arena.sub(arg, ref_arg);
2973 if let Some(d) = eval_rat(arena, diff)
2974 && d.is_integer()
2975 && let Some(di) = d.to_integer().to_i64()
2976 && di.abs() <= MAX_GAMMA_SHIFT
2977 {
2978 g.push((arg, e, di));
2979 continue 'outer;
2980 }
2981 }
2982 groups.push(vec![(arg, e, 0)]);
2983 }
2984 let mut out = others;
2985 for g in groups {
2986 if g.len() == 1 {
2987 let (arg, e, _) = g[0].clone();
2988 let ga = arena.gamma(arg);
2989 out.push(pow_rat(arena, ga, &e));
2990 continue;
2991 }
2992 let min_off = g.iter().map(|(_, _, d)| *d).min().unwrap_or(0);
2993 let (ref_arg, _, _) = g[0];
2994 let x = add_rat(arena, ref_arg, &rat_i(min_off));
2996 let x = eval::eval(arena, x);
2997 let mut total_e = Rat::zero();
2998 for (_, e, d) in &g {
2999 total_e += e;
3000 let steps = d - min_off;
3001 for j in 0..steps {
3002 let lin = add_rat(arena, x, &rat_i(j));
3003 out.push(pow_rat(arena, lin, e));
3004 }
3005 }
3006 if !total_e.is_zero() {
3007 let gx = arena.gamma(x);
3008 out.push(pow_rat(arena, gx, &total_e));
3009 }
3010 }
3011 let r = mul_all(arena, &out);
3012 eval::eval(arena, r)
3013}
3014
3015fn gamma_to_factorial(arena: &mut Arena, expr: ExprId) -> ExprId {
3017 let factors = mul_factors(arena, expr);
3018 let mut out = Vec::with_capacity(factors.len());
3019 let mut changed = false;
3020 for f in factors {
3021 let (base, exp) = arena.as_base_exp(f);
3022 if let ExprNode::Gamma(arg) = arena.node(base).clone()
3023 && let Some(fa) = gamma_arg_to_factorial(arena, arg)
3024 {
3025 changed = true;
3026 let p = if exp == arena.one {
3027 fa
3028 } else {
3029 arena.pow(fa, exp)
3030 };
3031 out.push(p);
3032 } else {
3033 out.push(f);
3034 }
3035 }
3036 if changed {
3037 let r = mul_all(arena, &out);
3038 eval::eval(arena, r)
3039 } else {
3040 expr
3041 }
3042}
3043
3044fn gamma_arg_to_factorial(arena: &mut Arena, arg: ExprId) -> Option<ExprId> {
3045 if let Some(r) = as_rat(arena, arg) {
3046 if r.is_integer() && r.is_positive() {
3047 let m1 = rat_expr(arena, r - Rat::one());
3048 return Some(arena.factorial(m1));
3049 }
3050 return None;
3051 }
3052 let terms = add_terms(arena, arg);
3053 let mut const_part = Rat::zero();
3054 let mut rest = Vec::new();
3055 for t in terms {
3056 if let Some(r) = as_rat(arena, t) {
3057 const_part += r;
3058 } else {
3059 rest.push(t);
3060 }
3061 }
3062 if rest.is_empty() {
3063 return None;
3064 }
3065 let inner = add_all(arena, &rest);
3066 if const_part.is_integer() && const_part.is_positive() {
3067 let shifted = add_rat(arena, inner, &(const_part - Rat::one()));
3068 return Some(arena.factorial(shifted));
3069 }
3070 let half = Rat::new(BigInt::one(), BigInt::from(2));
3072 let m_shift = &const_part - ½
3073 if m_shift.is_integer() && !m_shift.is_negative() {
3074 let m = add_rat(arena, inner, &m_shift);
3075 let two = arena.int(2);
3076 let two_m = arena.mul(&[two, m]);
3077 let num = arena.factorial(two_m);
3078 let pi = arena.pi;
3079 let sp = arena.sqrt(pi);
3080 let two_m_e = arena.mul(&[two, m]);
3082 let four_m = arena.pow(two, two_m_e);
3083 let mf = arena.factorial(m);
3084 let inv_four_m = arena.pow(four_m, arena_neg_one(arena));
3085 let inv_mf = arena.pow(mf, arena_neg_one(arena));
3086 return Some(arena.mul(&[num, inv_four_m, inv_mf, sp]));
3087 }
3088 None
3089}
3090
3091fn infinite_product(arena: &mut Arena, body: ExprId, var: ExprId, lo: ExprId) -> SumOutcome {
3092 if !depends_on(arena, body, var) {
3093 let v = eval::eval(arena, body);
3094 if let Some(r) = as_rat(arena, v) {
3095 if r.is_one() {
3096 return SumOutcome::Closed(v);
3097 }
3098 if r.is_zero() || r.abs() < Rat::one() {
3099 return SumOutcome::Closed(arena.zero);
3100 }
3101 if r > Rat::one() {
3102 return SumOutcome::Divergent(Some(arena.infinity));
3103 }
3104 return SumOutcome::Divergent(None);
3105 }
3106 return SumOutcome::Unevaluated;
3107 }
3108 let n = arena.symbol("_N");
3109 let Some(pn) = product_closed(arena, body, var, lo, n) else {
3110 return SumOutcome::Unevaluated;
3111 };
3112 let pn = gamma_ratio_normalize(arena, pn);
3113 match rational_limit_at_infinity(arena, pn, n) {
3115 Some(Some(r)) => SumOutcome::Closed(rat_expr(arena, r)),
3116 Some(None) => {
3117 let sign = eventual_sign(arena, pn, n);
3118 SumOutcome::Divergent(infinity_of_sign(arena, sign))
3119 }
3120 None => {
3121 if let Some(l) = limit_at_infinity(arena, pn, n) {
3123 return SumOutcome::Closed(l);
3124 }
3125 SumOutcome::Unevaluated
3126 }
3127 }
3128}
3129
3130#[cfg(test)]
3135mod tests {
3136 use super::*;
3137
3138 fn closed(o: SumOutcome) -> ExprId {
3139 match o {
3140 SumOutcome::Closed(id) => id,
3141 other => panic!("expected closed form, got {other:?}"),
3142 }
3143 }
3144
3145 fn eval_at(arena: &mut Arena, e: ExprId, n: ExprId, v: i64) -> Rat {
3146 let ve = arena.int(v);
3147 let s = subs::subs(arena, e, n, ve);
3148 let s = eval::eval(arena, s);
3149 as_rat(arena, s).unwrap_or_else(|| panic!("not rational: {}", arena.display(s)))
3150 }
3151
3152 fn brute(arena: &mut Arena, body: ExprId, k: ExprId, lo: i64, hi: i64) -> Rat {
3153 let mut acc = Rat::zero();
3154 for i in lo..=hi {
3155 let ie = arena.int(i);
3156 let t = subs::subs(arena, body, k, ie);
3157 let t = eval::eval(arena, t);
3158 acc += as_rat(arena, t)
3159 .unwrap_or_else(|| panic!("term not rational: {}", arena.display(t)));
3160 }
3161 acc
3162 }
3163
3164 #[test]
3165 fn faulhaber_matches_enumeration_p_0_to_8() {
3166 let mut arena = Arena::new();
3167 let k = arena.symbol("k");
3168 let n = arena.symbol("n");
3169 let one = arena.one;
3170 for p in 0..=8usize {
3171 let body = if p == 0 {
3172 arena.one
3173 } else if p == 1 {
3174 k
3175 } else {
3176 let pe = arena.int(p as i64);
3177 arena.pow(k, pe)
3178 };
3179 let s = closed(summation(&mut arena, body, k, one, n));
3180 for nv in [0i64, 1, 2, 5, 10, 17] {
3181 let expected = brute(&mut arena, body, k, 1, nv);
3182 let got = eval_at(&mut arena, s, n, nv);
3183 assert_eq!(got, expected, "p={p}, n={nv}");
3184 }
3185 }
3186 }
3187
3188 #[test]
3189 fn faulhaber_general_lower_bound() {
3190 let mut arena = Arena::new();
3191 let k = arena.symbol("k");
3192 let n = arena.symbol("n");
3193 let three = arena.int(3);
3194 let e = arena.int(3);
3195 let body = arena.pow(k, e);
3196 let s = closed(summation(&mut arena, body, k, three, n));
3197 for nv in [3i64, 4, 9, 20] {
3198 let expected = brute(&mut arena, body, k, 3, nv);
3199 assert_eq!(eval_at(&mut arena, s, n, nv), expected);
3200 }
3201 }
3202
3203 #[test]
3204 fn faulhaber_b1_sign_convention() {
3205 let p = faulhaber_coefficients(1);
3207 assert_eq!(p.coeff(1), Rat::new(BigInt::from(1), BigInt::from(2)));
3208 assert_eq!(p.coeff(2), Rat::new(BigInt::from(1), BigInt::from(2)));
3209 }
3210
3211 #[test]
3212 fn telescoping_rational() {
3213 let mut arena = Arena::new();
3214 let k = arena.symbol("k");
3215 let n = arena.symbol("n");
3216 let one = arena.one;
3217 let k1 = arena.add(&[k, one]);
3218 let den = arena.mul(&[k, k1]);
3219 let body = arena.div(one, den);
3220 let s = closed(summation(&mut arena, body, k, one, n));
3221 for nv in [1i64, 2, 7, 30] {
3222 let expected = brute(&mut arena, body, k, 1, nv);
3223 assert_eq!(eval_at(&mut arena, s, n, nv), expected, "n={nv}");
3224 }
3225 let inf = arena.infinity;
3227 let s = closed(summation(&mut arena, body, k, one, inf));
3228 assert_eq!(as_rat(&arena, s), Some(Rat::one()));
3229 }
3230
3231 #[test]
3232 fn telescoping_shift_two() {
3233 let mut arena = Arena::new();
3234 let k = arena.symbol("k");
3235 let n = arena.symbol("n");
3236 let one = arena.one;
3237 let two = arena.int(2);
3238 let k2 = arena.add(&[k, two]);
3239 let den = arena.mul(&[k, k2]);
3240 let body = arena.div(one, den);
3241 let s = closed(summation(&mut arena, body, k, one, n));
3242 for nv in [1i64, 2, 5, 12] {
3243 let expected = brute(&mut arena, body, k, 1, nv);
3244 assert_eq!(eval_at(&mut arena, s, n, nv), expected, "n={nv}");
3245 }
3246 let inf = arena.infinity;
3247 let s = closed(summation(&mut arena, body, k, one, inf));
3248 assert_eq!(
3249 as_rat(&arena, s),
3250 Some(Rat::new(BigInt::from(3), BigInt::from(4)))
3251 );
3252 }
3253
3254 #[test]
3255 fn odd_reciprocals_product() {
3256 let mut arena = Arena::new();
3258 let k = arena.symbol("k");
3259 let n = arena.symbol("n");
3260 let one = arena.one;
3261 let two = arena.int(2);
3262 let m1 = arena.neg_one;
3263 let twok = arena.mul(&[two, k]);
3264 let a = arena.add(&[twok, m1]);
3265 let b = arena.add(&[twok, one]);
3266 let den = arena.mul(&[a, b]);
3267 let body = arena.div(one, den);
3268 let s = closed(summation(&mut arena, body, k, one, n));
3269 for nv in [1i64, 3, 8] {
3270 let expected = brute(&mut arena, body, k, 1, nv);
3271 assert_eq!(eval_at(&mut arena, s, n, nv), expected, "n={nv}");
3272 }
3273 let inf = arena.infinity;
3274 let s = closed(summation(&mut arena, body, k, one, inf));
3275 assert_eq!(
3276 as_rat(&arena, s),
3277 Some(Rat::new(BigInt::from(1), BigInt::from(2)))
3278 );
3279 }
3280
3281 #[test]
3282 fn harmonic_finite() {
3283 let mut arena = Arena::new();
3284 let k = arena.symbol("k");
3285 let n = arena.symbol("n");
3286 let one = arena.one;
3287 let body = arena.div(one, k);
3288 let s = closed(summation(&mut arena, body, k, one, n));
3289 assert_eq!(arena.display(s).to_string(), "harmonic(n)");
3290 assert_eq!(
3291 eval_at(&mut arena, s, n, 4),
3292 Rat::new(BigInt::from(25), BigInt::from(12))
3293 );
3294 }
3295
3296 #[test]
3297 fn harmonic_infinite_diverges() {
3298 let mut arena = Arena::new();
3299 let k = arena.symbol("k");
3300 let one = arena.one;
3301 let inf = arena.infinity;
3302 let body = arena.div(one, k);
3303 assert_eq!(
3304 summation(&mut arena, body, k, one, inf),
3305 SumOutcome::Divergent(Some(arena.infinity))
3306 );
3307 }
3308
3309 #[test]
3310 fn basel_and_friends() {
3311 let mut arena = Arena::new();
3312 let k = arena.symbol("k");
3313 let one = arena.one;
3314 let inf = arena.infinity;
3315 let pi = arena.pi;
3316 let m2 = arena.int(-2);
3318 let body = arena.pow(k, m2);
3319 let s = closed(summation(&mut arena, body, k, one, inf));
3320 let two = arena.int(2);
3321 let pi2 = arena.pow(pi, two);
3322 let six = arena.int(6);
3323 let expected = arena.div(pi2, six);
3324 assert_eq!(s, expected, "got {}", arena.display(s));
3325 let m4 = arena.int(-4);
3327 let body = arena.pow(k, m4);
3328 let s = closed(summation(&mut arena, body, k, one, inf));
3329 let four = arena.int(4);
3330 let pi4 = arena.pow(pi, four);
3331 let ninety = arena.int(90);
3332 let expected = arena.div(pi4, ninety);
3333 assert_eq!(s, expected, "got {}", arena.display(s));
3334 let m3 = arena.int(-3);
3336 let body = arena.pow(k, m3);
3337 let s = closed(summation(&mut arena, body, k, one, inf));
3338 assert_eq!(arena.display(s).to_string(), "zeta(3)");
3339 let two = arena.int(2);
3341 let two_k = arena.mul(&[two, k]);
3342 let odd = arena.add(&[two_k, one]);
3343 let m2 = arena.int(-2);
3344 let inv_sq = arena.pow(odd, m2);
3345 let neg_one = arena.neg_one;
3346 let alt = arena.pow(neg_one, k);
3347 let body = arena.mul(&[alt, inv_sq]);
3348 let zero = arena.zero;
3349 let s = closed(summation(&mut arena, body, k, zero, inf));
3350 assert_eq!(s, arena.catalan);
3351 }
3352
3353 #[test]
3354 fn zeta_even_rationals() {
3355 assert_eq!(
3356 zeta_even_rational(1),
3357 Rat::new(BigInt::from(1), BigInt::from(6))
3358 );
3359 assert_eq!(
3360 zeta_even_rational(2),
3361 Rat::new(BigInt::from(1), BigInt::from(90))
3362 );
3363 assert_eq!(
3364 zeta_even_rational(3),
3365 Rat::new(BigInt::from(1), BigInt::from(945))
3366 );
3367 assert_eq!(
3368 zeta_even_rational(4),
3369 Rat::new(BigInt::from(1), BigInt::from(9450))
3370 );
3371 }
3372
3373 #[test]
3374 fn euler_numbers() {
3375 assert_eq!(euler_number(0), BigInt::from(1));
3376 assert_eq!(euler_number(1), BigInt::from(-1));
3377 assert_eq!(euler_number(2), BigInt::from(5));
3378 assert_eq!(euler_number(3), BigInt::from(-61));
3379 assert_eq!(euler_number(4), BigInt::from(1385));
3380 }
3381
3382 #[test]
3383 fn alternating_constants() {
3384 let mut arena = Arena::new();
3385 let k = arena.symbol("k");
3386 let one = arena.one;
3387 let zero = arena.zero;
3388 let inf = arena.infinity;
3389 let m1 = arena.neg_one;
3390 let k1 = arena.add(&[k, one]);
3392 let sgn = arena.pow(m1, k1);
3393 let body = arena.div(sgn, k);
3394 let s = closed(summation(&mut arena, body, k, one, inf));
3395 assert_eq!(arena.display(s).to_string(), "ln(2)");
3396 let two = arena.int(2);
3398 let twok1 = arena.mul(&[two, k]);
3399 let twok1 = arena.add(&[twok1, one]);
3400 let sgn = arena.pow(m1, k);
3401 let body = arena.div(sgn, twok1);
3402 let s = closed(summation(&mut arena, body, k, zero, inf));
3403 let pi = arena.pi;
3404 let four = arena.int(4);
3405 let expected = arena.div(pi, four);
3406 assert_eq!(s, expected, "got {}", arena.display(s));
3407 let k1 = arena.add(&[k, one]);
3409 let sgn = arena.pow(m1, k1);
3410 let m2 = arena.int(-2);
3411 let k2 = arena.pow(k, m2);
3412 let body = arena.mul(&[sgn, k2]);
3413 let s = closed(summation(&mut arena, body, k, one, inf));
3414 let pi2 = arena.pow(pi, two);
3415 let twelve = arena.int(12);
3416 let expected = arena.div(pi2, twelve);
3417 assert_eq!(s, expected, "got {}", arena.display(s));
3418 let m2 = arena.int(-2);
3420 let body = arena.pow(twok1, m2);
3421 let s = closed(summation(&mut arena, body, k, zero, inf));
3422 let eight = arena.int(8);
3423 let expected = arena.div(pi2, eight);
3424 assert_eq!(s, expected, "got {}", arena.display(s));
3425 }
3426
3427 #[test]
3428 fn geometric_finite_and_infinite() {
3429 let mut arena = Arena::new();
3430 let k = arena.symbol("k");
3431 let n = arena.symbol("n");
3432 let zero = arena.zero;
3433 let inf = arena.infinity;
3434 let half = arena.rational(1, 2);
3435 let body = arena.pow(half, k);
3436 let s = closed(summation(&mut arena, body, k, zero, n));
3437 for nv in [0i64, 1, 5, 9] {
3438 let expected = brute(&mut arena, body, k, 0, nv);
3439 assert_eq!(eval_at(&mut arena, s, n, nv), expected);
3440 }
3441 let s = closed(summation(&mut arena, body, k, zero, inf));
3442 assert_eq!(as_rat(&arena, s), Some(rat_i(2)));
3443 let body2 = arena.mul(&[k, body]);
3445 let s = closed(summation(&mut arena, body2, k, zero, inf));
3446 assert_eq!(
3447 as_rat(&arena, s),
3448 Some(rat_i(2)),
3449 "got {}",
3450 arena.display(s)
3451 );
3452 let two = arena.int(2);
3454 let k2 = arena.pow(k, two);
3455 let body3 = arena.mul(&[k2, body]);
3456 let s = closed(summation(&mut arena, body3, k, zero, inf));
3457 assert_eq!(
3458 as_rat(&arena, s),
3459 Some(rat_i(6)),
3460 "got {}",
3461 arena.display(s)
3462 );
3463 let body4 = arena.pow(two, k);
3465 assert_eq!(
3466 summation(&mut arena, body4, k, zero, inf),
3467 SumOutcome::Divergent(Some(arena.infinity))
3468 );
3469 }
3470
3471 #[test]
3472 fn arithmetico_geometric_symbolic_ratio() {
3473 let mut arena = Arena::new();
3474 let k = arena.symbol("k");
3475 let n = arena.symbol("n");
3476 let r = arena.symbol("r");
3477 let zero = arena.zero;
3478 let rk = arena.pow(r, k);
3479 let body = arena.mul(&[k, rk]);
3480 let s = closed(summation(&mut arena, body, k, zero, n));
3481 assert!(
3482 matches!(arena.node(s), ExprNode::Piecewise(_)),
3483 "{}",
3484 arena.display(s)
3485 );
3486 let three = arena.int(3);
3488 let four = arena.int(4);
3489 let v = subs::subs(&mut arena, s, r, three);
3490 let v = subs::subs(&mut arena, v, n, four);
3491 let v = eval::eval(&mut arena, v);
3492 assert_eq!(
3493 as_rat(&arena, v),
3494 Some(rat_i(426)),
3495 "got {}",
3496 arena.display(v)
3497 );
3498 let one = arena.one;
3500 let v = subs::subs(&mut arena, s, r, one);
3501 let v = subs::subs(&mut arena, v, n, four);
3502 let v = eval::eval(&mut arena, v);
3503 assert_eq!(
3504 as_rat(&arena, v),
3505 Some(rat_i(10)),
3506 "got {}",
3507 arena.display(v)
3508 );
3509 }
3510
3511 #[test]
3512 fn binomial_identities() {
3513 let mut arena = Arena::new();
3514 let k = arena.symbol("k");
3515 let n = arena.symbol("n");
3516 let zero = arena.zero;
3517 let bin = arena.binomial(n, k);
3518 let s = closed(summation(&mut arena, bin, k, zero, n));
3519 let two = arena.int(2);
3520 assert_eq!(s, arena.pow(two, n), "got {}", arena.display(s));
3521 let body = arena.mul(&[k, bin]);
3522 let s = closed(summation(&mut arena, body, k, zero, n));
3523 for nv in [1i64, 2, 5, 8] {
3524 let nve = arena.int(nv);
3525 let body_n = subs::subs(&mut arena, body, n, nve);
3526 let expected = brute(&mut arena, body_n, k, 0, nv);
3527 assert_eq!(eval_at(&mut arena, s, n, nv), expected);
3528 }
3529 let body = arena.pow(bin, two);
3530 let s = closed(summation(&mut arena, body, k, zero, n));
3531 for nv in [0i64, 1, 3, 6] {
3532 let nve = arena.int(nv);
3533 let body_n = subs::subs(&mut arena, body, n, nve);
3534 let expected = brute(&mut arena, body_n, k, 0, nv);
3535 assert_eq!(eval_at(&mut arena, s, n, nv), expected);
3536 }
3537 let x = arena.symbol("x");
3538 let xk = arena.pow(x, k);
3539 let body = arena.mul(&[bin, xk]);
3540 let s = closed(summation(&mut arena, body, k, zero, n));
3541 let one = arena.one;
3542 let opx = arena.add(&[one, x]);
3543 assert_eq!(s, arena.pow(opx, n), "got {}", arena.display(s));
3544 let m1 = arena.neg_one;
3545 let sgn = arena.pow(m1, k);
3546 let body = arena.mul(&[bin, sgn]);
3547 let s = closed(summation(&mut arena, body, k, zero, n));
3548 assert_eq!(eval_at(&mut arena, s, n, 0), Rat::one());
3549 assert_eq!(eval_at(&mut arena, s, n, 5), Rat::zero());
3550 }
3551
3552 #[test]
3553 fn power_series_table() {
3554 let mut arena = Arena::new();
3555 let k = arena.symbol("k");
3556 let x = arena.symbol("x");
3557 let zero = arena.zero;
3558 let one = arena.one;
3559 let inf = arena.infinity;
3560 let kf = arena.factorial(k);
3561 let xk = arena.pow(x, k);
3562 let body = arena.div(xk, kf);
3563 let s = closed(summation(&mut arena, body, k, zero, inf));
3564 assert_eq!(s, arena.exp(x), "got {}", arena.display(s));
3565 let two = arena.int(2);
3567 let m1 = arena.neg_one;
3568 let twok1 = arena.mul(&[two, k]);
3569 let twok1 = arena.add(&[twok1, one]);
3570 let sgn = arena.pow(m1, k);
3571 let xp = arena.pow(x, twok1);
3572 let f = arena.factorial(twok1);
3573 let num = arena.mul(&[sgn, xp]);
3574 let body = arena.div(num, f);
3575 let s = closed(summation(&mut arena, body, k, zero, inf));
3576 assert_eq!(s, arena.sin(x), "got {}", arena.display(s));
3577 let twok = arena.mul(&[two, k]);
3579 let xp = arena.pow(x, twok);
3580 let f = arena.factorial(twok);
3581 let body = arena.div(xp, f);
3582 let s = closed(summation(&mut arena, body, k, zero, inf));
3583 assert_eq!(s, arena.cosh(x), "got {}", arena.display(s));
3584 let half = arena.rational(1, 2);
3586 let hk = arena.pow(half, k);
3587 let body = arena.div(hk, k);
3588 let s = closed(summation(&mut arena, body, k, one, inf));
3589 assert_eq!(arena.display(s).to_string(), "ln(2)");
3590 let body = arena.div(xk, k);
3592 assert_eq!(
3593 summation(&mut arena, body, k, one, inf),
3594 SumOutcome::Unevaluated
3595 );
3596 let body = arena.div(one, kf);
3598 let s = closed(summation(&mut arena, body, k, zero, inf));
3599 assert_eq!(s, arena.e_const, "got {}", arena.display(s));
3600 let body = arena.mul(&[k, xk]);
3602 let body = arena.div(body, kf);
3603 let s = closed(summation(&mut arena, body, k, zero, inf));
3604 let ex = arena.exp(x);
3605 let expected = arena.mul(&[x, ex]);
3606 assert_eq!(s, expected, "got {}", arena.display(s));
3607 let body = arena.div(xk, kf);
3609 let s = closed(summation(&mut arena, body, k, one, inf));
3610 let expected = arena.sub(ex, one);
3611 assert_eq!(s, expected, "got {}", arena.display(s));
3612 }
3613
3614 #[test]
3615 fn gosper_fallback_k_factorial() {
3616 let mut arena = Arena::new();
3617 let k = arena.symbol("k");
3618 let n = arena.symbol("n");
3619 let zero = arena.zero;
3620 let kf = arena.factorial(k);
3621 let body = arena.mul(&[k, kf]);
3622 let s = closed(summation(&mut arena, body, k, zero, n));
3623 for nv in [0i64, 1, 3, 5] {
3624 let expected = brute(&mut arena, body, k, 0, nv);
3625 assert_eq!(eval_at(&mut arena, s, n, nv), expected);
3626 }
3627 }
3628
3629 #[test]
3630 fn linearity_partial_keeps_unevaluated_sum() {
3631 let mut arena = Arena::new();
3632 let k = arena.symbol("k");
3633 let n = arena.symbol("n");
3634 let one = arena.one;
3635 let sk = arena.sin(k);
3636 let body = arena.add(&[k, sk]);
3637 let s = closed(summation(&mut arena, body, k, one, n));
3638 assert!(walk::has_unevaluated(&arena, s));
3639 assert!(arena.display(s).to_string().contains("Sum"));
3640 }
3641
3642 #[test]
3643 fn products_basic() {
3644 let mut arena = Arena::new();
3645 let k = arena.symbol("k");
3646 let n = arena.symbol("n");
3647 let one = arena.one;
3648 let two = arena.int(2);
3649 let p = closed(product(&mut arena, k, k, one, n));
3651 assert_eq!(p, arena.factorial(n), "got {}", arena.display(p));
3652 let body = arena.mul(&[two, k]);
3654 let p = closed(product(&mut arena, body, k, one, n));
3655 for nv in [1i64, 2, 4, 6] {
3656 let expected: Rat = (1..=nv).map(|i| rat_i(2 * i)).product();
3657 assert_eq!(eval_at(&mut arena, p, n, nv), expected);
3658 }
3659 let inv = arena.div(one, k);
3661 let body = arena.add(&[one, inv]);
3662 let p = closed(product(&mut arena, body, k, one, n));
3663 let expected = arena.add(&[n, one]);
3664 assert_eq!(p, expected, "got {}", arena.display(p));
3665 let m2 = arena.int(-2);
3667 let k2 = arena.pow(k, m2);
3668 let body = arena.sub(one, k2);
3669 let p = closed(product(&mut arena, body, k, two, n));
3670 for nv in [2i64, 3, 5, 9] {
3671 let expected = Rat::new(BigInt::from(nv + 1), BigInt::from(2 * nv));
3672 assert_eq!(
3673 eval_at(&mut arena, p, n, nv),
3674 expected,
3675 "got {}",
3676 arena.display(p)
3677 );
3678 }
3679 let inf = arena.infinity;
3680 let p = closed(product(&mut arena, body, k, two, inf));
3681 assert_eq!(
3682 as_rat(&arena, p),
3683 Some(Rat::new(BigInt::from(1), BigInt::from(2)))
3684 );
3685 let m1 = arena.neg_one;
3687 let twok = arena.mul(&[two, k]);
3688 let body = arena.add(&[twok, m1]);
3689 let p = closed(product(&mut arena, body, k, one, n));
3690 for nv in [1i64, 2, 3, 5] {
3691 let expected: Rat = (1..=nv).map(|i| rat_i(2 * i - 1)).product();
3692 assert_eq!(
3693 eval_at(&mut arena, p, n, nv),
3694 expected,
3695 "got {}",
3696 arena.display(p)
3697 );
3698 }
3699 let a = arena.symbol("a");
3701 let body = arena.pow(a, k);
3702 let p = closed(product(&mut arena, body, k, one, n));
3703 let three = arena.int(3);
3704 let v = subs::subs(&mut arena, p, n, three);
3705 let v = eval::eval(&mut arena, v);
3706 let six = arena.int(6);
3707 assert_eq!(v, arena.pow(a, six), "got {}", arena.display(v));
3708 }
3709}