1use crate::defs::RoundingMode;
4use crate::defs::WORD_BIT_SIZE;
5use crate::Consts;
6use crate::ExactInt;
7use crate::ExactNum;
8use crate::ExactNumArray;
9use alloc::vec;
10use alloc::vec::Vec;
11
12pub const POLY_COMPANION_CLOSED_DEG: usize = 2;
19
20#[derive(Clone, Debug)]
27pub struct ExactNumPoly {
28 coeffs: Vec<ExactNum>,
29 p: usize,
30 rm: RoundingMode,
31}
32
33impl ExactNumPoly {
34 pub fn zero(p: usize, rm: RoundingMode) -> Self {
36 Self {
37 coeffs: Vec::new(),
38 p,
39 rm,
40 }
41 }
42
43 pub fn one(p: usize, rm: RoundingMode) -> Self {
45 Self::from_coeffs(p, rm, &[ExactNum::from_u8(1, p)])
46 }
47
48 pub fn from_coeffs(p: usize, rm: RoundingMode, coeffs: &[ExactNum]) -> Self {
51 let mut out = Vec::with_capacity(coeffs.len());
52 for c in coeffs {
53 let mut y = c.clone();
54 let _ = y.set_precision(p, rm);
55 out.push(y);
56 }
57 let mut s = Self { coeffs: out, p, rm };
58 s.strip_leading();
59 s
60 }
61
62 pub fn from_i64_coeffs(p: usize, rm: RoundingMode, coeffs: &[i64]) -> Self {
64 let xs: Vec<ExactNum> = coeffs.iter().map(|&k| ExactNum::from_i64(k, p)).collect();
65 Self::from_coeffs(p, rm, &xs)
66 }
67
68 pub fn precision(&self) -> usize {
70 self.p
71 }
72
73 pub fn rounding(&self) -> RoundingMode {
75 self.rm
76 }
77
78 pub fn coeffs(&self) -> &[ExactNum] {
80 &self.coeffs
81 }
82
83 pub fn degree(&self) -> Option<usize> {
85 if self.coeffs.is_empty() {
86 None
87 } else {
88 Some(self.coeffs.len() - 1)
89 }
90 }
91
92 pub fn is_zero(&self) -> bool {
94 self.coeffs.is_empty() || self.coeffs.iter().all(|c| c.is_zero())
95 }
96
97 pub fn coeff(&self, k: usize) -> ExactNum {
99 self.coeffs
100 .get(k)
101 .cloned()
102 .unwrap_or_else(|| ExactNum::new(self.p))
103 }
104
105 fn leading(&self) -> ExactNum {
106 self.coeffs
107 .last()
108 .cloned()
109 .unwrap_or_else(|| ExactNum::new(self.p))
110 }
111
112 fn all_finite(&self) -> bool {
113 self.coeffs.iter().all(|c| !c.is_nan() && !c.is_inf())
114 }
115
116 fn strip_leading(&mut self) {
117 while self.coeffs.last().is_some_and(|c| c.is_zero()) {
118 self.coeffs.pop();
119 }
120 }
121
122 fn work_p(p: usize) -> usize {
123 p.saturating_add(WORD_BIT_SIZE)
124 }
125
126 pub fn eval(&self, x: &ExactNum, p: usize, rm: RoundingMode, _cc: &mut Consts) -> ExactNum {
131 ExactNum::polyval(&self.coeffs, x, p, rm)
132 }
133
134 pub fn add(&self, rhs: &Self, p: usize, rm: RoundingMode) -> Self {
136 let n = self.coeffs.len().max(rhs.coeffs.len());
137 let mut out = Vec::with_capacity(n);
138 for i in 0..n {
139 let a = self.coeff(i);
140 let b = rhs.coeff(i);
141 out.push(a.add(&b, p, rm));
142 }
143 Self::from_coeffs(p, rm, &out)
144 }
145
146 pub fn sub(&self, rhs: &Self, p: usize, rm: RoundingMode) -> Self {
148 let n = self.coeffs.len().max(rhs.coeffs.len());
149 let mut out = Vec::with_capacity(n);
150 for i in 0..n {
151 let a = self.coeff(i);
152 let b = rhs.coeff(i);
153 out.push(a.sub(&b, p, rm));
154 }
155 Self::from_coeffs(p, rm, &out)
156 }
157
158 pub fn mul(&self, rhs: &Self, p: usize, rm: RoundingMode) -> Self {
160 if self.is_zero() || rhs.is_zero() {
161 return Self::zero(p, rm);
162 }
163 let n = self.coeffs.len() + rhs.coeffs.len() - 1;
164 let mut out = vec![ExactNum::new(p); n];
165 for (i, a) in self.coeffs.iter().enumerate() {
166 for (j, b) in rhs.coeffs.iter().enumerate() {
167 let t = a.mul(b, p, rm);
168 out[i + j] = out[i + j].add(&t, p, rm);
169 }
170 }
171 Self::from_coeffs(p, rm, &out)
172 }
173
174 pub fn div_rem(&self, g: &Self, p: usize, rm: RoundingMode) -> Option<(Self, Self)> {
178 if g.is_zero() || !self.all_finite() || !g.all_finite() {
179 return None;
180 }
181 let deg_g = g.degree()?;
182 let lc_g = g.leading();
183 if lc_g.is_zero() {
184 return None;
185 }
186 let mut r = self.coeffs.clone();
187 for c in &mut r {
188 let _ = c.set_precision(p, rm);
189 }
190 while r.last().is_some_and(|c| c.is_zero()) {
191 r.pop();
192 }
193 if r.is_empty() {
194 return Some((Self::zero(p, rm), Self::zero(p, rm)));
195 }
196 let deg_f = r.len() - 1;
197 let mut q = if deg_f >= deg_g {
198 vec![ExactNum::new(p); deg_f - deg_g + 1]
199 } else {
200 Vec::new()
201 };
202 while r.len() > deg_g {
203 let deg_r = r.len() - 1;
204 let shift = deg_r - deg_g;
205 let t = r[deg_r].div(&lc_g, p, rm);
206 q[shift] = q[shift].add(&t, p, rm);
207 for i in 0..=deg_g {
208 let term = t.mul(&g.coeff(i), p, rm);
209 let idx = i + shift;
210 r[idx] = r[idx].sub(&term, p, rm);
211 }
212 while r.last().is_some_and(|c| c.is_zero()) {
213 r.pop();
214 }
215 }
216 Some((Self::from_coeffs(p, rm, &q), Self::from_coeffs(p, rm, &r)))
217 }
218
219 fn integer_content(&self) -> Option<ExactInt> {
220 if self.coeffs.is_empty() {
221 return Some(ExactInt::zero());
222 }
223 let mut g: Option<ExactInt> = None;
224 for c in &self.coeffs {
225 if c.is_nan() || c.is_inf() || !c.fract().is_zero() {
226 return None;
227 }
228 let Some(i) = ExactInt::from_exact_num(c) else {
229 return None;
230 };
231 g = Some(match g {
232 None => i,
233 Some(prev) => prev.gcd(&i),
234 });
235 }
236 g
237 }
238
239 fn primitive_monic(&self, p: usize, rm: RoundingMode) -> Self {
245 if self.is_zero() {
246 return Self::zero(p, rm);
247 }
248 let mut s = if let Some(cont) = self.integer_content() {
249 if !cont.is_zero() && !cont.is_one() {
250 let d = cont.to_exact_num(p, rm);
251 let xs: Vec<ExactNum> = self.coeffs.iter().map(|c| c.div(&d, p, rm)).collect();
252 Self::from_coeffs(p, rm, &xs)
253 } else {
254 self.clone()
255 }
256 } else {
257 self.clone()
258 };
259 let lc = s.leading();
260 if lc.is_zero() || lc.is_nan() || lc.is_inf() {
261 return s;
262 }
263 if lc.is_negative() {
264 s.coeffs = s.coeffs.into_iter().map(|c| c.neg()).collect();
265 }
266 let lc = s.leading();
267 if lc.is_zero() {
268 return s;
269 }
270 let xs: Vec<ExactNum> = s.coeffs.iter().map(|c| c.div(&lc, p, rm)).collect();
271 Self::from_coeffs(p, rm, &xs)
272 }
273
274 pub fn gcd(&self, other: &Self, p: usize, rm: RoundingMode) -> Self {
278 if self.is_zero() && other.is_zero() {
279 return Self::zero(p, rm);
280 }
281 let mut a = self.primitive_monic(p, rm);
282 let mut b = other.primitive_monic(p, rm);
283 let max_steps = a.degree().unwrap_or(0) + b.degree().unwrap_or(0) + 1;
284 let mut steps = 0;
285 while !b.is_zero() && steps < max_steps {
286 steps += 1;
287 let Some((_, r)) = a.div_rem(&b, p, rm) else {
288 break;
289 };
290 a = b;
291 b = r.primitive_monic(p, rm);
292 }
293 a.primitive_monic(p, rm)
294 }
295
296 pub fn compose(&self, g: &Self, p: usize, rm: RoundingMode, _cc: &mut Consts) -> Self {
298 if self.is_zero() {
299 return Self::zero(p, rm);
300 }
301 let mut acc = Self::from_coeffs(p, rm, &[self.leading()]);
302 for c in self.coeffs.iter().rev().skip(1) {
303 acc = acc.mul(g, p, rm);
304 acc = acc.add(&Self::from_coeffs(p, rm, &[c.clone()]), p, rm);
305 }
306 acc
307 }
308
309 pub fn derivative(&self, p: usize, rm: RoundingMode) -> Self {
311 if self.coeffs.len() <= 1 {
312 return Self::zero(p, rm);
313 }
314 let mut out = Vec::with_capacity(self.coeffs.len() - 1);
315 for (k, c) in self.coeffs.iter().enumerate().skip(1) {
316 let n = ExactNum::from_u32(k as u32, p);
317 out.push(c.mul(&n, p, rm));
318 }
319 Self::from_coeffs(p, rm, &out)
320 }
321
322 pub fn integral(&self, p: usize, rm: RoundingMode) -> Self {
324 if self.is_zero() {
325 return Self::zero(p, rm);
326 }
327 let mut out = Vec::with_capacity(self.coeffs.len() + 1);
328 out.push(ExactNum::new(p));
329 for (k, c) in self.coeffs.iter().enumerate() {
330 let den = ExactNum::from_u32((k + 1) as u32, p);
331 out.push(c.div(&den, p, rm));
332 }
333 Self::from_coeffs(p, rm, &out)
334 }
335
336 pub fn companion_matrix(&self, p: usize, rm: RoundingMode) -> Option<ExactNumArray> {
341 let n = self.degree()?;
342 if n == 0 || !self.all_finite() {
343 return None;
344 }
345 let lc = self.leading();
346 if lc.is_zero() {
347 return None;
348 }
349 let mut vals = vec![ExactNum::new(p); n * n];
350 let one = ExactNum::from_u8(1, p);
351 for i in 1..n {
352 vals[i * n + (i - 1)] = one.clone();
353 }
354 for i in 0..n {
355 let a = self.coeff(i).div(&lc, p, rm);
356 vals[i * n + (n - 1)] = a.neg();
357 }
358 ExactNumArray::from_shape(p, n, n, &vals)
359 }
360
361 pub fn roots_real(
372 &self,
373 p: usize,
374 rm: RoundingMode,
375 _cc: &mut Consts,
376 ) -> Option<Vec<ExactNum>> {
377 if !self.all_finite() {
378 return None;
379 }
380 let n = match self.degree() {
381 None => return None,
382 Some(0) => return Some(Vec::new()),
383 Some(n) => n,
384 };
385 if n > POLY_COMPANION_CLOSED_DEG {
386 return None;
387 }
388 let wrk = Self::work_p(p);
389 let lc = self.leading();
390 if n == 1 {
391 let r = self.coeff(0).div(&lc, wrk, RoundingMode::None).neg();
392 let mut r = r;
393 let _ = r.set_precision(p, rm);
394 return Some(vec![r]);
395 }
396 let a = self.coeff(2);
398 let b = self.coeff(1);
399 let c = self.coeff(0);
400 let four = ExactNum::from_u8(4, wrk);
401 let disc = b.mul(&b, wrk, RoundingMode::None).sub(
402 &four
403 .mul(&a, wrk, RoundingMode::None)
404 .mul(&c, wrk, RoundingMode::None),
405 wrk,
406 RoundingMode::None,
407 );
408 if disc.is_negative() {
409 return Some(Vec::new());
410 }
411 let two_a = ExactNum::from_u8(2, wrk).mul(&a, wrk, RoundingMode::None);
412 if two_a.is_zero() {
413 return None;
414 }
415 let sqrt_d = disc.sqrt(wrk, RoundingMode::None);
416 let mut r0 =
417 b.neg()
418 .sub(&sqrt_d, wrk, RoundingMode::None)
419 .div(&two_a, wrk, RoundingMode::None);
420 let mut r1 =
421 b.neg()
422 .add(&sqrt_d, wrk, RoundingMode::None)
423 .div(&two_a, wrk, RoundingMode::None);
424 let _ = r0.set_precision(p, rm);
425 let _ = r1.set_precision(p, rm);
426 if disc.is_zero() {
427 Some(vec![r0])
428 } else {
429 Some(vec![r0, r1])
430 }
431 }
432}
433
434impl PartialEq for ExactNumPoly {
435 fn eq(&self, other: &Self) -> bool {
436 if self.coeffs.len() != other.coeffs.len() {
437 return false;
438 }
439 self.coeffs
440 .iter()
441 .zip(other.coeffs.iter())
442 .all(|(a, b)| a == b)
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449 use crate::Consts;
450
451 fn gold_p() -> (usize, RoundingMode) {
452 (256, RoundingMode::ToEven)
453 }
454
455 #[test]
456 fn exact_num_poly_div_rem_gcd_compose_deriv_roots() {
457 let (p, rm) = gold_p();
458 let mut cc = Consts::new().expect("consts");
459
460 let x2m1 = ExactNumPoly::from_i64_coeffs(p, rm, &[-1, 0, 1]);
461 let xm1 = ExactNumPoly::from_i64_coeffs(p, rm, &[-1, 1]);
462 let (q, r) = x2m1.div_rem(&xm1, p, rm).expect("div_rem");
463 assert_eq!(q, ExactNumPoly::from_i64_coeffs(p, rm, &[1, 1]));
464 assert!(r.is_zero());
465
466 let g = x2m1.gcd(&xm1, p, rm);
467 assert_eq!(g, xm1);
468
469 let x2 = ExactNumPoly::from_i64_coeffs(p, rm, &[0, 0, 1]);
470 let xp1 = ExactNumPoly::from_i64_coeffs(p, rm, &[1, 1]);
471 let composed = x2.compose(&xp1, p, rm, &mut cc);
472 assert_eq!(composed, ExactNumPoly::from_i64_coeffs(p, rm, &[1, 2, 1]));
473
474 let x3 = ExactNumPoly::from_i64_coeffs(p, rm, &[0, 0, 0, 1]);
475 let dx = x3.derivative(p, rm);
476 assert_eq!(dx, ExactNumPoly::from_i64_coeffs(p, rm, &[0, 0, 3]));
477
478 let x2m2 = ExactNumPoly::from_i64_coeffs(p, rm, &[-2, 0, 1]);
479 let roots = x2m2.roots_real(p, rm, &mut cc).expect("roots");
480 assert_eq!(roots.len(), 2);
481 let s2 = ExactNum::from_u8(2, p).sqrt(p, rm);
482 let ns2 = s2.neg();
483 let mut saw_pos = false;
484 let mut saw_neg = false;
485 for root in &roots {
486 if root.cmp(&s2) == Some(0) {
487 saw_pos = true;
488 }
489 if root.cmp(&ns2) == Some(0) {
490 saw_neg = true;
491 }
492 }
493 assert!(saw_pos && saw_neg);
494
495 let c = x2m2.companion_matrix(p, rm).expect("companion");
496 assert_eq!(c.shape(), (2, 2));
497 let two = ExactNum::from_u8(2, p);
498 let one = ExactNum::from_u8(1, p);
499 let z = ExactNum::new(p);
500 assert_eq!(c.get2(0, 0).map(|x| x.cmp(&z)), Some(Some(0)));
501 assert_eq!(c.get2(0, 1).map(|x| x.cmp(&two)), Some(Some(0)));
502 assert_eq!(c.get2(1, 0).map(|x| x.cmp(&one)), Some(Some(0)));
503 assert_eq!(c.get2(1, 1).map(|x| x.cmp(&z)), Some(Some(0)));
504
505 assert!(x2m1.div_rem(&ExactNumPoly::zero(p, rm), p, rm).is_none());
506 assert!(x3.roots_real(p, rm, &mut cc).is_none());
507 }
508}