mathhook_core/algebra/groebner/
s_polynomial.rs1use super::monomial_order::{MonomialOrder, MonomialOrdering};
7use crate::core::{Expression, Number, Symbol};
8
9pub fn s_polynomial(
42 f: &Expression,
43 g: &Expression,
44 variables: &[Symbol],
45 order: &MonomialOrder,
46) -> Expression {
47 if f.is_zero() || g.is_zero() {
48 return Expression::integer(0);
49 }
50
51 let lt_f = order.leading_monomial(f, variables);
52 let lt_g = order.leading_monomial(g, variables);
53
54 let lc_f = order.leading_coefficient(f, variables);
55 let lc_g = order.leading_coefficient(g, variables);
56
57 let lcm_lt = monomial_lcm(<_f, <_g, variables);
58
59 let coeff_f = Expression::div(lcm_lt.clone(), Expression::mul(vec![lc_f, lt_f]));
60 let coeff_g = Expression::div(lcm_lt, Expression::mul(vec![lc_g, lt_g]));
61
62 let term1 = Expression::mul(vec![coeff_f, f.clone()]);
63 let term2 = Expression::mul(vec![coeff_g, g.clone()]);
64
65 term1 - term2
67}
68
69fn monomial_lcm(mono1: &Expression, mono2: &Expression, variables: &[Symbol]) -> Expression {
83 let exp1 = extract_exponents(mono1, variables);
84 let exp2 = extract_exponents(mono2, variables);
85
86 let mut lcm_factors = Vec::new();
87
88 for (i, (e1, e2)) in exp1.iter().zip(exp2.iter()).enumerate() {
89 let max_exp = (*e1).max(*e2);
90 if max_exp > 0 {
91 let var_expr = Expression::symbol(variables[i].clone());
92 if max_exp == 1 {
93 lcm_factors.push(var_expr);
94 } else {
95 lcm_factors.push(Expression::pow(var_expr, Expression::integer(max_exp)));
96 }
97 }
98 }
99
100 if lcm_factors.is_empty() {
101 Expression::integer(1)
102 } else if lcm_factors.len() == 1 {
103 lcm_factors[0].clone()
104 } else {
105 Expression::mul(lcm_factors)
106 }
107}
108
109fn extract_exponents(mono: &Expression, variables: &[Symbol]) -> Vec<i64> {
111 let mut exponents = vec![0i64; variables.len()];
112
113 match mono {
114 Expression::Symbol(s) => {
115 if let Some(idx) = variables.iter().position(|v| v == s) {
116 exponents[idx] = 1;
117 }
118 }
119 Expression::Pow(base, exp) => {
120 if let Expression::Symbol(s) = base.as_ref() {
121 if let Some(idx) = variables.iter().position(|v| v == s) {
122 if let Expression::Number(Number::Integer(i)) = exp.as_ref() {
124 exponents[idx] = *i;
125 }
126 }
127 }
128 }
129 Expression::Mul(factors) => {
130 for factor in factors.iter() {
132 if !matches!(factor, Expression::Number(_)) {
133 let factor_exp = extract_exponents(factor, variables);
134 for (i, e) in factor_exp.iter().enumerate() {
135 exponents[i] += e;
136 }
137 }
138 }
139 }
140 _ => {}
141 }
142
143 exponents
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::symbol;
150
151 #[test]
152 fn test_monomial_lcm_simple() {
153 let x = symbol!(x);
154 let y = symbol!(y);
155 let vars = vec![x.clone(), y.clone()];
156
157 let mono1 = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
158 let mono2 = Expression::symbol(y.clone());
159
160 let lcm = monomial_lcm(&mono1, &mono2, &vars);
161
162 let expected = Expression::mul(vec![
163 Expression::pow(Expression::symbol(x), Expression::integer(2)),
164 Expression::symbol(y),
165 ]);
166
167 assert_eq!(lcm, expected);
168 }
169
170 #[test]
171 fn test_monomial_lcm_overlap() {
172 let x = symbol!(x);
173 let y = symbol!(y);
174 let vars = vec![x.clone(), y.clone()];
175
176 let mono1 = Expression::mul(vec![
177 Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
178 Expression::symbol(y.clone()),
179 ]);
180
181 let mono2 = Expression::mul(vec![
182 Expression::symbol(x.clone()),
183 Expression::pow(Expression::symbol(y.clone()), Expression::integer(3)),
184 ]);
185
186 let lcm = monomial_lcm(&mono1, &mono2, &vars);
187
188 let expected = Expression::mul(vec![
189 Expression::pow(Expression::symbol(x), Expression::integer(2)),
190 Expression::pow(Expression::symbol(y), Expression::integer(3)),
191 ]);
192
193 assert_eq!(lcm, expected);
194 }
195
196 #[test]
197 fn test_s_polynomial_basic() {
198 let x = symbol!(x);
199 let y = symbol!(y);
200 let vars = vec![x.clone(), y.clone()];
201
202 let f = Expression::add(vec![
203 Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
204 Expression::symbol(y.clone()),
205 ]);
206
207 let g = Expression::add(vec![
208 Expression::mul(vec![
209 Expression::symbol(x.clone()),
210 Expression::symbol(y.clone()),
211 ]),
212 Expression::integer(1),
213 ]);
214
215 let s = s_polynomial(&f, &g, &vars, &MonomialOrder::Lex);
216
217 assert!(!s.is_zero());
218 }
219
220 #[test]
221 fn test_s_polynomial_with_zero() {
222 let x = symbol!(x);
223 let vars = vec![x.clone()];
224
225 let f = Expression::symbol(x.clone());
226 let g = Expression::integer(0);
227
228 let s = s_polynomial(&f, &g, &vars, &MonomialOrder::Lex);
229
230 assert!(s.is_zero());
231 }
232
233 #[test]
234 fn test_s_polynomial_identical() {
235 let x = symbol!(x);
236 let vars = vec![x.clone()];
237
238 let f = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
239 let g = f.clone();
240
241 let s = s_polynomial(&f, &g, &vars, &MonomialOrder::Lex);
242
243 assert!(s.is_zero());
244 }
245}