mathhook_core/algebra/collect/
terms.rs

1//! Term collection operations
2
3use crate::core::commutativity::Commutativity;
4use crate::core::{Expression, Number, Symbol};
5use crate::expr;
6use num_bigint::BigInt;
7use num_traits::{One, Zero};
8
9impl Expression {
10    /// Collect terms in an addition with respect to a variable
11    pub(super) fn collect_addition_terms(&self, terms: &[Expression], var: &Symbol) -> Expression {
12        let mut term_coefficients: Vec<(Expression, BigInt)> = Vec::new();
13        let mut constant_term = BigInt::zero();
14
15        for term in terms {
16            let (coeff, power_expr) = self.extract_coefficient_and_power(term, var);
17
18            if power_expr == expr!(0) {
19                constant_term += coeff;
20            } else {
21                let mut found = false;
22                for (existing_expr, existing_coeff) in term_coefficients.iter_mut() {
23                    if *existing_expr == power_expr {
24                        *existing_coeff += &coeff;
25                        found = true;
26                        break;
27                    }
28                }
29                if !found {
30                    term_coefficients.push((power_expr, coeff));
31                }
32            }
33        }
34
35        let mut result_terms = Vec::new();
36
37        if !constant_term.is_zero() {
38            result_terms.push(Expression::big_integer(constant_term));
39        }
40
41        for (power_expr, coeff) in term_coefficients {
42            if !coeff.is_zero() {
43                let term = if coeff.is_one() {
44                    if power_expr == expr!(1) {
45                        Expression::symbol(var.clone())
46                    } else {
47                        Expression::pow(Expression::symbol(var.clone()), power_expr)
48                    }
49                } else {
50                    let var_part = if power_expr == expr!(1) {
51                        Expression::symbol(var.clone())
52                    } else {
53                        Expression::pow(Expression::symbol(var.clone()), power_expr)
54                    };
55                    Expression::mul(vec![Expression::big_integer(coeff), var_part])
56                };
57                result_terms.push(term);
58            }
59        }
60
61        if result_terms.is_empty() {
62            expr!(0)
63        } else if result_terms.len() == 1 {
64            result_terms[0].clone()
65        } else {
66            Expression::add(result_terms)
67        }
68    }
69
70    /// Collect all like terms regardless of variable
71    ///
72    /// For commutative terms: 2AB + 3AB = 5AB
73    /// For noncommutative terms: 2AB + 3BA stays as 2AB + 3BA (different order!)
74    pub(super) fn collect_all_like_terms(&self, terms: &[Expression]) -> Expression {
75        let mut term_coefficients: Vec<(Expression, BigInt)> = Vec::new();
76
77        for term in terms {
78            let (coeff, base_term) = self.extract_coefficient_and_base(term);
79
80            let mut found = false;
81            for (existing_expr, existing_coeff) in term_coefficients.iter_mut() {
82                if *existing_expr == base_term {
83                    let commutativity = Commutativity::combine(vec![
84                        existing_expr.commutativity(),
85                        base_term.commutativity(),
86                    ]);
87
88                    if commutativity.can_sort() || self.same_factor_order(existing_expr, &base_term)
89                    {
90                        *existing_coeff += &coeff;
91                        found = true;
92                        break;
93                    }
94                }
95            }
96            if !found {
97                term_coefficients.push((base_term, coeff));
98            }
99        }
100
101        let mut result_terms = Vec::new();
102
103        for (base_term, total_coeff) in term_coefficients {
104            if !total_coeff.is_zero() {
105                let final_term = if total_coeff.is_one() {
106                    base_term
107                } else if base_term == expr!(1) {
108                    Expression::big_integer(total_coeff)
109                } else {
110                    Expression::mul(vec![Expression::big_integer(total_coeff), base_term])
111                };
112                result_terms.push(final_term);
113            }
114        }
115
116        if result_terms.is_empty() {
117            expr!(0)
118        } else if result_terms.len() == 1 {
119            result_terms[0].clone()
120        } else {
121            Expression::add(result_terms)
122        }
123    }
124
125    /// Collect terms in multiplication (combine powers of same base)
126    pub(super) fn collect_multiplication_terms(&self, factors: &[Expression]) -> Expression {
127        let mut base_powers: Vec<(Expression, Vec<Expression>)> = Vec::new();
128        let mut numeric_factor = BigInt::one();
129        let mut other_factors = Vec::new();
130
131        for factor in factors {
132            match factor {
133                Expression::Number(Number::Integer(n)) => {
134                    numeric_factor *= BigInt::from(*n);
135                }
136                Expression::Pow(base, exp) => {
137                    let base_expr = (**base).clone();
138                    let exp_expr = (**exp).clone();
139                    let mut found = false;
140                    for (existing_base, powers) in base_powers.iter_mut() {
141                        if *existing_base == base_expr {
142                            powers.push(exp_expr.clone());
143                            found = true;
144                            break;
145                        }
146                    }
147                    if !found {
148                        base_powers.push((base_expr, vec![exp_expr]));
149                    }
150                }
151                Expression::Symbol(_) => {
152                    let mut found = false;
153                    for (existing_base, powers) in base_powers.iter_mut() {
154                        if *existing_base == *factor {
155                            powers.push(expr!(1));
156                            found = true;
157                            break;
158                        }
159                    }
160                    if !found {
161                        base_powers.push((factor.clone(), vec![expr!(1)]));
162                    }
163                }
164                _ => {
165                    other_factors.push(factor.clone());
166                }
167            }
168        }
169
170        let mut result_factors = Vec::new();
171
172        if !numeric_factor.is_one() {
173            result_factors.push(Expression::big_integer(numeric_factor));
174        }
175
176        for (base, exponents) in base_powers {
177            if exponents.len() == 1 {
178                if exponents[0] == expr!(1) {
179                    result_factors.push(base);
180                } else {
181                    result_factors.push(Expression::pow(base, exponents[0].clone()));
182                }
183            } else {
184                let total_exp = Expression::add(exponents);
185                result_factors.push(Expression::pow(base, total_exp));
186            }
187        }
188
189        result_factors.extend(other_factors);
190
191        if result_factors.is_empty() {
192            expr!(1)
193        } else if result_factors.len() == 1 {
194            result_factors[0].clone()
195        } else {
196            Expression::mul(result_factors)
197        }
198    }
199
200    /// Separate variables and constants
201    pub fn separate_constants(&self) -> (Expression, Expression) {
202        match self {
203            Expression::Add(terms) => {
204                let mut constants = Vec::new();
205                let mut variables = Vec::new();
206
207                for term in terms.iter() {
208                    if term.is_constant() {
209                        constants.push(term.clone());
210                    } else {
211                        variables.push(term.clone());
212                    }
213                }
214
215                let const_part = if constants.is_empty() {
216                    expr!(0)
217                } else {
218                    Expression::add(constants)
219                };
220
221                let var_part = if variables.is_empty() {
222                    expr!(0)
223                } else {
224                    Expression::add(variables)
225                };
226
227                (const_part, var_part)
228            }
229            _ => {
230                if self.is_constant() {
231                    (self.clone(), expr!(0))
232                } else {
233                    (expr!(0), self.clone())
234                }
235            }
236        }
237    }
238}