qualia_core_db/specialized_libs/
polynomial_algebra.rs1const EPS: f64 = 1e-9;
19
20#[derive(Clone, Debug, PartialEq)]
22pub struct Polynomial {
23 coeffs: Vec<f64>,
26}
27
28impl Polynomial {
29 pub fn new(coeffs: Vec<f64>) -> Self {
32 let mut p = Polynomial { coeffs };
33 p.trim();
34 p
35 }
36
37 pub fn zero() -> Self {
39 Polynomial { coeffs: Vec::new() }
40 }
41
42 pub fn constant(c: f64) -> Self {
44 Polynomial::new(vec![c])
45 }
46
47 pub fn coeffs(&self) -> &[f64] {
49 &self.coeffs
50 }
51
52 pub fn is_zero(&self) -> bool {
54 self.coeffs.is_empty()
55 }
56
57 pub fn degree(&self) -> Option<usize> {
59 if self.coeffs.is_empty() {
60 None
61 } else {
62 Some(self.coeffs.len() - 1)
63 }
64 }
65
66 pub fn leading(&self) -> f64 {
68 *self.coeffs.last().unwrap_or(&0.0)
69 }
70
71 fn trim(&mut self) {
73 while let Some(&c) = self.coeffs.last() {
74 if c.abs() <= EPS {
75 self.coeffs.pop();
76 } else {
77 break;
78 }
79 }
80 }
81
82 pub fn eval(&self, x: f64) -> f64 {
84 let mut acc = 0.0;
85 for &c in self.coeffs.iter().rev() {
86 acc = acc * x + c;
87 }
88 acc
89 }
90
91 pub fn add(&self, other: &Polynomial) -> Polynomial {
93 let n = self.coeffs.len().max(other.coeffs.len());
94 let mut out = vec![0.0; n];
95 for (i, c) in self.coeffs.iter().enumerate() {
96 out[i] += c;
97 }
98 for (i, c) in other.coeffs.iter().enumerate() {
99 out[i] += c;
100 }
101 Polynomial::new(out)
102 }
103
104 pub fn sub(&self, other: &Polynomial) -> Polynomial {
106 let n = self.coeffs.len().max(other.coeffs.len());
107 let mut out = vec![0.0; n];
108 for (i, c) in self.coeffs.iter().enumerate() {
109 out[i] += c;
110 }
111 for (i, c) in other.coeffs.iter().enumerate() {
112 out[i] -= c;
113 }
114 Polynomial::new(out)
115 }
116
117 pub fn scale(&self, s: f64) -> Polynomial {
119 Polynomial::new(self.coeffs.iter().map(|c| c * s).collect())
120 }
121
122 pub fn mul(&self, other: &Polynomial) -> Polynomial {
124 if self.is_zero() || other.is_zero() {
125 return Polynomial::zero();
126 }
127 let mut out = vec![0.0; self.coeffs.len() + other.coeffs.len() - 1];
128 for (i, a) in self.coeffs.iter().enumerate() {
129 for (j, b) in other.coeffs.iter().enumerate() {
130 out[i + j] += a * b;
131 }
132 }
133 Polynomial::new(out)
134 }
135
136 pub fn div_rem(&self, divisor: &Polynomial) -> Option<(Polynomial, Polynomial)> {
140 if divisor.is_zero() {
141 return None; }
143 let div_deg = divisor.degree().unwrap();
145 if self.is_zero() || self.degree().unwrap() < div_deg {
146 return Some((Polynomial::zero(), self.clone()));
147 }
148 let mut rem = self.coeffs.clone();
149 let div_lead = divisor.leading();
150 let quot_len = self.coeffs.len() - divisor.coeffs.len() + 1;
151 let mut quot = vec![0.0; quot_len];
152
153 for i in (0..quot_len).rev() {
155 let rem_idx = i + div_deg; let factor = rem[rem_idx] / div_lead;
157 quot[i] = factor;
158 if factor != 0.0 {
159 for (j, dc) in divisor.coeffs.iter().enumerate() {
160 rem[i + j] -= factor * dc;
161 }
162 }
163 }
164 Some((Polynomial::new(quot), Polynomial::new(rem)))
165 }
166
167 pub fn derivative(&self) -> Polynomial {
169 if self.coeffs.len() <= 1 {
170 return Polynomial::zero();
171 }
172 let mut out = Vec::with_capacity(self.coeffs.len() - 1);
173 for (i, c) in self.coeffs.iter().enumerate().skip(1) {
174 out.push(c * i as f64);
175 }
176 Polynomial::new(out)
177 }
178
179 pub fn monic(&self) -> Polynomial {
182 if self.is_zero() {
183 return Polynomial::zero();
184 }
185 let lead = self.leading();
186 self.scale(1.0 / lead)
187 }
188
189 pub fn gcd(&self, other: &Polynomial) -> Polynomial {
193 let mut a = self.clone();
194 let mut b = other.clone();
195 while !b.is_zero() {
196 let (_, r) = a.div_rem(&b).expect("b non-zero in gcd loop");
198 a = b;
199 b = r;
200 }
201 a.monic()
202 }
203
204 pub fn resultant(&self, other: &Polynomial) -> f64 {
217 if self.is_zero() || other.is_zero() {
219 return 0.0;
220 }
221 let mut a = self.clone();
222 let mut b = other.clone();
223 let mut result = 1.0_f64;
224
225 loop {
226 let deg_a = a.degree().unwrap();
227 let deg_b = b.degree().unwrap();
228
229 if deg_b == 0 {
231 result *= b.leading().powi(deg_a as i32);
233 return result;
234 }
235
236 let (_, r) = a.div_rem(&b).expect("b non-constant ⇒ non-zero");
238
239 if (deg_a % 2 == 1) && (deg_b % 2 == 1) {
241 result = -result;
242 }
243
244 if r.is_zero() {
245 return 0.0;
247 }
248
249 let deg_r = r.degree().unwrap();
250 result *= b.leading().powi((deg_a as i32) - (deg_r as i32));
252
253 a = b;
254 b = r;
255 }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 fn linear(r: f64) -> Polynomial {
265 Polynomial::new(vec![-r, 1.0])
266 }
267
268 fn approx(a: &Polynomial, b: &Polynomial) -> bool {
269 if a.coeffs().len() != b.coeffs().len() {
270 return false;
271 }
272 a.coeffs()
273 .iter()
274 .zip(b.coeffs())
275 .all(|(x, y)| (x - y).abs() < 1e-6)
276 }
277
278 #[test]
279 fn eval_horner() {
280 let p = Polynomial::new(vec![2.0, 3.0, 1.0]);
282 assert!((p.eval(2.0) - 12.0).abs() < 1e-12);
283 }
284
285 #[test]
286 fn add_sub() {
287 let a = Polynomial::new(vec![1.0, 2.0, 3.0]);
288 let b = Polynomial::new(vec![0.0, 1.0, -3.0]);
289 assert_eq!(a.add(&b), Polynomial::new(vec![1.0, 3.0, 0.0]));
290 assert_eq!(a.add(&b).degree(), Some(1));
292 assert_eq!(a.sub(&a), Polynomial::zero());
293 }
294
295 #[test]
296 fn mul_factors() {
297 let prod = linear(1.0).mul(&Polynomial::new(vec![1.0, 1.0]));
299 assert!(approx(&prod, &Polynomial::new(vec![-1.0, 0.0, 1.0])));
300 }
301
302 #[test]
303 fn division_exact_x2_minus_1() {
304 let num = Polynomial::new(vec![-1.0, 0.0, 1.0]);
306 let den = linear(1.0); let (q, r) = num.div_rem(&den).unwrap();
308 assert!(approx(&q, &Polynomial::new(vec![1.0, 1.0])), "q = {:?}", q);
309 assert!(r.is_zero(), "remainder = {:?}", r);
310 }
311
312 #[test]
313 fn division_with_remainder_reconstructs() {
314 let num = Polynomial::new(vec![1.0, 2.0, 0.0, 1.0]);
316 let den = Polynomial::new(vec![1.0, 0.0, 1.0]);
317 let (q, r) = num.div_rem(&den).unwrap();
318 let recon = q.mul(&den).add(&r);
320 assert!(approx(&recon, &num), "recon = {:?}", recon);
321 assert!(r.degree().unwrap_or(0) < den.degree().unwrap());
322 }
323
324 #[test]
325 fn divide_by_zero_poly_fails_closed() {
326 let num = Polynomial::new(vec![1.0, 1.0]);
327 assert!(num.div_rem(&Polynomial::zero()).is_none());
328 }
329
330 #[test]
331 fn gcd_x2_minus_1_and_x_minus_1() {
332 let a = Polynomial::new(vec![-1.0, 0.0, 1.0]);
334 let b = linear(1.0);
335 let g = a.gcd(&b);
336 assert!(
338 approx(&g, &Polynomial::new(vec![-1.0, 1.0])),
339 "gcd = {:?}",
340 g
341 );
342 }
343
344 #[test]
345 fn gcd_shared_quadratic_factor() {
346 let a = linear(1.0).mul(&linear(2.0));
348 let b = linear(2.0).mul(&linear(3.0));
349 let g = a.gcd(&b);
350 assert!(approx(&g, &linear(2.0)), "gcd = {:?}", g);
351 }
352
353 #[test]
354 fn derivative_basic() {
355 let p = Polynomial::new(vec![7.0, 5.0, 2.0, 1.0]);
357 assert_eq!(p.derivative(), Polynomial::new(vec![5.0, 4.0, 3.0]));
358 assert!(Polynomial::constant(4.0).derivative().is_zero());
359 }
360
361 #[test]
362 fn resultant_zero_iff_common_root() {
363 let a = linear(1.0).mul(&linear(2.0)); let b = linear(2.0).mul(&linear(3.0)); assert!(
367 a.resultant(&b).abs() < 1e-6,
368 "expected ~0, got {}",
369 a.resultant(&b)
370 );
371
372 let c = linear(1.0).mul(&linear(2.0)); let d = linear(3.0).mul(&linear(4.0)); assert!(
376 c.resultant(&d).abs() > 1e-6,
377 "expected non-zero, got {}",
378 c.resultant(&d)
379 );
380 }
381
382 #[test]
383 fn resultant_known_value() {
384 let a = linear(1.0); let b = linear(2.0); let r = a.resultant(&b);
390 assert!((r - (-1.0)).abs() < 1e-9, "resultant = {}", r);
391 }
392}