1use crate::kernel::{ExprData, ExprId, ExprPool};
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub enum ZTransformError {
91 NoRule(String),
93 NotInvertible(String),
96 SameVariable,
99}
100
101impl std::fmt::Display for ZTransformError {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 match self {
104 ZTransformError::NoRule(m) => {
105 write!(f, "z_transform: no rule for {m} [E-TRANSFORM-101]")
106 }
107 ZTransformError::NotInvertible(m) => write!(
108 f,
109 "inverse_z_transform: cannot invert {m} [E-TRANSFORM-102]"
110 ),
111 ZTransformError::SameVariable => write!(
112 f,
113 "z_transform: index and frequency variables must differ [E-TRANSFORM-103]"
114 ),
115 }
116 }
117}
118
119impl std::error::Error for ZTransformError {}
120
121fn is_free_of(expr: ExprId, var: ExprId, pool: &ExprPool) -> bool {
126 crate::integrate::risch::poly_rde::is_free_of_var(expr, var, pool)
127}
128
129fn as_affine(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<(ExprId, ExprId)> {
132 if expr == var {
133 return Some((pool.integer(1_i32), pool.integer(0_i32)));
134 }
135 if is_free_of(expr, var, pool) {
136 return Some((pool.integer(0_i32), expr));
137 }
138 match pool.get(expr) {
139 ExprData::Mul(_) => {
140 let (a, b) = as_affine_term(expr, var, pool)?;
141 if b == pool.integer(0_i32) {
142 Some((a, pool.integer(0_i32)))
143 } else {
144 None
145 }
146 }
147 ExprData::Add(args) => {
148 let mut a_acc: Vec<ExprId> = Vec::new();
149 let mut b_acc: Vec<ExprId> = Vec::new();
150 for arg in args {
151 if is_free_of(arg, var, pool) {
152 b_acc.push(arg);
153 } else {
154 let (a, b) = as_affine_term(arg, var, pool)?;
155 if b != pool.integer(0_i32) {
156 return None;
157 }
158 a_acc.push(a);
159 }
160 }
161 let a = match a_acc.len() {
162 0 => pool.integer(0_i32),
163 1 => a_acc[0],
164 _ => pool.add(a_acc),
165 };
166 let b = match b_acc.len() {
167 0 => pool.integer(0_i32),
168 1 => b_acc[0],
169 _ => pool.add(b_acc),
170 };
171 Some((a, b))
172 }
173 _ => None,
174 }
175}
176
177fn as_affine_term(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<(ExprId, ExprId)> {
178 if expr == var {
179 return Some((pool.integer(1_i32), pool.integer(0_i32)));
180 }
181 if is_free_of(expr, var, pool) {
182 return Some((pool.integer(0_i32), expr));
183 }
184 if let ExprData::Mul(args) = pool.get(expr) {
185 let pos = args.iter().position(|&a| a == var)?;
186 let others: Vec<ExprId> = args
187 .iter()
188 .enumerate()
189 .filter(|&(i, _)| i != pos)
190 .map(|(_, &a)| a)
191 .collect();
192 if others.iter().all(|&o| is_free_of(o, var, pool)) {
193 let coeff = match others.len() {
194 0 => pool.integer(1_i32),
195 1 => others[0],
196 _ => pool.mul(others),
197 };
198 return Some((coeff, pool.integer(0_i32)));
199 }
200 }
201 None
202}
203
204fn simp(expr: ExprId, pool: &ExprPool) -> ExprId {
205 crate::simplify::simplify(expr, pool).value
206}
207
208fn neg(expr: ExprId, pool: &ExprPool) -> ExprId {
209 pool.mul(vec![pool.integer(-1_i32), expr])
210}
211
212fn recip(expr: ExprId, pool: &ExprPool) -> ExprId {
213 pool.pow(expr, pool.integer(-1_i32))
214}
215
216fn subs_one(expr: ExprId, from: ExprId, to: ExprId, pool: &ExprPool) -> ExprId {
218 let mut map = std::collections::HashMap::new();
219 map.insert(from, to);
220 crate::kernel::subs(expr, &map, pool)
221}
222
223fn remove_index(factors: &[ExprId], idx: usize, pool: &ExprPool) -> ExprId {
226 let rest: Vec<ExprId> = factors
227 .iter()
228 .enumerate()
229 .filter(|&(i, _)| i != idx)
230 .map(|(_, &f)| f)
231 .collect();
232 match rest.len() {
233 0 => pool.integer(1_i32),
234 1 => rest[0],
235 _ => pool.mul(rest),
236 }
237}
238
239fn nonneg_int_exp(exp: ExprId, pool: &ExprPool) -> Option<u64> {
241 if let ExprData::Integer(n) = pool.get(exp) {
242 let n = n.0;
243 if n >= 0 {
244 return n.to_u64();
245 }
246 }
247 None
248}
249
250const MAX_DEPTH: usize = 32;
255
256pub fn z_transform(
290 a: ExprId,
291 n: ExprId,
292 z: ExprId,
293 pool: &ExprPool,
294) -> Result<ExprId, ZTransformError> {
295 if n == z {
296 return Err(ZTransformError::SameVariable);
297 }
298 let out = z_inner(a, n, z, pool, 0)?;
299 Ok(simp(out, pool))
300}
301
302fn z_inner(
303 a: ExprId,
304 n: ExprId,
305 z: ExprId,
306 pool: &ExprPool,
307 depth: usize,
308) -> Result<ExprId, ZTransformError> {
309 if depth > MAX_DEPTH {
310 return Err(ZTransformError::NoRule("recursion depth exceeded".into()));
311 }
312
313 if is_free_of(a, n, pool) {
315 return Ok(pool.mul(vec![a, geometric_transform(pool.integer(1_i32), z, pool)]));
316 }
317
318 if a == n {
320 return Ok(ramp_transform(z, pool));
321 }
322
323 match pool.get(a) {
324 ExprData::Add(args) => {
326 let mut terms = Vec::with_capacity(args.len());
327 for arg in args {
328 terms.push(z_inner(arg, n, z, pool, depth + 1)?);
329 }
330 Ok(pool.add(terms))
331 }
332
333 ExprData::Mul(args) => z_mul(&args, n, z, pool, depth),
336
337 ExprData::Pow { base, exp } if base == n => {
339 if nonneg_int_exp(exp, pool) == Some(2) {
340 Ok(quadratic_ramp_transform(z, pool))
341 } else {
342 Err(ZTransformError::NoRule(format!(
343 "n^e (only n and n^2 are tabulated): {}",
344 pool.display(a)
345 )))
346 }
347 }
348
349 ExprData::Pow { base, exp } if exp == n => {
351 if is_free_of(base, n, pool) {
352 Ok(geometric_transform(base, z, pool))
353 } else {
354 Err(ZTransformError::NoRule(format!(
355 "base^n with base depending on n: {}",
356 pool.display(a)
357 )))
358 }
359 }
360
361 ExprData::Func { name, args } if args.len() == 1 => z_func(&name, args[0], n, z, pool),
362
363 _ => Err(ZTransformError::NoRule(pool.display(a).to_string())),
364 }
365}
366
367fn geometric_transform(base: ExprId, z: ExprId, pool: &ExprPool) -> ExprId {
369 let denom = pool.add(vec![z, neg(base, pool)]);
370 pool.mul(vec![z, recip(denom, pool)])
371}
372
373fn ramp_transform(z: ExprId, pool: &ExprPool) -> ExprId {
375 let denom = pool.pow(pool.add(vec![z, pool.integer(-1_i32)]), pool.integer(2_i32));
376 pool.mul(vec![z, recip(denom, pool)])
377}
378
379fn quadratic_ramp_transform(z: ExprId, pool: &ExprPool) -> ExprId {
381 let numer = pool.mul(vec![z, pool.add(vec![z, pool.integer(1_i32)])]);
382 let denom = pool.pow(pool.add(vec![z, pool.integer(-1_i32)]), pool.integer(3_i32));
383 pool.mul(vec![numer, recip(denom, pool)])
384}
385
386fn z_mul(
388 args: &[ExprId],
389 n: ExprId,
390 z: ExprId,
391 pool: &ExprPool,
392 depth: usize,
393) -> Result<ExprId, ZTransformError> {
394 let (consts, rest): (Vec<ExprId>, Vec<ExprId>) =
396 args.iter().partition(|&&a| is_free_of(a, n, pool));
397 let scalar = match consts.len() {
398 0 => None,
399 1 => Some(consts[0]),
400 _ => Some(pool.mul(consts.clone())),
401 };
402
403 let inner = match rest.len() {
404 0 => {
405 let c = scalar.unwrap_or_else(|| pool.integer(1_i32));
406 return Ok(pool.mul(vec![c, geometric_transform(pool.integer(1_i32), z, pool)]));
407 }
408 1 => rest[0],
409 _ => pool.mul(rest.clone()),
410 };
411
412 let transformed = z_product_body(inner, n, z, pool, depth)?;
413 Ok(match scalar {
414 Some(c) => pool.mul(vec![c, transformed]),
415 None => transformed,
416 })
417}
418
419fn z_product_body(
423 body: ExprId,
424 n: ExprId,
425 z: ExprId,
426 pool: &ExprPool,
427 depth: usize,
428) -> Result<ExprId, ZTransformError> {
429 let factors: Vec<ExprId> = match pool.get(body) {
430 ExprData::Mul(a) => a,
431 _ => vec![body],
432 };
433
434 for (i, &fac) in factors.iter().enumerate() {
436 if let Some(a) = match_geometric(fac, n, pool) {
437 let rest = remove_index(&factors, i, pool);
438 let x_transform = z_inner(rest, n, z, pool, depth + 1)?;
439 let z_over_a = simp(pool.mul(vec![z, recip(a, pool)]), pool);
440 return Ok(subs_one(x_transform, z, z_over_a, pool));
441 }
442 }
443
444 for (i, &fac) in factors.iter().enumerate() {
446 if let Some(k) = match_n_power(fac, n, pool) {
447 let rest = remove_index(&factors, i, pool);
448 let mut x_transform = z_inner(rest, n, z, pool, depth + 1)?;
449 for _ in 0..k {
450 let dxdz = crate::diff::diff(x_transform, z, pool)
451 .map_err(|_| ZTransformError::NoRule("differentiation theorem failed".into()))?
452 .value;
453 x_transform = simp(pool.mul(vec![pool.integer(-1_i32), z, dxdz]), pool);
454 }
455 return Ok(x_transform);
456 }
457 }
458
459 if !matches!(pool.get(body), ExprData::Mul(_)) {
465 return z_inner(body, n, z, pool, depth + 1);
466 }
467
468 Err(ZTransformError::NoRule(pool.display(body).to_string()))
469}
470
471fn match_geometric(fac: ExprId, n: ExprId, pool: &ExprPool) -> Option<ExprId> {
473 if let ExprData::Pow { base, exp } = pool.get(fac) {
474 if exp == n && is_free_of(base, n, pool) {
475 return Some(base);
476 }
477 }
478 None
479}
480
481fn match_n_power(fac: ExprId, n: ExprId, pool: &ExprPool) -> Option<u64> {
483 if fac == n {
484 return Some(1);
485 }
486 if let ExprData::Pow { base, exp } = pool.get(fac) {
487 if base == n {
488 return nonneg_int_exp(exp, pool).filter(|&k| k >= 1);
489 }
490 }
491 None
492}
493
494fn z_func(
496 name: &str,
497 arg: ExprId,
498 n: ExprId,
499 z: ExprId,
500 pool: &ExprPool,
501) -> Result<ExprId, ZTransformError> {
502 if matches!(name, "sin" | "cos") {
503 let (omega, off) = as_affine(arg, n, pool).ok_or_else(|| {
504 ZTransformError::NoRule(format!(
505 "{name} of non-affine argument: {}",
506 pool.display(arg)
507 ))
508 })?;
509 if off != pool.integer(0_i32) || omega == pool.integer(0_i32) {
510 return Err(ZTransformError::NoRule(format!(
511 "{name}(ω n): argument must be a nonzero multiple of n"
512 )));
513 }
514 let cos_w = pool.func("cos", vec![omega]);
515 let sin_w = pool.func("sin", vec![omega]);
516 let z2 = pool.pow(z, pool.integer(2_i32));
517 let two_z_cos = pool.mul(vec![pool.integer(2_i32), z, cos_w]);
518 let denom = pool.add(vec![z2, neg(two_z_cos, pool), pool.integer(1_i32)]);
520 return Ok(match name {
521 "sin" => {
523 let numer = pool.mul(vec![z, sin_w]);
524 pool.mul(vec![numer, recip(denom, pool)])
525 }
526 "cos" => {
528 let z_minus_cos = pool.add(vec![z, neg(cos_w, pool)]);
529 let numer = pool.mul(vec![z, z_minus_cos]);
530 pool.mul(vec![numer, recip(denom, pool)])
531 }
532 _ => unreachable!(),
533 });
534 }
535
536 Err(ZTransformError::NoRule(format!("{name}(...)")))
537}
538
539pub fn z_shift_delay(x_transform: ExprId, z: ExprId, k: u32, pool: &ExprPool) -> ExprId {
555 if k == 0 {
556 return x_transform;
557 }
558 let z_neg_k = pool.pow(z, pool.integer(-(k as i64)));
559 simp(pool.mul(vec![z_neg_k, x_transform]), pool)
560}
561
562pub fn z_shift_advance(
579 x_transform: ExprId,
580 z: ExprId,
581 order: u32,
582 initial_values: &[ExprId],
583 pool: &ExprPool,
584) -> ExprId {
585 let z_m = pool.pow(z, pool.integer(order as i64));
587 let mut terms = vec![pool.mul(vec![z_m, x_transform])];
588 for k in 0..order {
590 let xk = initial_values
591 .get(k as usize)
592 .copied()
593 .unwrap_or_else(|| pool.integer(0_i32));
594 if xk == pool.integer(0_i32) {
595 continue;
596 }
597 let power = (order - k) as i64;
598 let z_pow = pool.pow(z, pool.integer(power));
599 terms.push(pool.mul(vec![pool.integer(-1_i32), z_pow, xk]));
600 }
601 simp(pool.add(terms), pool)
602}
603
604pub fn inverse_z_transform(
622 big_x: ExprId,
623 z: ExprId,
624 n: ExprId,
625 pool: &ExprPool,
626) -> Result<ExprId, ZTransformError> {
627 if z == n {
628 return Err(ZTransformError::SameVariable);
629 }
630
631 let x_over_z = simp(pool.mul(vec![big_x, recip(z, pool)]), pool);
633 let pf = crate::poly::apart(x_over_z, z, pool)
634 .map_err(|e| ZTransformError::NotInvertible(format!("apart failed: {e}")))?;
635
636 let pf_terms: Vec<ExprId> = match pool.get(pf) {
637 ExprData::Add(args) => args,
638 _ => vec![pf],
639 };
640
641 let mut out = Vec::with_capacity(pf_terms.len());
642 for term in pf_terms {
643 let term_z = simp(pool.mul(vec![term, z]), pool);
645 out.push(invert_term(term_z, z, n, pool)?);
646 }
647 Ok(simp(pool.add(out), pool))
648}
649
650fn invert_term(
653 term: ExprId,
654 z: ExprId,
655 n: ExprId,
656 pool: &ExprPool,
657) -> Result<ExprId, ZTransformError> {
658 let (numer, base, k) = split_rational_term(term, pool)
659 .ok_or_else(|| ZTransformError::NotInvertible(pool.display(term).to_string()))?;
660
661 if k == 0 {
665 return Err(ZTransformError::NotInvertible(format!(
666 "constant term {} (Kronecker delta δ[n] — no discrete-impulse primitive)",
667 pool.display(term)
668 )));
669 }
670
671 if poly_degree(base, z, pool) == Some(2) {
676 return invert_quadratic_pole(numer, base, k, z, n, pool);
677 }
678
679 let (coeff, p) = split_z_power(numer, z, pool).ok_or_else(|| {
680 ZTransformError::NotInvertible(format!(
681 "linear-pole numerator not of the form A·z^p: {}",
682 pool.display(numer)
683 ))
684 })?;
685 if p != 1 {
686 return Err(ZTransformError::NotInvertible(format!(
687 "numerator power of z ({p}) not in the table (expected A·z)"
688 )));
689 }
690
691 match poly_degree(base, z, pool) {
692 Some(1) => invert_linear_pole(coeff, base, k, z, n, pool),
693 Some(d) => Err(ZTransformError::NotInvertible(format!(
694 "denominator factor of degree {d} (only linear poles are tabulated): {}",
695 pool.display(base)
696 ))),
697 None => Err(ZTransformError::NotInvertible(
698 pool.display(base).to_string(),
699 )),
700 }
701}
702
703fn split_z_power(numer: ExprId, z: ExprId, pool: &ExprPool) -> Option<(ExprId, u64)> {
706 if numer == z {
707 return Some((pool.integer(1_i32), 1));
708 }
709 if is_free_of(numer, z, pool) {
710 return Some((numer, 0));
711 }
712 match pool.get(numer) {
713 ExprData::Pow { base, exp } if base == z => {
714 nonneg_int_exp(exp, pool).map(|p| (pool.integer(1_i32), p))
715 }
716 ExprData::Mul(args) => {
717 let mut coeff_parts: Vec<ExprId> = Vec::new();
718 let mut p = 0u64;
719 for a in args {
720 if a == z {
721 p += 1;
722 continue;
723 }
724 if let ExprData::Pow { base, exp } = pool.get(a) {
725 if base == z {
726 p += nonneg_int_exp(exp, pool)?;
727 continue;
728 }
729 }
730 if !is_free_of(a, z, pool) {
731 return None;
732 }
733 coeff_parts.push(a);
734 }
735 let coeff = match coeff_parts.len() {
736 0 => pool.integer(1_i32),
737 1 => coeff_parts[0],
738 _ => pool.mul(coeff_parts),
739 };
740 Some((coeff, p))
741 }
742 _ => None,
743 }
744}
745
746fn split_rational_term(term: ExprId, pool: &ExprPool) -> Option<(ExprId, ExprId, u64)> {
750 let factors: Vec<ExprId> = match pool.get(term) {
751 ExprData::Mul(a) => a,
752 _ => vec![term],
753 };
754 let mut numer_parts: Vec<ExprId> = Vec::new();
755 let mut base: Option<ExprId> = None;
756 let mut k: u64 = 0;
757
758 for &fac in &factors {
759 if let ExprData::Pow { base: b, exp } = pool.get(fac) {
760 if let ExprData::Integer(e) = pool.get(exp) {
761 let ev = e.0;
762 if ev < 0 {
763 if base.is_some() && base != Some(b) {
764 return None;
765 }
766 base = Some(b);
767 k = (-ev).to_u64()?;
768 continue;
769 }
770 }
771 }
772 numer_parts.push(fac);
773 }
774
775 let numer = match numer_parts.len() {
776 0 => pool.integer(1_i32),
777 1 => numer_parts[0],
778 _ => pool.mul(numer_parts),
779 };
780 match base {
781 Some(b) => Some((numer, b, k)),
782 None => Some((numer, pool.integer(1_i32), 0)),
783 }
784}
785
786fn poly_degree(base: ExprId, z: ExprId, pool: &ExprPool) -> Option<u64> {
789 if base == z {
790 return Some(1);
791 }
792 match pool.get(base) {
793 ExprData::Add(args) => {
794 let mut deg = 0u64;
795 for a in args {
796 deg = deg.max(monomial_degree(a, z, pool)?);
797 }
798 Some(deg)
799 }
800 ExprData::Pow { .. } | ExprData::Mul(_) => monomial_degree(base, z, pool),
801 _ if is_free_of(base, z, pool) => Some(0),
802 _ => None,
803 }
804}
805
806fn monomial_degree(term: ExprId, z: ExprId, pool: &ExprPool) -> Option<u64> {
807 if term == z {
808 return Some(1);
809 }
810 if is_free_of(term, z, pool) {
811 return Some(0);
812 }
813 match pool.get(term) {
814 ExprData::Pow { base, exp } if base == z => nonneg_int_exp(exp, pool),
815 ExprData::Mul(args) => {
816 let mut deg = 0u64;
817 for a in args {
818 deg += monomial_degree(a, z, pool)?;
819 }
820 Some(deg)
821 }
822 _ => None,
823 }
824}
825
826fn invert_linear_pole(
833 numer: ExprId,
834 base: ExprId,
835 k: u64,
836 z: ExprId,
837 n: ExprId,
838 pool: &ExprPool,
839) -> Result<ExprId, ZTransformError> {
840 let (coeff, b) = as_affine(base, z, pool)
844 .ok_or_else(|| ZTransformError::NotInvertible(pool.display(base).to_string()))?;
845 if coeff != pool.integer(1_i32) {
846 return Err(ZTransformError::NotInvertible(
847 "non-monic linear denominator".into(),
848 ));
849 }
850 let a = simp(neg(b, pool), pool); match k {
853 1 => {
854 if a == pool.integer(0_i32) {
855 return Err(ZTransformError::NotInvertible(
858 "A·z/z term reduces to a Kronecker delta (no discrete-impulse primitive)"
859 .into(),
860 ));
861 }
862 if a == pool.integer(1_i32) {
863 return Ok(numer);
865 }
866 let a_pow_n = pool.pow(a, n);
868 Ok(pool.mul(vec![numer, a_pow_n]))
869 }
870 2 => {
871 if a == pool.integer(0_i32) {
872 return Err(ZTransformError::NotInvertible(
873 "A·z/z² term has no causal-sequence inverse in the table".into(),
874 ));
875 }
876 let coeff = simp(pool.mul(vec![numer, recip(a, pool)]), pool);
878 let a_pow_n = pool.pow(a, n);
879 Ok(pool.mul(vec![coeff, n, a_pow_n]))
880 }
881 _ => Err(ZTransformError::NotInvertible(format!(
882 "repeated linear pole of order {k} (only k = 1, 2 are tabulated)"
883 ))),
884 }
885}
886
887fn invert_quadratic_pole(
903 numer: ExprId,
904 base: ExprId,
905 k: u64,
906 z: ExprId,
907 n: ExprId,
908 pool: &ExprPool,
909) -> Result<ExprId, ZTransformError> {
910 if k != 1 {
911 return Err(ZTransformError::NotInvertible(format!(
912 "repeated complex-conjugate pole of order {k} (only k = 1 is tabulated)"
913 )));
914 }
915
916 let (b, c) = monic_quadratic_coeffs(base, z, pool).ok_or_else(|| {
918 ZTransformError::NotInvertible(format!(
919 "non-monic / non-quadratic denominator: {}",
920 pool.display(base)
921 ))
922 })?;
923
924 let b2 = pool.pow(b, pool.integer(2_i32));
928 let four_c = pool.mul(vec![pool.integer(4_i32), c]);
929 let disc = simp(pool.add(vec![b2, neg(four_c, pool)]), pool);
930 match literal_rational(disc, pool) {
931 Some(d) if d < 0 => {}
932 Some(_) => {
933 return Err(ZTransformError::NotInvertible(format!(
934 "real-root quadratic denominator (discriminant ≥ 0): {}",
935 pool.display(base)
936 )));
937 }
938 None => {
939 return Err(ZTransformError::NotInvertible(format!(
940 "quadratic denominator with non-literal discriminant: {}",
941 pool.display(base)
942 )));
943 }
944 }
945
946 let (p_coeff, q_coeff) = quadratic_numer_pq(numer, z, pool).ok_or_else(|| {
949 ZTransformError::NotInvertible(format!(
950 "complex-pole numerator not of the form P·z² + Q·z: {}",
951 pool.display(numer)
952 ))
953 })?;
954
955 let half = pool.rational(1_i32, 2_i32);
957 let r = simp(pool.pow(c, half), pool);
958 let two_r = pool.mul(vec![pool.integer(2_i32), r]);
959 let cos_theta = simp(pool.mul(vec![neg(b, pool), recip(two_r, pool)]), pool);
960 let cos2 = pool.pow(cos_theta, pool.integer(2_i32));
962 let sin_theta = simp(
963 pool.pow(pool.add(vec![pool.integer(1_i32), neg(cos2, pool)]), half),
964 pool,
965 );
966 let theta = pool.func("acos", vec![cos_theta]);
967 let theta_n = simp(pool.mul(vec![theta, n]), pool);
968
969 let a_amp = p_coeff;
972 let r_cos = pool.mul(vec![r, cos_theta]);
973 let r_sin = pool.mul(vec![r, sin_theta]);
974 let b_amp = simp(
975 pool.mul(vec![
976 pool.add(vec![q_coeff, pool.mul(vec![a_amp, r_cos])]),
977 recip(r_sin, pool),
978 ]),
979 pool,
980 );
981
982 let r_pow_n = pool.pow(r, n);
984 let cos_term = pool.mul(vec![a_amp, pool.func("cos", vec![theta_n])]);
985 let sin_term = pool.mul(vec![b_amp, pool.func("sin", vec![theta_n])]);
986 let combo = pool.add(vec![cos_term, sin_term]);
987 Ok(simp(pool.mul(vec![r_pow_n, combo]), pool))
988}
989
990fn monic_quadratic_coeffs(base: ExprId, z: ExprId, pool: &ExprPool) -> Option<(ExprId, ExprId)> {
994 let z2 = pool.pow(z, pool.integer(2_i32));
995 let terms: Vec<ExprId> = match pool.get(base) {
996 ExprData::Add(a) => a,
997 _ => vec![base],
998 };
999 let mut a2: Option<ExprId> = None; let mut b1_parts: Vec<ExprId> = Vec::new(); let mut c0_parts: Vec<ExprId> = Vec::new(); for term in terms {
1003 if is_free_of(term, z, pool) {
1004 c0_parts.push(term);
1005 continue;
1006 }
1007 if let Some(coeff) = monomial_coeff(term, z2, z, pool) {
1008 if a2.is_some() {
1009 return None;
1010 }
1011 a2 = Some(coeff);
1012 continue;
1013 }
1014 if let Some(coeff) = monomial_coeff(term, z, z, pool) {
1015 b1_parts.push(coeff);
1016 continue;
1017 }
1018 return None;
1019 }
1020 if simp(a2?, pool) != pool.integer(1_i32) {
1022 return None;
1023 }
1024 let b = match b1_parts.len() {
1025 0 => pool.integer(0_i32),
1026 1 => b1_parts[0],
1027 _ => pool.add(b1_parts),
1028 };
1029 let c = match c0_parts.len() {
1030 0 => pool.integer(0_i32),
1031 1 => c0_parts[0],
1032 _ => pool.add(c0_parts),
1033 };
1034 Some((b, c))
1035}
1036
1037fn monomial_coeff(term: ExprId, power: ExprId, var: ExprId, pool: &ExprPool) -> Option<ExprId> {
1040 if term == power {
1041 return Some(pool.integer(1_i32));
1042 }
1043 if let ExprData::Mul(args) = pool.get(term) {
1044 let pos = args.iter().position(|&m| m == power)?;
1045 let others: Vec<ExprId> = args
1046 .iter()
1047 .enumerate()
1048 .filter(|&(i, _)| i != pos)
1049 .map(|(_, &m)| m)
1050 .collect();
1051 if others.iter().all(|&o| is_free_of(o, var, pool)) {
1052 return Some(match others.len() {
1053 0 => pool.integer(1_i32),
1054 1 => others[0],
1055 _ => pool.mul(others),
1056 });
1057 }
1058 }
1059 None
1060}
1061
1062fn quadratic_numer_pq(numer: ExprId, z: ExprId, pool: &ExprPool) -> Option<(ExprId, ExprId)> {
1065 let z2 = pool.pow(z, pool.integer(2_i32));
1066 let terms: Vec<ExprId> = match pool.get(numer) {
1067 ExprData::Add(a) => a,
1068 _ => vec![numer],
1069 };
1070 let mut p_parts: Vec<ExprId> = Vec::new();
1071 let mut q_parts: Vec<ExprId> = Vec::new();
1072 for term in terms {
1073 if let Some(coeff) = monomial_coeff(term, z2, z, pool) {
1074 p_parts.push(coeff);
1075 continue;
1076 }
1077 if let Some(coeff) = monomial_coeff(term, z, z, pool) {
1078 q_parts.push(coeff);
1079 continue;
1080 }
1081 return None; }
1083 let p = match p_parts.len() {
1084 0 => pool.integer(0_i32),
1085 1 => p_parts[0],
1086 _ => pool.add(p_parts),
1087 };
1088 let q = match q_parts.len() {
1089 0 => pool.integer(0_i32),
1090 1 => q_parts[0],
1091 _ => pool.add(q_parts),
1092 };
1093 Some((p, q))
1094}
1095
1096fn literal_rational(expr: ExprId, pool: &ExprPool) -> Option<rug::Rational> {
1098 match pool.get(expr) {
1099 ExprData::Integer(n) => Some(rug::Rational::from(n.0.clone())),
1100 ExprData::Rational(r) => Some(r.0.clone()),
1101 _ => None,
1102 }
1103}
1104
1105#[cfg(test)]
1106mod tests;