1use std::cmp::Ordering;
33use std::fmt;
34use std::hash::{Hash, Hasher};
35use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
36
37use crate::algebraic::{negative_root, Algebraic};
38use crate::hyperreal::{Hyperreal, PowerError};
39use crate::numeric::Rational;
40
41fn zero() -> Hyperreal {
42 Hyperreal::from_i64(0)
43}
44
45fn constant(c: &Algebraic) -> Hyperreal {
46 Hyperreal::from_algebraic(c.clone())
47}
48
49fn power_of_s(k: i64) -> Hyperreal {
50 Hyperreal::monomial(Algebraic::from_i64(1), k)
51}
52
53fn gcd_i64(a: i64, b: i64) -> i64 {
54 if b == 0 {
55 a.abs()
56 } else {
57 gcd_i64(b, a % b)
58 }
59}
60
61fn lcm_u32(a: u32, b: u32) -> u32 {
62 a / gcd_i64(i64::from(a), i64::from(b)) as u32 * b
63}
64
65#[derive(Clone, Debug, PartialEq, Eq, Hash)]
70struct KPoly {
71 c: Vec<Hyperreal>,
72}
73
74impl KPoly {
75 fn from(c: Vec<Hyperreal>) -> KPoly {
76 let mut c = c;
77 while c.last().is_some_and(Hyperreal::is_zero) {
78 c.pop();
79 }
80 KPoly { c }
81 }
82
83 fn is_zero(&self) -> bool {
84 self.c.is_empty()
85 }
86
87 fn degree(&self) -> Option<usize> {
88 self.c.len().checked_sub(1)
89 }
90
91 fn coeff(&self, i: usize) -> Hyperreal {
92 self.c.get(i).cloned().unwrap_or_else(zero)
93 }
94
95 fn linear(k: &Hyperreal) -> KPoly {
97 KPoly::from(vec![k.neg(), Hyperreal::from_i64(1)])
98 }
99
100 fn add(&self, o: &KPoly) -> KPoly {
101 let n = self.c.len().max(o.c.len());
102 KPoly::from((0..n).map(|i| self.coeff(i).add(&o.coeff(i))).collect())
103 }
104
105 fn mul(&self, o: &KPoly) -> KPoly {
106 if self.is_zero() || o.is_zero() {
107 return KPoly { c: Vec::new() };
108 }
109 let mut out = vec![zero(); self.c.len() + o.c.len() - 1];
110 for (i, a) in self.c.iter().enumerate() {
111 if a.is_zero() {
112 continue;
113 }
114 for (j, b) in o.c.iter().enumerate() {
115 if !b.is_zero() {
116 out[i + j] = out[i + j].add(&a.mul(b));
117 }
118 }
119 }
120 KPoly::from(out)
121 }
122
123 fn scale(&self, k: &Hyperreal) -> KPoly {
124 KPoly::from(self.c.iter().map(|a| a.mul(k)).collect())
125 }
126
127 fn monic(&self) -> KPoly {
128 match self.c.last() {
129 None => self.clone(),
130 Some(lc) => self.scale(&lc.recip().expect("a leading coefficient is nonzero")),
131 }
132 }
133
134 fn div_rem(&self, d: &KPoly) -> (KPoly, KPoly) {
135 let dd = d.degree().expect("the divisor is nonzero");
136 let inv = d.c[dd].recip().expect("a leading coefficient is nonzero");
137 if self.c.len() <= dd {
138 return (KPoly { c: Vec::new() }, self.clone());
139 }
140 let mut r = self.c.clone();
141 let mut q = vec![zero(); r.len() - dd];
142 for k in (0..q.len()).rev() {
143 let coef = r[k + dd].mul(&inv);
144 if !coef.is_zero() {
145 for (j, dj) in d.c.iter().enumerate() {
146 r[k + j] = r[k + j].sub(&coef.mul(dj));
147 }
148 }
149 q[k] = coef;
150 }
151 r.truncate(dd);
152 (KPoly::from(q), KPoly::from(r))
153 }
154
155 fn gcd(&self, o: &KPoly) -> KPoly {
156 let (mut a, mut b) = (self.monic(), o.monic());
157 while !b.is_zero() {
158 let r = a.div_rem(&b).1;
159 a = b;
160 b = r.monic();
161 }
162 a
163 }
164
165 fn derivative(&self) -> KPoly {
166 KPoly::from(
167 self.c
168 .iter()
169 .enumerate()
170 .skip(1)
171 .map(|(i, a)| a.mul(&Hyperreal::from_i64(i as i64)))
172 .collect(),
173 )
174 }
175
176 fn squarefree(&self) -> KPoly {
178 let g = self.gcd(&self.derivative());
179 if g.degree().unwrap_or(0) == 0 {
180 return self.monic();
181 }
182 self.div_rem(&g).0.monic()
183 }
184
185 fn eval(&self, y: &Hyperreal) -> Hyperreal {
186 self.c.iter().rev().fold(zero(), |acc, a| acc.mul(y).add(a))
187 }
188
189 fn shift(&self, a: &Hyperreal) -> KPoly {
191 let mut out = KPoly { c: Vec::new() };
192 let step = KPoly::from(vec![a.clone(), Hyperreal::from_i64(1)]);
193 for c in self.c.iter().rev() {
194 out = out.mul(&step).add(&KPoly::from(vec![c.clone()]));
195 }
196 out
197 }
198
199 fn stretch(&self, k: &Hyperreal) -> KPoly {
201 let mut power = Hyperreal::from_i64(1);
202 let mut out = Vec::with_capacity(self.c.len());
203 for c in &self.c {
204 out.push(c.mul(&power));
205 power = power.mul(k);
206 }
207 KPoly::from(out)
208 }
209
210 fn neg_var(&self) -> KPoly {
212 KPoly::from(
213 self.c
214 .iter()
215 .enumerate()
216 .map(|(i, c)| if i % 2 == 1 { c.neg() } else { c.clone() })
217 .collect(),
218 )
219 }
220
221 fn spread(&self, n: u32) -> KPoly {
223 let mut out = vec![zero(); self.c.len().saturating_sub(1) * n as usize + 1];
224 for (i, c) in self.c.iter().enumerate() {
225 out[i * n as usize] = c.clone();
226 }
227 KPoly::from(out)
228 }
229
230 fn ramify(&self, m: u32) -> KPoly {
232 KPoly {
233 c: self.c.iter().map(|c| c.ramify(m)).collect(),
234 }
235 }
236
237 fn without_zero_root(&self) -> KPoly {
239 let k = self.c.iter().position(|c| !c.is_zero()).unwrap_or(0);
240 KPoly::from(self.c[k..].to_vec())
241 }
242
243 fn resultant(&self, o: &KPoly) -> Hyperreal {
246 let (Some(m), Some(n)) = (self.degree(), o.degree()) else {
247 return zero();
248 };
249 let power = |h: &Hyperreal, k: usize| h.pow(k as i32).expect("a resultant's power is small");
250 if n == 0 {
251 return power(&o.c[0], m);
252 }
253 if m == 0 {
254 return power(&self.c[0], n);
255 }
256 let r = self.div_rem(o).1;
257 let Some(dr) = r.degree() else {
258 return zero();
259 };
260 let v = power(&o.c[n], m - dr).mul(&o.resultant(&r));
261 if (m * n) % 2 == 1 {
262 v.neg()
263 } else {
264 v
265 }
266 }
267
268 fn interpolate(degree: usize, at: impl Fn(i64) -> Hyperreal) -> KPoly {
271 let nodes: Vec<i64> = (0..=degree as i64).collect();
272 let mut values: Vec<Hyperreal> = nodes.iter().map(|j| at(*j)).collect();
273 let n = nodes.len();
274 for j in 1..n {
275 for i in (j..n).rev() {
276 let gap = Hyperreal::from_i64(nodes[i] - nodes[i - j]);
277 values[i] = values[i].sub(&values[i - 1]).div(&gap).expect("the nodes are distinct");
278 }
279 }
280 let mut out = KPoly::from(vec![values[n - 1].clone()]);
281 for i in (0..n - 1).rev() {
282 let factor = KPoly::from(vec![Hyperreal::from_i64(-nodes[i]), Hyperreal::from_i64(1)]);
283 out = out.mul(&factor).add(&KPoly::from(vec![values[i].clone()]));
284 }
285 out
286 }
287
288 fn sum_poly(p: &KPoly, q: &KPoly) -> KPoly {
290 let degree = p.degree().unwrap_or(0) * q.degree().unwrap_or(0);
291 let reflected = q.neg_var();
292 KPoly::interpolate(degree, |j| p.resultant(&reflected.shift(&Hyperreal::from_i64(-j))))
293 }
294
295 fn product_poly(p: &KPoly, q: &KPoly) -> KPoly {
298 let n = q.degree().unwrap_or(0);
299 let degree = p.degree().unwrap_or(0) * n;
300 KPoly::interpolate(degree, |j| {
301 let j = Hyperreal::from_i64(j);
302 let mut c = vec![zero(); n + 1];
303 let mut power = Hyperreal::from_i64(1);
304 for i in 0..=n {
305 c[n - i] = q.coeff(i).mul(&power);
306 power = power.mul(&j);
307 }
308 p.resultant(&KPoly::from(c))
309 })
310 }
311
312 fn power_poly(p: &KPoly, k: u32) -> KPoly {
314 let degree = p.degree().unwrap_or(0);
315 KPoly::interpolate(degree, |j| {
316 let mut c = vec![zero(); k as usize + 1];
317 c[0] = Hyperreal::from_i64(j);
318 c[k as usize] = Hyperreal::from_i64(-1);
319 p.resultant(&KPoly::from(c))
320 })
321 }
322}
323
324#[derive(Clone, Debug)]
329struct Branch {
330 q: u32,
332 terms: Vec<(i64, Algebraic)>,
334 at: i64,
335 rest: KPoly,
336 exact: bool,
338}
339
340impl Branch {
341 fn ramified(&self, m: u32) -> Branch {
343 Branch {
344 q: self.q * m,
345 terms: self.terms.iter().map(|(e, c)| (e * i64::from(m), c.clone())).collect(),
346 at: self.at * i64::from(m),
347 rest: self.rest.ramify(m),
348 exact: self.exact,
349 }
350 }
351
352 fn in_dt(&self, e: i64) -> Rational {
354 Rational::from_ratio_i64(e, i64::from(self.q)).expect("q is positive")
355 }
356
357 fn extend(&mut self) {
359 if self.exact {
360 return;
361 }
362 let (c0, c1) = (self.rest.coeff(0), self.rest.coeff(1));
363 if c0.is_zero() {
364 self.exact = true;
365 return;
366 }
367 let v0 = c0.valuation().expect("nonzero");
368 let v1 = c1.valuation().expect("a separated branch's linear coefficient is nonzero");
369 let gamma = v0 - v1;
370 assert!(gamma > 0, "a separated branch's next term lies past its last");
371 let lead = c0
372 .leading_coefficient()
373 .expect("nonzero")
374 .neg()
375 .div(&c1.leading_coefficient().expect("nonzero"))
376 .expect("nonzero");
377 self.rest = descend(&self.rest, gamma, &lead);
378 self.at += gamma;
379 self.terms.push((self.at, lead));
380 }
381
382 fn through(&mut self, e: &Rational) -> Series {
384 while !self.exact && self.in_dt(self.at) < *e {
385 self.extend();
386 }
387 self.terms
388 .iter()
389 .map(|(k, c)| (self.in_dt(*k), c.clone()))
390 .filter(|(k, _)| k <= e)
391 .collect()
392 }
393
394 fn last(&self) -> Option<Rational> {
396 self.terms.last().map(|(k, _)| self.in_dt(*k))
397 }
398
399 fn coefficient(&mut self, k: i64) -> Algebraic {
401 while !self.exact && self.at < k {
402 self.extend();
403 }
404 self.terms
405 .iter()
406 .find(|(e, _)| *e == k)
407 .map_or_else(|| Algebraic::from_i64(0), |(_, c)| c.clone())
408 }
409}
410
411fn descend(p: &KPoly, gamma: i64, c: &Algebraic) -> KPoly {
413 let stretched = p.stretch(&power_of_s(gamma));
414 let v = stretched
415 .c
416 .iter()
417 .filter_map(Hyperreal::valuation)
418 .min()
419 .expect("a nonzero polynomial");
420 stretched.shift(&constant(c)).scale(&power_of_s(-v))
421}
422
423fn lower_hull(points: &[(i64, i64)]) -> Vec<(i64, i64)> {
425 let mut hull: Vec<(i64, i64)> = Vec::new();
426 for &p in points {
427 while hull.len() >= 2 {
428 let (o, a) = (hull[hull.len() - 2], hull[hull.len() - 1]);
429 let cross = (a.0 - o.0) * (p.1 - o.1) - (a.1 - o.1) * (p.0 - o.0);
430 if cross <= 0 {
431 hull.pop();
432 } else {
433 break;
434 }
435 }
436 hull.push(p);
437 }
438 hull
439}
440
441fn branches(p: &KPoly, q: u32) -> Vec<Branch> {
443 let root = Branch {
444 q,
445 terms: Vec::new(),
446 at: 0,
447 rest: p.clone(),
448 exact: false,
449 };
450 split(root, true)
451}
452
453fn split(b: Branch, top: bool) -> Vec<Branch> {
456 let mut out = Vec::new();
457 let mut rest = b.rest.clone();
458 if rest.coeff(0).is_zero() {
459 out.push(Branch {
460 exact: true,
461 ..b.clone()
462 });
463 rest = rest.without_zero_root();
464 }
465 let points: Vec<(i64, i64)> = rest
466 .c
467 .iter()
468 .enumerate()
469 .filter_map(|(i, c)| c.valuation().map(|v| (i as i64, v)))
470 .collect();
471 for edge in lower_hull(&points).windows(2) {
472 let ((i0, v0), (i1, v1)) = (edge[0], edge[1]);
473 let (num, den) = (v0 - v1, i1 - i0);
474 if !top && num <= 0 {
475 continue;
476 }
477 let g = gcd_i64(num, den).max(1);
478 let (a, d) = (num / g, den / g);
479 let phi: Vec<Algebraic> = (i0..=i1)
480 .map(|i| match points.iter().find(|(j, _)| *j == i) {
481 Some(&(_, v)) if (v - v0) * den == -num * (i - i0) => {
482 rest.coeff(i as usize).leading_coefficient().expect("nonzero")
483 }
484 _ => Algebraic::from_i64(0),
485 })
486 .collect();
487 for (c, multiplicity) in Algebraic::real_roots(&phi) {
488 if c.is_zero() {
489 continue;
490 }
491 let mut child = Branch {
492 rest: rest.clone(),
493 exact: false,
494 ..b.clone()
495 };
496 if d > 1 {
497 child = child.ramified(d as u32);
498 }
499 child.rest = descend(&child.rest, a, &c);
500 child.at += a;
501 child.terms.push((child.at, c));
502 if multiplicity == 1 {
503 out.push(child);
504 } else {
505 out.extend(split(child, false));
506 }
507 }
508 }
509 out
510}
511
512type Series = Vec<(Rational, Algebraic)>;
517
518fn series_add(a: &Series, b: &Series) -> Series {
519 let mut out: Series = Vec::new();
520 let (mut i, mut j) = (0, 0);
521 while i < a.len() || j < b.len() {
522 let take_a = j >= b.len() || (i < a.len() && a[i].0 < b[j].0);
523 let take_b = i >= a.len() || (j < b.len() && b[j].0 < a[i].0);
524 if take_a {
525 out.push(a[i].clone());
526 i += 1;
527 } else if take_b {
528 out.push(b[j].clone());
529 j += 1;
530 } else {
531 let c = a[i].1.add(&b[j].1);
532 if !c.is_zero() {
533 out.push((a[i].0.clone(), c));
534 }
535 i += 1;
536 j += 1;
537 }
538 }
539 out
540}
541
542fn series_neg(a: &Series) -> Series {
543 a.iter().map(|(e, c)| (e.clone(), c.neg())).collect()
544}
545
546fn series_mul(a: &Series, b: &Series, e: &Rational) -> Series {
548 let mut out: Series = Vec::new();
549 for (ea, ca) in a {
550 for (eb, cb) in b {
551 let k = ea.add(eb);
552 if k > *e {
553 continue;
554 }
555 out = series_add(&out, &vec![(k, ca.mul(cb))]);
556 }
557 }
558 out
559}
560
561fn series_compose(u: &Series, e: &Rational, mut weight: impl FnMut(usize) -> Algebraic) -> Series {
564 let mut out: Series = Vec::new();
565 let mut power: Series = vec![(Rational::zero(), Algebraic::from_i64(1))];
566 let mut k = 0;
567 while !power.is_empty() {
568 let w = weight(k);
569 if !w.is_zero() {
570 out = series_add(&out, &power.iter().map(|(x, c)| (x.clone(), c.mul(&w))).collect());
571 }
572 power = series_mul(&power, u, e);
573 k += 1;
574 }
575 out
576}
577
578fn series_split(a: &Series) -> (Rational, Algebraic, Series) {
580 let (v, c) = a[0].clone();
581 let inv = c.recip().expect("a leading coefficient is nonzero");
582 let u = a[1..].iter().map(|(x, k)| (x.sub(&v), k.mul(&inv))).collect();
583 (v, c, u)
584}
585
586fn shift_series(a: &Series, by: &Rational, scale: &Algebraic) -> Series {
587 a.iter().map(|(x, c)| (x.add(by), c.mul(scale))).collect()
588}
589
590fn series_recip(a: &Series, e: &Rational) -> Series {
592 let (v, c, u) = series_split(a);
593 let reach = e.add(&v);
594 let minus_u = series_neg(&u);
595 let body = series_compose(&minus_u, &reach, |_| Algebraic::from_i64(1));
596 shift_series(&body, &v.negated(), &c.recip().expect("nonzero"))
597}
598
599fn series_root(a: &Series, n: u32, e: &Rational) -> Result<Series, String> {
602 let (v, c, u) = series_split(a);
603 let lead = c.nth_root(n).ok_or_else(|| negative_root(n))?;
604 let order = v.div(&Rational::from_i64(i64::from(n))).expect("n is positive");
605 let reach = e.sub(&order);
606 let alpha = Rational::from_ratio_i64(1, i64::from(n)).expect("n is positive");
607 let mut binomial = Rational::one();
608 let body = series_compose(&u, &reach, |k| {
609 if k > 0 {
610 let k = Rational::from_i64(k as i64);
611 binomial = binomial.mul(&alpha.sub(&k.sub(&Rational::one()))).div(&k).expect("k ≥ 1");
612 }
613 Algebraic::from_rational(binomial.clone())
614 });
615 Ok(shift_series(&body, &order, &lead))
616}
617
618#[derive(Clone)]
623pub struct Puiseux {
624 poly: KPoly,
625 q: u32,
626 function: Option<Hyperreal>,
628 picked: Arc<OnceLock<Picked>>,
632}
633
634struct Picked {
637 branch: Mutex<Branch>,
638 separation: Rational,
639}
640
641#[derive(Clone, Debug, PartialEq, Eq)]
644pub struct PowerRefusal {
645 pub error: PowerError,
646 pub q: u32,
647}
648
649impl PowerRefusal {
650 pub fn describe(&self, name: &str) -> String {
652 self.error.describe(&power_text(name, &ratio(1, self.q)))
653 }
654}
655
656#[derive(Clone, Debug)]
659pub enum Closed {
660 Field(Hyperreal),
661 Series(Puiseux),
662}
663
664fn separation_of(bs: &[Branch]) -> Rational {
666 bs.iter().filter_map(Branch::last).max().unwrap_or_else(Rational::zero)
667}
668
669impl Puiseux {
670 fn select(poly: KPoly, q: u32, target: &dyn Fn(&Rational) -> Series) -> Puiseux {
673 let poly = poly.squarefree();
674 let bs = branches(&poly, q);
675 let separation = separation_of(&bs);
676 let want = target(&separation);
677 let chosen = bs
678 .into_iter()
679 .find_map(|mut b| (b.through(&separation) == want).then_some(b))
680 .expect("the operation's value is a real root of its polynomial");
681 Puiseux::picked_as(poly, q, chosen, separation)
682 }
683
684 fn picked_as(poly: KPoly, q: u32, branch: Branch, separation: Rational) -> Puiseux {
685 let function = (poly.degree() == Some(1))
686 .then(|| poly.coeff(0).neg().div(&poly.coeff(1)).expect("degree one"));
687 Puiseux {
688 poly,
689 q,
690 function,
691 picked: Arc::new(OnceLock::from(Picked {
692 branch: Mutex::new(branch),
693 separation,
694 })),
695 }
696 }
697
698 fn of_field(h: &Hyperreal, q: u32) -> Puiseux {
700 Puiseux {
701 poly: KPoly::linear(h),
702 q,
703 function: Some(h.clone()),
704 picked: Arc::new(OnceLock::new()),
705 }
706 }
707
708 fn picked(&self) -> &Picked {
710 self.picked.get_or_init(|| {
711 assert_eq!(self.poly.degree(), Some(1), "only the root of `y − h` is picked when first asked for");
712 let bs = branches(&self.poly, self.q);
713 let separation = separation_of(&bs);
714 let branch = bs.into_iter().next().expect("y − h has its root");
715 Picked {
716 branch: Mutex::new(branch),
717 separation,
718 }
719 })
720 }
721
722 fn branch(&self) -> MutexGuard<'_, Branch> {
723 self.picked().branch.lock().expect("unpoisoned")
724 }
725
726 fn separation(&self) -> Rational {
729 match self.function {
730 Some(_) => self.leading().map_or_else(Rational::zero, |(v, _)| v),
731 None => self.picked().separation.clone(),
732 }
733 }
734
735 fn through(&self, e: &Rational) -> Series {
737 let (Some(h), q) = (&self.function, self.q) else {
738 return self.branch().through(e);
739 };
740 let Some(v) = h.valuation() else {
741 return Vec::new();
742 };
743 let top = e.mul(&Rational::from_i64(i64::from(q))).floor().to_i64().expect("a small order");
744 (v..=top)
745 .filter_map(|k| {
746 let c = h.laurent_coefficient(k);
747 (!c.is_zero()).then(|| (Rational::from_ratio_i64(k, i64::from(q)).expect("q is positive"), c))
748 })
749 .collect()
750 }
751
752 pub fn ramification(&self) -> u32 {
756 match self.function {
757 Some(_) => self.q,
758 None => self.branch().q,
759 }
760 }
761
762 pub fn coefficient(&self, k: i64) -> Algebraic {
764 match &self.function {
765 Some(h) => h.laurent_coefficient(k),
766 None => self.branch().coefficient(k),
767 }
768 }
769
770 pub(crate) fn annihilator(&self) -> (Vec<Hyperreal>, u32) {
773 (self.poly.c.clone(), self.q)
774 }
775
776 pub fn rational_function(&self) -> Option<(Hyperreal, u32)> {
779 self.function.clone().map(|h| (h, self.q))
780 }
781
782 pub fn order(&self) -> i64 {
784 match &self.function {
785 Some(h) => h.valuation().expect("a series value is nonzero"),
786 None => self.branch().terms.first().expect("a series value is nonzero").0,
787 }
788 }
789
790 fn leading(&self) -> Option<(Rational, Algebraic)> {
792 if let Some(h) = &self.function {
793 let v = h.valuation()?;
794 let c = h.leading_coefficient()?;
795 return Some((Rational::from_ratio_i64(v, i64::from(self.q)).expect("q is positive"), c));
796 }
797 let b = self.branch();
798 b.terms.first().map(|(k, c)| (b.in_dt(*k), c.clone()))
799 }
800
801 fn signum(&self) -> i32 {
802 self.leading().map_or(0, |(_, c)| c.signum())
803 }
804
805 fn poly_over(&self, q: u32) -> KPoly {
807 self.poly.ramify(q / self.q)
808 }
809
810 fn plus(&self, o: &Puiseux) -> Puiseux {
811 let q = lcm_u32(self.q, o.q);
812 let poly = KPoly::sum_poly(&self.poly_over(q), &o.poly_over(q));
813 Puiseux::select(poly, q, &|e| series_add(&self.through(e), &o.through(e)))
814 }
815
816 fn negated(&self) -> Puiseux {
818 if let Some(h) = &self.function {
819 return Puiseux::of_field(&h.neg(), self.q);
820 }
821 let b = self.branch().clone();
822 let branch = Branch {
823 terms: b.terms.iter().map(|(k, c)| (*k, c.neg())).collect(),
824 rest: b.rest.neg_var(),
825 ..b
826 };
827 Puiseux::picked_as(self.poly.neg_var(), self.q, branch, self.separation())
828 }
829
830 fn times(&self, o: &Puiseux) -> Puiseux {
832 let q = lcm_u32(self.q, o.q);
833 let (va, _) = self.leading().expect("nonzero");
834 let (vb, _) = o.leading().expect("nonzero");
835 let poly = KPoly::product_poly(&self.poly_over(q).without_zero_root(), &o.poly_over(q).without_zero_root());
836 Puiseux::select(poly, q, &|e| series_mul(&self.through(&e.sub(&vb)), &o.through(&e.sub(&va)), e))
837 }
838
839 fn inverse(&self) -> Puiseux {
841 let (v, _) = self.leading().expect("nonzero");
842 let mut reversed = self.poly.without_zero_root().c;
843 reversed.reverse();
844 let target = |e: &Rational| series_recip(&self.through(&e.add(&v).add(&v)), e);
845 Puiseux::select(KPoly::from(reversed), self.q, &target)
846 }
847
848 fn power(&self, k: u32) -> Puiseux {
851 let (v, _) = self.leading().expect("nonzero");
852 let target = |e: &Rational| {
853 let base = self.through(&e.sub(&v.mul(&Rational::from_i64(i64::from(k) - 1))));
854 let mut acc: Series = vec![(Rational::zero(), Algebraic::from_i64(1))];
855 for done in 1..=k {
856 let to_come = Rational::from_i64(i64::from(k - done));
857 acc = series_mul(&acc, &base, &e.sub(&v.mul(&to_come)));
858 }
859 acc
860 };
861 Puiseux::select(KPoly::power_poly(&self.poly, k), self.q, &target)
862 }
863
864 fn nth_root(&self, n: u32) -> Result<Puiseux, String> {
867 let (v, c) = self.leading().expect("nonzero");
868 if n % 2 == 0 && c.signum() < 0 {
869 return Err(negative_root(n));
870 }
871 let order = v.div(&Rational::from_i64(i64::from(n))).expect("n is positive");
872 let target = |e: &Rational| {
873 series_root(&self.through(&e.sub(&order).add(&v)), n, e).expect("the sign was checked")
874 };
875 Ok(Puiseux::select(self.poly.spread(n), self.q, &target))
876 }
877
878 fn field_value(&self) -> Option<(Hyperreal, u32)> {
885 if let Some(h) = &self.function {
886 return Some((h.clone(), self.q));
887 }
888 let (qb, exact, terms) = {
889 let b = self.branch();
890 (b.q, b.exact, b.terms.clone())
891 };
892 if exact {
893 let sum = terms.iter().fold(zero(), |acc, (k, c)| acc.add(&Hyperreal::monomial(c.clone(), *k)));
894 return Some((sum, qb));
895 }
896 let poly = self.poly_over(qb).monic();
897 let degrees: Vec<(usize, usize)> = poly.c.iter().map(Hyperreal::degrees).collect();
898 let lcm_bound: usize = degrees.iter().map(|(_, d)| d).sum();
899 let (n0, d0) = degrees[0];
900 let num_bound = (lcm_bound + n0).checked_sub(d0)?;
901 let q = Rational::from_i64(i64::from(qb));
902 let (v, _) = self.leading()?;
903 let v_s = v.mul(&q);
904 let v_s = v_s.to_bigint().filter(|_| v_s.is_integer())?.to_i64()?;
905 let (l, m) = if v_s >= 0 {
906 (num_bound.checked_sub(usize::try_from(v_s).ok()?)?, lcm_bound)
907 } else {
908 (num_bound, lcm_bound.checked_sub(usize::try_from(-v_s).ok()?)?)
909 };
910 let reach = Rational::from_i64(v_s + (l + m) as i64).div(&q).expect("q is positive");
911 let mut series = vec![Algebraic::from_i64(0); l + m + 1];
912 for (e, c) in self.through(&reach) {
913 let e_s = e.mul(&q);
914 if !e_s.is_integer() {
915 return None;
916 }
917 let k = e_s.to_bigint()?.to_i64()? - v_s;
918 series[usize::try_from(k).ok()?] = c;
919 }
920 let candidate = Hyperreal::pade(&series, l, m)?.mul(&power_of_s(v_s));
921 if !poly.eval(&candidate).is_zero() {
922 return None;
923 }
924 let check = Puiseux::of_field(&candidate, qb);
925 let separation = self.separation();
926 (check.through(&separation) == self.through(&separation)).then_some((candidate, qb))
927 }
928
929 pub(crate) fn canonical(self) -> Closed {
931 match self.field_value() {
932 Some((h, q)) => Closed::of_field(h, q),
933 None => Closed::Series(self),
934 }
935 }
936
937 fn render(&self, name: &str) -> String {
940 let q = self.q;
941 if let Some(h) = &self.function {
942 return h.render_powers(&|k| power_text(name, &ratio(k, q)));
943 }
944 if let Some(text) = self.radical(name) {
945 return text;
946 }
947 let mut b = self.branch();
948 while b.terms.len() < 3 && !b.exact {
949 b.extend();
950 }
951 let terms: Vec<(Rational, Algebraic)> = b.terms.iter().map(|(k, c)| (b.in_dt(*k), c.clone())).collect();
952 drop(b);
953 let mut out = String::new();
954 for (i, (e, c)) in terms.iter().take(2).enumerate() {
955 let t = term_text(name, c, e);
956 match (i, t.strip_prefix('-')) {
957 (0, _) => out.push_str(&t),
958 (_, Some(rest)) => {
959 out.push_str(" - ");
960 out.push_str(rest);
961 }
962 (_, None) => {
963 out.push_str(" + ");
964 out.push_str(&t);
965 }
966 }
967 }
968 if let Some((next, _)) = terms.get(2) {
969 out.push_str(&format!(" + {}", remainder(name, &remainder_order(&terms[..2], next))));
970 }
971 out
972 }
973
974 fn radical(&self, name: &str) -> Option<String> {
979 let degree = self.poly.degree().unwrap_or(0) as u32;
980 let negative = self.signum() < 0;
981 for n in 2..=degree.max(2) {
982 let Some((kappa, q)) = self.power(n).field_value() else {
983 continue;
984 };
985 let powers = |k: usize| power_text(name, &ratio(k, q));
986 let (kappa, inverted) = if kappa.numerator_is_constant() && kappa.degrees().1 > 0 {
987 (kappa.recip()?, true)
988 } else {
989 (kappa, false)
990 };
991 let (outside, inside) = kappa.root_split(n)?;
992 let outside = if n % 2 == 0 && negative { outside.neg() } else { outside };
993 let radicand = inside.render_powers(&powers);
994 let radicand = if radicand.contains([' ', '/', '·', '^']) {
995 format!("({radicand})")
996 } else {
997 radicand
998 };
999 let sign = radical_sign(n);
1000 let factor = outside.render_powers(&powers);
1001 let (minus, factor) = match factor.strip_prefix('-') {
1002 Some(rest) if !rest.contains(' ') => ("-", rest.to_string()),
1003 _ => ("", factor),
1004 };
1005 let factor = if factor.contains([' ', '/']) { format!("({factor})") } else { factor };
1006 let root = if factor == "1" {
1007 format!("{sign}{radicand}")
1008 } else {
1009 format!("{factor}·{sign}{radicand}")
1010 };
1011 return Some(if inverted {
1012 let root = if root.contains('·') { format!("({root})") } else { root };
1013 format!("{minus}1/{root}")
1014 } else {
1015 format!("{minus}{root}")
1016 });
1017 }
1018 None
1019 }
1020}
1021
1022fn remainder_order(shown: &[(Rational, Algebraic)], next: &Rational) -> Rational {
1027 let exponents: Vec<Rational> = shown.iter().map(|(e, _)| e.clone()).collect();
1028 remainder_after(&exponents, next)
1029}
1030
1031pub(crate) fn remainder_after(shown: &[Rational], next: &Rational) -> Rational {
1033 let grid = shown
1034 .iter()
1035 .map(|e| e.denominator().to_i64().expect("a small denominator"))
1036 .fold(1i64, |l, d| l / gcd_i64(l, d) * d);
1037 let after = shown
1038 .last()
1039 .map(|e| e.add(&Rational::from_ratio_i64(1, grid).expect("grid > 0")))
1040 .expect("terms are shown");
1041 if *next < after {
1042 next.clone()
1043 } else {
1044 after
1045 }
1046}
1047
1048fn ratio(k: usize, q: u32) -> Rational {
1050 Rational::from_ratio_i64(k as i64, i64::from(q)).expect("q is positive")
1051}
1052
1053impl Closed {
1054 pub fn from_hyperreal(h: Hyperreal) -> Closed {
1056 Closed::Field(h)
1057 }
1058
1059 pub(crate) fn of_field(h: Hyperreal, q: u32) -> Closed {
1062 let mut h = h;
1063 let mut q = q;
1064 for p in 2..=q {
1065 while q % p == 0 {
1066 match h.unramify(p) {
1067 Some(r) => {
1068 h = r;
1069 q /= p;
1070 }
1071 None => break,
1072 }
1073 }
1074 }
1075 if q == 1 {
1076 Closed::Field(h)
1077 } else {
1078 Closed::Series(Puiseux::of_field(&h, q))
1079 }
1080 }
1081
1082 pub fn as_standard(&self) -> Option<Algebraic> {
1084 match self {
1085 Closed::Field(h) => h.as_standard(),
1086 Closed::Series(_) => None,
1087 }
1088 }
1089
1090 pub fn leading(&self) -> Option<(Rational, Algebraic)> {
1092 match self {
1093 Closed::Field(h) => Some((Rational::from_i64(h.valuation()?), h.leading_coefficient()?)),
1094 Closed::Series(p) => p.leading(),
1095 }
1096 }
1097
1098 pub(crate) fn field(&self) -> Option<(Hyperreal, u32)> {
1100 match self {
1101 Closed::Field(h) => Some((h.clone(), 1)),
1102 Closed::Series(p) => p.rational_function(),
1103 }
1104 }
1105
1106 fn on_one_grid(&self, o: &Closed) -> Option<(Hyperreal, Hyperreal, u32)> {
1109 let (a, qa) = self.field()?;
1110 let (b, qb) = o.field()?;
1111 let q = lcm_u32(qa, qb);
1112 Some((a.ramify(q / qa), b.ramify(q / qb), q))
1113 }
1114
1115 fn series(&self) -> Puiseux {
1117 match self {
1118 Closed::Field(h) => Puiseux::of_field(h, 1),
1119 Closed::Series(p) => p.clone(),
1120 }
1121 }
1122
1123 pub fn is_zero(&self) -> bool {
1124 matches!(self, Closed::Field(h) if h.is_zero())
1125 }
1126
1127 pub fn add(&self, o: &Closed) -> Closed {
1128 if let Some((a, b, q)) = self.on_one_grid(o) {
1129 return Closed::of_field(a.add(&b), q);
1130 }
1131 self.series().plus(&o.series()).canonical()
1132 }
1133
1134 pub fn neg(&self) -> Closed {
1135 match self {
1136 Closed::Field(h) => Closed::Field(h.neg()),
1137 Closed::Series(p) => Closed::Series(p.negated()),
1138 }
1139 }
1140
1141 pub fn sub(&self, o: &Closed) -> Closed {
1142 self.add(&o.neg())
1143 }
1144
1145 pub fn mul(&self, o: &Closed) -> Closed {
1146 if let Some((a, b, q)) = self.on_one_grid(o) {
1147 return Closed::of_field(a.mul(&b), q);
1148 }
1149 if self.is_zero() || o.is_zero() {
1150 return Closed::Field(zero());
1151 }
1152 self.series().times(&o.series()).canonical()
1153 }
1154
1155 pub fn recip(&self) -> Option<Closed> {
1158 match self {
1159 Closed::Field(h) => h.recip().map(Closed::Field),
1160 Closed::Series(p) if p.poly.degree() == Some(1) => p.clone().canonical_recip(),
1161 Closed::Series(p) => Some(Closed::Series(p.inverse())),
1162 }
1163 }
1164
1165 pub fn div(&self, o: &Closed) -> Option<Closed> {
1166 Some(self.mul(&o.recip()?))
1167 }
1168
1169 pub fn pow(&self, k: i32) -> Result<Closed, PowerRefusal> {
1172 if k < 0 {
1173 let inverse = self.recip().ok_or(PowerRefusal {
1174 error: PowerError::ZeroToNegative,
1175 q: 1,
1176 })?;
1177 return inverse.pow(-k);
1178 }
1179 if k == 0 {
1180 return Ok(Closed::Field(Hyperreal::from_i64(1)));
1181 }
1182 if let Some((h, q)) = self.field() {
1183 let power = h.pow(k).map_err(|error| PowerRefusal { error, q })?;
1184 return Ok(Closed::of_field(power, q));
1185 }
1186 match self {
1187 Closed::Field(_) => unreachable!("a hyperreal is a rational function of dt"),
1188 Closed::Series(p) => Ok(p.power(k as u32).canonical()),
1189 }
1190 }
1191
1192 pub fn root(&self, n: u32) -> Result<Closed, String> {
1194 if n == 1 || self.is_zero() {
1195 return Ok(self.clone());
1196 }
1197 Ok(self.series().nth_root(n)?.canonical())
1198 }
1199
1200 pub fn signum(&self) -> i32 {
1202 match self {
1203 Closed::Field(h) => h.signum(),
1204 Closed::Series(p) => p.signum(),
1205 }
1206 }
1207
1208 pub fn compare(&self, o: &Closed) -> Ordering {
1213 if let Some((a, b, _)) = self.on_one_grid(o) {
1214 return a.cmp(&b);
1215 }
1216 let (a, b) = (self.series(), o.series());
1217 let depth = a.separation().max(b.separation());
1218 let differ = series_add(&a.through(&depth), &series_neg(&b.through(&depth)));
1219 if let Some((_, c)) = differ.first() {
1220 return if c.signum() < 0 { Ordering::Less } else { Ordering::Greater };
1221 }
1222 match a.plus(&b.negated()).signum() {
1223 -1 => Ordering::Less,
1224 0 => Ordering::Equal,
1225 _ => Ordering::Greater,
1226 }
1227 }
1228
1229 pub fn valuation(&self) -> Option<Rational> {
1231 match self {
1232 Closed::Field(h) => h.valuation().map(Rational::from_i64),
1233 Closed::Series(p) => p.leading().map(|(v, _)| v),
1234 }
1235 }
1236
1237 pub fn is_infinitesimal(&self) -> bool {
1238 self.valuation().is_none_or(|v| v.is_positive())
1239 }
1240
1241 pub fn is_limited(&self) -> bool {
1242 self.valuation().is_none_or(|v| !v.is_negative())
1243 }
1244
1245 pub fn standard_part(&self) -> Option<Algebraic> {
1247 match self {
1248 Closed::Field(h) => h.standard_part(),
1249 Closed::Series(p) => {
1250 let (v, c) = p.leading().expect("a series value is nonzero");
1251 if v.is_positive() {
1252 Some(Algebraic::from_i64(0))
1253 } else if v.is_zero() {
1254 Some(c)
1255 } else {
1256 None
1257 }
1258 }
1259 }
1260 }
1261
1262 pub fn render(&self, name: &str) -> String {
1264 match self {
1265 Closed::Field(h) => h.render(name),
1266 Closed::Series(p) => p.render(name),
1267 }
1268 }
1269}
1270
1271impl Puiseux {
1272 fn canonical_recip(self) -> Option<Closed> {
1274 let (h, q) = self.field_value()?;
1275 Some(Closed::of_field(h.recip()?, q))
1276 }
1277}
1278
1279fn radical_sign(n: u32) -> String {
1280 match n {
1281 2 => "√".to_string(),
1282 3 => "∛".to_string(),
1283 4 => "∜".to_string(),
1284 n => {
1285 let digits: String = n
1286 .to_string()
1287 .chars()
1288 .map(|d| ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'][d.to_digit(10).expect("a digit") as usize])
1289 .collect();
1290 format!("{digits}√")
1291 }
1292 }
1293}
1294
1295pub(crate) fn power_text(name: &str, e: &Rational) -> String {
1297 let one = Rational::one();
1298 if *e == one {
1299 return name.to_string();
1300 }
1301 if e.is_integer() {
1302 return format!("{name}^{e}");
1303 }
1304 let half = Rational::from_ratio_i64(1, 2).expect("2 > 0");
1305 let third = Rational::from_ratio_i64(1, 3).expect("3 > 0");
1306 if *e == half {
1307 return format!("√{name}");
1308 }
1309 if *e == third {
1310 return format!("∛{name}");
1311 }
1312 format!("{name}^({e})")
1313}
1314
1315pub(crate) fn remainder(name: &str, e: &Rational) -> String {
1317 if e.is_zero() {
1318 "O(1)".to_string()
1319 } else {
1320 format!("O({})", power_text(name, e))
1321 }
1322}
1323
1324fn term_text(name: &str, c: &Algebraic, e: &Rational) -> String {
1325 expansion_term(name, &c.to_string(), e)
1326}
1327
1328pub(crate) fn expansion_term(name: &str, text: &str, e: &Rational) -> String {
1332 if e.is_zero() {
1333 return text.to_string();
1334 }
1335 let (sign, magnitude) = match text.strip_prefix('-') {
1336 Some(m) if !top_level(m, &[' ']) => ("-", m),
1337 _ => ("", text),
1338 };
1339 let magnitude = if top_level(magnitude, &[' ', '/']) {
1340 format!("({magnitude})")
1341 } else {
1342 magnitude.to_string()
1343 };
1344 if e.is_negative() {
1345 return format!("{sign}{magnitude}/{}", power_text(name, &e.negated()));
1346 }
1347 let power = power_text(name, e);
1348 if magnitude == "1" {
1349 format!("{sign}{power}")
1350 } else {
1351 format!("{sign}{magnitude}·{power}")
1352 }
1353}
1354
1355pub(crate) fn top_level(text: &str, marks: &[char]) -> bool {
1357 let mut depth = 0i32;
1358 for ch in text.chars() {
1359 match ch {
1360 '(' => depth += 1,
1361 ')' => depth -= 1,
1362 ch if depth == 0 && marks.contains(&ch) => return true,
1363 _ => {}
1364 }
1365 }
1366 false
1367}
1368
1369impl fmt::Debug for Puiseux {
1370 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1371 write!(f, "Puiseux {{ q: {}, degree: {:?}, leading: {:?} }}", self.q, self.poly.degree(), self.leading())
1372 }
1373}
1374
1375impl PartialEq for Closed {
1376 fn eq(&self, o: &Self) -> bool {
1377 match (self, o) {
1378 (Closed::Field(a), Closed::Field(b)) => a == b,
1379 (Closed::Series(_), Closed::Series(_)) => self.compare(o) == Ordering::Equal,
1380 _ => false,
1381 }
1382 }
1383}
1384
1385impl Eq for Closed {}
1386
1387impl Hash for Closed {
1388 fn hash<H: Hasher>(&self, state: &mut H) {
1391 match self {
1392 Closed::Field(h) => {
1393 0u8.hash(state);
1394 h.hash(state);
1395 }
1396 Closed::Series(p) => {
1397 1u8.hash(state);
1398 let (e, c) = p.leading().expect("a series value is nonzero");
1399 e.hash(state);
1400 c.hash(state);
1401 }
1402 }
1403 }
1404}
1405
1406impl crate::exact_codec::ExactCodec for Puiseux {
1407 fn write(&self, out: &mut Vec<u8>) {
1410 (u64::from(self.q)).write(out);
1411 crate::exact_codec::write_list(&self.poly.c, out);
1412 let terms = self.through(&self.separation());
1413 crate::exact_codec::write_len(terms.len(), out);
1414 for (e, c) in &terms {
1415 e.write(out);
1416 c.write(out);
1417 }
1418 }
1419
1420 fn read(buf: &[u8], pos: &mut usize) -> Option<Self> {
1421 use crate::exact_codec::{read_len, MAX_WIRE_DEGREE};
1422 let q = u32::try_from(u64::read(buf, pos)?).ok().filter(|q| *q > 0)?;
1423 let poly = KPoly::from(crate::exact_codec::read_list(buf, pos)?);
1424 if poly.degree().unwrap_or(0) == 0 || poly.squarefree().degree() != poly.degree() {
1425 return None;
1426 }
1427 let count = read_len(buf, pos, MAX_WIRE_DEGREE)?;
1428 let mut want: Series = Vec::with_capacity(count);
1429 for _ in 0..count {
1430 want.push((Rational::read(buf, pos)?, Algebraic::read(buf, pos)?));
1431 }
1432 let bs = branches(&poly, q);
1433 let separation = separation_of(&bs);
1434 let chosen = bs.into_iter().find_map(|mut b| (b.through(&separation) == want).then_some(b))?;
1435 Some(Puiseux::picked_as(poly, q, chosen, separation))
1436 }
1437}
1438
1439impl Closed {
1440 pub fn encode(&self, out: &mut Vec<u8>) {
1442 use crate::exact_codec::ExactCodec;
1443 match self {
1444 Closed::Field(h) => {
1445 out.push(0);
1446 h.write(out);
1447 }
1448 Closed::Series(p) => {
1449 out.push(1);
1450 p.write(out);
1451 }
1452 }
1453 }
1454
1455 pub fn decode(buf: &[u8], pos: &mut usize) -> Option<Closed> {
1456 use crate::exact_codec::{read_byte, ExactCodec};
1457 match read_byte(buf, pos)? {
1458 0 => Some(Closed::Field(Hyperreal::read(buf, pos)?)),
1459 1 => Some(Puiseux::read(buf, pos)?.canonical()),
1460 _ => None,
1461 }
1462 }
1463}
1464
1465#[cfg(test)]
1466#[path = "../../logicaffeine_tests/unit/logicaffeine_base/puiseux/tests.rs"]
1467mod tests;