mathhook_core/algebra/collect/
coefficients.rs

1//! Coefficient extraction operations for term collection
2
3use crate::core::{Expression, Number, Symbol};
4use crate::expr;
5use num_bigint::BigInt;
6use num_traits::{One, Zero};
7
8impl Expression {
9    /// Extract coefficient and power from a term with respect to a variable
10    pub(super) fn extract_coefficient_and_power(
11        &self,
12        term: &Expression,
13        var: &Symbol,
14    ) -> (BigInt, Expression) {
15        match term {
16            Expression::Number(Number::Integer(n)) => (BigInt::from(*n), Expression::integer(0)),
17
18            Expression::Symbol(s) if s == var => (BigInt::one(), Expression::integer(1)),
19
20            Expression::Symbol(_) => (BigInt::zero(), Expression::integer(0)),
21
22            Expression::Pow(base, exp) => {
23                if let Expression::Symbol(s) = base.as_ref() {
24                    if s == var {
25                        return (BigInt::one(), exp.as_ref().clone());
26                    }
27                }
28                (BigInt::zero(), Expression::integer(0))
29            }
30
31            Expression::Mul(factors) => {
32                let mut coefficient = BigInt::one();
33                let mut power = expr!(0);
34                let mut has_var = false;
35
36                for factor in factors.iter() {
37                    match factor {
38                        Expression::Number(Number::Integer(n)) => {
39                            coefficient *= BigInt::from(*n);
40                        }
41                        Expression::Symbol(s) if s == var => {
42                            power = expr!(1);
43                            has_var = true;
44                        }
45                        Expression::Pow(base, exp) => {
46                            if let Expression::Symbol(s) = base.as_ref() {
47                                if s == var {
48                                    power = exp.as_ref().clone();
49                                    has_var = true;
50                                }
51                            }
52                        }
53                        _ => {}
54                    }
55                }
56
57                if has_var {
58                    (coefficient, power)
59                } else {
60                    (BigInt::zero(), expr!(0))
61                }
62            }
63
64            _ => (BigInt::zero(), expr!(0)),
65        }
66    }
67
68    /// Extract coefficient and base term from any expression
69    pub(super) fn extract_coefficient_and_base(&self, expr: &Expression) -> (BigInt, Expression) {
70        match expr {
71            Expression::Number(Number::Integer(n)) => (BigInt::from(*n), expr!(1)),
72
73            Expression::Symbol(_) => (BigInt::one(), expr.clone()),
74
75            Expression::Mul(factors) => {
76                let mut coefficient = BigInt::one();
77                let mut non_numeric_factors = Vec::new();
78
79                for factor in factors.iter() {
80                    if let Expression::Number(Number::Integer(n)) = factor {
81                        coefficient *= BigInt::from(*n);
82                    } else {
83                        non_numeric_factors.push(factor.clone());
84                    }
85                }
86
87                let base = if non_numeric_factors.is_empty() {
88                    expr!(1)
89                } else if non_numeric_factors.len() == 1 {
90                    non_numeric_factors[0].clone()
91                } else {
92                    Expression::mul(non_numeric_factors)
93                };
94
95                (coefficient, base)
96            }
97
98            _ => (BigInt::one(), expr.clone()),
99        }
100    }
101
102    /// Check if an expression is constant (contains no variables)
103    pub fn is_constant(&self) -> bool {
104        match self {
105            Expression::Number(_) => true,
106            Expression::Symbol(_) => false,
107            Expression::Add(terms) | Expression::Mul(terms) => {
108                terms.iter().all(|t| t.is_constant())
109            }
110            Expression::Pow(base, exp) => base.is_constant() && exp.is_constant(),
111            Expression::Function { args, .. } => args.iter().all(|a| a.is_constant()),
112            Expression::Complex(_) => true,
113            Expression::Matrix(_) => false,
114            Expression::Constant(_) => true,
115            Expression::Relation(_) => false,
116            Expression::Piecewise(_) => false,
117            Expression::Set(_) => false,
118            Expression::Interval(_) => true,
119            Expression::Calculus(_) => false,
120            Expression::MethodCall(method_data) => {
121                method_data.object.is_constant() && method_data.args.iter().all(|a| a.is_constant())
122            }
123        }
124    }
125    /// Check if two expressions have the same factor order
126    ///
127    /// For noncommutative terms, AB and BA are DIFFERENT and should NOT be combined
128    pub(super) fn same_factor_order(&self, expr1: &Expression, expr2: &Expression) -> bool {
129        match (expr1, expr2) {
130            (Expression::Mul(factors1), Expression::Mul(factors2)) => {
131                if factors1.len() != factors2.len() {
132                    return false;
133                }
134                factors1
135                    .iter()
136                    .zip(factors2.iter())
137                    .all(|(f1, f2)| f1 == f2)
138            }
139            _ => expr1 == expr2,
140        }
141    }
142}