1use crate::algebraic::Algebraic;
24use crate::ordered_field::PolyOver;
25use crate::numeric::{BigInt, Rational};
26use std::cmp::Ordering;
27use std::fmt;
28use std::hash::{Hash, Hasher};
29
30pub const MAX_POWER_DEGREE: u64 = 1 << 12;
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub enum PowerError {
36 ZeroToNegative,
38 DegreeTooHigh { degree: u128 },
40}
41
42impl PowerError {
43 pub fn describe(&self, inf: &str) -> String {
45 match self {
46 PowerError::ZeroToNegative => "zero cannot be raised to a negative power".to_string(),
47 PowerError::DegreeTooHigh { degree } => format!(
48 "the power would have degree {degree} in {inf}; one power reaches at most degree 4,096"
49 ),
50 }
51 }
52}
53
54impl fmt::Display for PowerError {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 f.write_str(&self.describe("the infinitesimal"))
57 }
58}
59
60pub(crate) fn check_power_degree(degree: usize, k: u64) -> Result<(), PowerError> {
63 let reach = degree as u128 * u128::from(k);
64 if reach > u128::from(MAX_POWER_DEGREE) {
65 return Err(PowerError::DegreeTooHigh { degree: reach });
66 }
67 Ok(())
68}
69
70type APoly = PolyOver<Algebraic>;
72
73#[derive(Clone, Debug)]
75pub struct Hyperreal {
76 num: APoly,
77 den: APoly,
78}
79
80impl Hyperreal {
81 fn make(num: APoly, den: APoly) -> Hyperreal {
83 if num.is_zero() {
84 return Hyperreal {
85 num,
86 den: APoly::one(),
87 };
88 }
89 if den.degree() == Some(0) {
90 let inv = den.c[0].recip().expect("the denominator is nonzero");
91 return Hyperreal {
92 num: num.scale(&inv),
93 den: APoly::one(),
94 };
95 }
96 let g = num.gcd(&den);
97 let num = num.div_rem(&g).0;
98 let den = den.div_rem(&g).0;
99 let low = den.coeff(den.order().expect("the denominator is nonzero"));
100 let inv = low.recip().expect("nonzero");
101 Hyperreal {
102 num: num.scale(&inv),
103 den: den.scale(&inv),
104 }
105 }
106
107 pub(crate) fn from_parts(num: APoly, den: APoly) -> Hyperreal {
109 Self::make(num, den)
110 }
111
112 pub(crate) fn parts(&self) -> (&APoly, &APoly) {
114 (&self.num, &self.den)
115 }
116
117 pub fn dt() -> Hyperreal {
119 Hyperreal {
120 num: APoly::from(vec![Algebraic::from_i64(0), Algebraic::from_i64(1)]),
121 den: APoly::one(),
122 }
123 }
124
125 pub fn from_algebraic(a: Algebraic) -> Hyperreal {
126 Hyperreal {
127 num: APoly::constant(a),
128 den: APoly::one(),
129 }
130 }
131
132 pub fn from_rational(r: Rational) -> Hyperreal {
133 Self::from_algebraic(Algebraic::from_rational(r))
134 }
135
136 pub fn from_i64(n: i64) -> Hyperreal {
137 Self::from_algebraic(Algebraic::from_i64(n))
138 }
139
140 pub fn from_reciprocal_powers(c: &[Rational]) -> Hyperreal {
145 let Some(d) = c.iter().rposition(|x| !x.is_zero()) else {
146 return Self::from_i64(0);
147 };
148 let num = APoly::from((0..=d).map(|j| Algebraic::from_rational(c[d - j].clone())).collect());
149 let mut den = vec![Algebraic::from_i64(0); d + 1];
150 den[d] = Algebraic::from_i64(1);
151 Hyperreal {
152 num,
153 den: APoly::from(den),
154 }
155 }
156
157 pub fn is_zero(&self) -> bool {
158 self.num.is_zero()
159 }
160
161 pub fn add(&self, o: &Hyperreal) -> Hyperreal {
162 if self.den == o.den {
163 return Self::make(self.num.add(&o.num), self.den.clone());
164 }
165 Self::make(
166 self.num.mul(&o.den).add(&o.num.mul(&self.den)),
167 self.den.mul(&o.den),
168 )
169 }
170
171 pub fn neg(&self) -> Hyperreal {
172 Hyperreal {
173 num: self.num.neg(),
174 den: self.den.clone(),
175 }
176 }
177
178 pub fn sub(&self, o: &Hyperreal) -> Hyperreal {
179 self.add(&o.neg())
180 }
181
182 pub fn mul(&self, o: &Hyperreal) -> Hyperreal {
183 Self::make(self.num.mul(&o.num), self.den.mul(&o.den))
184 }
185
186 pub fn recip(&self) -> Option<Hyperreal> {
187 if self.is_zero() {
188 return None;
189 }
190 Some(Self::make(self.den.clone(), self.num.clone()))
191 }
192
193 pub fn div(&self, o: &Hyperreal) -> Option<Hyperreal> {
194 Some(self.mul(&o.recip()?))
195 }
196
197 pub fn as_reciprocal_powers(&self) -> Option<Vec<Rational>> {
202 if self.num.is_zero() {
203 return Some(Vec::new());
204 }
205 let d = self.den.degree()?;
206 if self.den.order() != Some(d) || self.num.degree()? > d {
207 return None;
208 }
209 (0..=d).map(|i| self.num.coeff(d - i).to_rational()).collect()
210 }
211
212 pub fn principal_part(&self) -> (Vec<Algebraic>, Hyperreal) {
217 let k = match self.valuation() {
218 Some(v) if v < 0 => (-v) as usize,
219 _ => return (Vec::new(), self.clone()),
220 };
221 let numerator = &self.num.c[self.num.order().expect("a pole is nonzero")..];
222 let unit = &self.den.c[self.den.order().expect("the denominator is nonzero")..];
223 let unit_inv = unit[0].recip().expect("the unit's constant term is nonzero");
224 let zero = Algebraic::from_i64(0);
225 let mut series: Vec<Algebraic> = Vec::with_capacity(k);
226 for i in 0..k {
227 let mut acc = numerator.get(i).cloned().unwrap_or_else(|| zero.clone());
228 for j in 1..=i {
229 if let Some(u) = unit.get(j) {
230 acc = acc.sub(&u.mul(&series[i - j]));
231 }
232 }
233 series.push(acc.mul(&unit_inv));
234 }
235 let mut pole = vec![zero; k + 1];
236 pole[k] = Algebraic::from_i64(1);
237 let principal_value = Self::make(APoly::from(series.clone()), APoly::from(pole));
238 let rest = self.sub(&principal_value);
239 let coefficients = (1..=k).map(|j| series[k - j].clone()).collect();
240 (coefficients, rest)
241 }
242
243 pub fn as_dt_polynomial(&self) -> Option<Vec<Algebraic>> {
245 (self.den == APoly::one()).then(|| self.num.c.clone())
246 }
247
248 pub fn taylor_coefficient(&self, k: usize) -> Algebraic {
251 debug_assert!(self.is_limited(), "a Taylor series is of a limited value");
252 let mut s: Vec<Algebraic> = Vec::with_capacity(k + 1);
253 for i in 0..=k {
254 let mut acc = self.num.coeff(i);
255 for j in 1..=i {
256 let d = self.den.coeff(j);
257 if !d.is_zero() {
258 acc = acc.sub(&d.mul(&s[i - j]));
259 }
260 }
261 s.push(acc);
262 }
263 s.pop().expect("k + 1 terms")
264 }
265
266 pub fn root_split(&self, n: u32) -> Option<(Hyperreal, Hyperreal)> {
274 if self.is_zero() || (n % 2 == 0 && self.signum() < 0) {
275 return None;
276 }
277 let (num_lc, num_parts) = self.num.squarefree_parts();
278 let (den_lc, den_parts) = self.den.squarefree_parts();
279 let power = |parts: &[(APoly, usize)], take: &dyn Fn(usize) -> usize| {
280 parts
281 .iter()
282 .fold(APoly::one(), |acc, (a, i)| (0..take(*i)).fold(acc, |acc, _| acc.mul(a)))
283 };
284 let n = n as usize;
285 let out_num = power(&num_parts, &|i| i / n);
286 let out_den = power(&den_parts, &|i| i / n);
287 let mut in_num = power(&num_parts, &|i| i % n);
288 let mut in_den = power(&den_parts, &|i| i % n);
289 let mut constant = num_lc.div(&den_lc)?;
290 let rational = in_num.c.iter().chain(&in_den.c).all(|a| a.to_rational().is_some())
291 && constant.to_rational().is_some();
292 if rational {
293 let (sn, sd) = (primitive_scale(&in_num), primitive_scale(&in_den));
294 in_num = in_num.scale(&sn);
295 in_den = in_den.scale(&sd);
296 constant = constant.div(&sn)?.mul(&sd);
297 } else {
298 let ln = in_num.coeff(in_num.order().unwrap_or(0));
299 let ld = in_den.coeff(in_den.order().unwrap_or(0));
300 in_num = in_num.scale(&ln.recip()?);
301 in_den = in_den.scale(&ld.recip()?);
302 constant = constant.mul(&ln).div(&ld)?;
303 }
304 let root = constant.nth_root(n as u32).filter(|r| !rational || r.to_rational().is_some());
305 let (outside_constant, inside_constant) = match root {
306 Some(r) => (r, Algebraic::from_i64(1)),
307 None => (Algebraic::from_i64(1), constant),
308 };
309 let outside = Self::make(out_num.scale(&outside_constant), out_den);
310 let inside = Self::make(in_num.scale(&inside_constant), in_den);
311 Some((outside, inside))
312 }
313
314 pub fn numerator_is_constant(&self) -> bool {
316 self.num.degree().unwrap_or(0) == 0
317 }
318
319 pub fn degrees(&self) -> (usize, usize) {
321 (self.num.degree().unwrap_or(0), self.den.degree().unwrap_or(0))
322 }
323
324 pub fn degree(&self) -> usize {
326 self.num.degree().unwrap_or(0).max(self.den.degree().unwrap_or(0))
327 }
328
329 pub fn pow(&self, k: i32) -> Result<Hyperreal, PowerError> {
331 check_power_degree(self.degree(), u64::from(k.unsigned_abs()))?;
332 let mut base = if k < 0 {
333 self.recip().ok_or(PowerError::ZeroToNegative)?
334 } else {
335 self.clone()
336 };
337 let mut acc = Self::from_i64(1);
338 let mut e = k.unsigned_abs();
339 while e > 0 {
340 if e & 1 == 1 {
341 acc = acc.mul(&base);
342 }
343 e >>= 1;
344 if e > 0 {
345 base = base.mul(&base);
346 }
347 }
348 Ok(acc)
349 }
350
351 pub fn signum(&self) -> i32 {
353 match self.num.order() {
354 None => 0,
355 Some(i) => self.num.c[i].signum(),
356 }
357 }
358
359 pub fn as_standard(&self) -> Option<Algebraic> {
361 if self.den != APoly::one() || self.num.c.len() > 1 {
362 return None;
363 }
364 Some(self.num.coeff(0))
365 }
366
367 pub fn valuation(&self) -> Option<i64> {
369 let n = self.num.order()? as i64;
370 let d = self.den.order().expect("the denominator is nonzero") as i64;
371 Some(n - d)
372 }
373
374 pub fn leading_coefficient(&self) -> Option<Algebraic> {
376 Some(self.num.c[self.num.order()?].clone())
377 }
378
379 pub fn is_infinitesimal(&self) -> bool {
380 self.valuation().is_none_or(|v| v > 0)
381 }
382
383 pub fn is_limited(&self) -> bool {
384 self.valuation().is_none_or(|v| v >= 0)
385 }
386
387 pub fn standard_part(&self) -> Option<Algebraic> {
389 match self.valuation() {
390 None => Some(Algebraic::from_i64(0)),
391 Some(v) if v > 0 => Some(Algebraic::from_i64(0)),
392 Some(0) => self.leading_coefficient(),
393 Some(_) => None,
394 }
395 }
396
397 pub fn infinitely_close(&self, o: &Hyperreal) -> bool {
398 self.sub(o).is_infinitesimal()
399 }
400
401 pub fn eval_at(&self, t: &Rational) -> Option<Algebraic> {
403 let t = Algebraic::from_rational(t.clone());
404 self.num.eval(&t).div(&self.den.eval(&t))
405 }
406
407 pub fn monomial(c: Algebraic, k: i64) -> Hyperreal {
409 let zero = || Algebraic::from_i64(0);
410 let power = |n: usize| {
411 let mut p = vec![zero(); n + 1];
412 p[n] = Algebraic::from_i64(1);
413 APoly::from(p)
414 };
415 if c.is_zero() {
416 return Self::from_i64(0);
417 }
418 if k >= 0 {
419 Hyperreal {
420 num: power(k as usize).scale(&c),
421 den: APoly::one(),
422 }
423 } else {
424 Hyperreal {
425 num: APoly::constant(c),
426 den: power(k.unsigned_abs() as usize),
427 }
428 }
429 }
430
431 pub fn ramify(&self, m: u32) -> Hyperreal {
434 let spread = |p: &APoly| {
435 let mut c = vec![Algebraic::from_i64(0); p.c.len().saturating_sub(1) * m as usize + 1];
436 for (i, a) in p.c.iter().enumerate() {
437 c[i * m as usize] = a.clone();
438 }
439 APoly::from(c)
440 };
441 Hyperreal {
442 num: spread(&self.num),
443 den: spread(&self.den),
444 }
445 }
446
447 pub fn unramify(&self, m: u32) -> Option<Hyperreal> {
450 let gather = |p: &APoly| -> Option<APoly> {
451 let mut c = Vec::new();
452 for (i, a) in p.c.iter().enumerate() {
453 if i % m as usize == 0 {
454 c.push(a.clone());
455 } else if !a.is_zero() {
456 return None;
457 }
458 }
459 Some(APoly::from(c))
460 };
461 Some(Hyperreal {
462 num: gather(&self.num)?,
463 den: gather(&self.den)?,
464 })
465 }
466
467 pub fn laurent_coefficient(&self, k: i64) -> Algebraic {
469 let (principal, rest) = self.principal_part();
470 if k < 0 {
471 let j = k.unsigned_abs() as usize;
472 return principal.get(j - 1).cloned().unwrap_or_else(|| Algebraic::from_i64(0));
473 }
474 rest.taylor_coefficient(k as usize)
475 }
476
477 pub fn pade(series: &[Algebraic], l: usize, m: usize) -> Option<Hyperreal> {
482 let n = l + m + 1;
483 let mut top = vec![Algebraic::from_i64(0); n + 1];
484 top[n] = Algebraic::from_i64(1);
485 let truncated: Vec<Algebraic> = (0..n)
486 .map(|i| series.get(i).cloned().unwrap_or_else(|| Algebraic::from_i64(0)))
487 .collect();
488 let (mut r0, mut r1) = (APoly::from(top), APoly::from(truncated));
489 let (mut t0, mut t1) = (APoly::zero(), APoly::one());
490 while r1.degree().is_some_and(|d| d > l) {
491 let (q, r) = r0.div_rem(&r1);
492 let t = t0.add(&q.mul(&t1).neg());
493 r0 = std::mem::replace(&mut r1, r);
494 t0 = std::mem::replace(&mut t1, t);
495 }
496 if t1.degree().unwrap_or(0) > m || t1.coeff(0).is_zero() {
497 return None;
498 }
499 Some(Self::make(r1, t1))
500 }
501
502 pub fn render(&self, name: &str) -> String {
504 self.render_powers(&|k| match k {
505 1 => name.to_string(),
506 k => format!("{name}^{k}"),
507 })
508 }
509
510 pub fn render_powers(&self, power: &dyn Fn(usize) -> String) -> String {
513 if self.den == APoly::one() {
514 return poly_text(&self.num, power);
515 }
516 let (num, den) = self.display_parts();
517 let terms = |p: &APoly| p.c.iter().filter(|a| !a.is_zero()).count();
518 let numerator = poly_text(&num, power);
519 let numerator = if terms(&num) > 1 { format!("({numerator})") } else { numerator };
520 let denominator = poly_text(&den, power);
521 let denominator = if terms(&den) > 1 || denominator.contains('·') {
522 format!("({denominator})")
523 } else {
524 denominator
525 };
526 format!("{numerator}/{denominator}")
527 }
528
529 pub(crate) fn display_fraction(&self) -> (APoly, APoly) {
533 self.display_parts()
534 }
535
536 fn display_parts(&self) -> (APoly, APoly) {
539 let all: Vec<&Algebraic> = self.num.c.iter().chain(self.den.c.iter()).collect();
540 let rationals: Option<Vec<Rational>> = all.iter().map(|a| a.to_rational()).collect();
541 let Some(rs) = rationals else {
542 return (self.num.clone(), self.den.clone());
543 };
544 let lcm = rs.iter().fold(BigInt::from_i64(1), |l, r| {
545 let d = r.denominator();
546 let g = l.gcd(d);
547 l.div_rem(&g).expect("nonzero").0.mul(d)
548 });
549 let ints: Vec<BigInt> = rs
550 .iter()
551 .map(|r| {
552 r.numerator()
553 .mul(&lcm.div_rem(r.denominator()).expect("nonzero").0)
554 })
555 .collect();
556 let g = ints.iter().fold(BigInt::zero(), |g, x| g.gcd(x));
557 let k = Algebraic::from_rational(
558 Rational::new(lcm, if g.is_zero() { BigInt::from_i64(1) } else { g }).expect("nonzero"),
559 );
560 (self.num.scale(&k), self.den.scale(&k))
561 }
562}
563
564fn primitive_scale(p: &APoly) -> Algebraic {
567 let rs: Vec<Rational> = p.c.iter().map(|a| a.to_rational().expect("rational coefficients")).collect();
568 let lcm = rs.iter().fold(BigInt::from_i64(1), |l, r| {
569 let d = r.denominator();
570 let g = l.gcd(d);
571 l.div_rem(&g).expect("nonzero").0.mul(d)
572 });
573 let ints: Vec<BigInt> = rs
574 .iter()
575 .map(|r| r.numerator().mul(&lcm.div_rem(r.denominator()).expect("nonzero").0))
576 .collect();
577 let g = ints.iter().fold(BigInt::zero(), |g, x| g.gcd(x));
578 let low_negative = ints.iter().find(|x| !x.is_zero()).is_some_and(|x| x.is_negative());
579 let g = if low_negative { g.negated() } else { g };
580 Algebraic::from_rational(Rational::new(lcm, if g.is_zero() { BigInt::from_i64(1) } else { g }).expect("nonzero"))
581}
582
583fn poly_text(p: &APoly, power: &dyn Fn(usize) -> String) -> String {
586 if p.is_zero() {
587 return "0".to_string();
588 }
589 let mut out = String::new();
590 let mut first = true;
591 for (i, a) in p.c.iter().enumerate() {
592 if a.is_zero() {
593 continue;
594 }
595 let negative = a.signum() < 0;
596 let magnitude = if negative { a.neg() } else { a.clone() };
597 let power = if i == 0 { String::new() } else { power(i) };
598 let body = if i == 0 {
599 magnitude.to_string()
600 } else if magnitude == Algebraic::from_i64(1) {
601 power
602 } else {
603 let m = magnitude.to_string();
604 if m.contains(' ') || m.contains('/') {
605 format!("({m})·{power}")
606 } else {
607 format!("{m}·{power}")
608 }
609 };
610 match (first, negative) {
611 (true, true) => out.push_str(&format!("-{body}")),
612 (true, false) => out.push_str(&body),
613 (false, true) => out.push_str(&format!(" - {body}")),
614 (false, false) => out.push_str(&format!(" + {body}")),
615 }
616 first = false;
617 }
618 out
619}
620
621impl PartialEq for Hyperreal {
622 fn eq(&self, o: &Self) -> bool {
623 self.num == o.num && self.den == o.den
624 }
625}
626
627impl Eq for Hyperreal {}
628
629impl Hash for Hyperreal {
630 fn hash<H: Hasher>(&self, state: &mut H) {
631 self.num.hash(state);
632 self.den.hash(state);
633 }
634}
635
636impl Ord for Hyperreal {
637 fn cmp(&self, o: &Self) -> Ordering {
638 match self.sub(o).signum() {
639 -1 => Ordering::Less,
640 0 => Ordering::Equal,
641 _ => Ordering::Greater,
642 }
643 }
644}
645
646impl PartialOrd for Hyperreal {
647 fn partial_cmp(&self, o: &Self) -> Option<Ordering> {
648 Some(self.cmp(o))
649 }
650}
651
652impl fmt::Display for Hyperreal {
653 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654 write!(f, "{}", self.render("dt"))
655 }
656}
657
658impl crate::exact_codec::ExactCodec for Hyperreal {
659 fn write(&self, out: &mut Vec<u8>) {
661 crate::exact_codec::write_list(&self.num.c, out);
662 crate::exact_codec::write_list(&self.den.c, out);
663 }
664
665 fn read(buf: &[u8], pos: &mut usize) -> Option<Self> {
666 let num = APoly::from(crate::exact_codec::read_list(buf, pos)?);
667 let den = APoly::from(crate::exact_codec::read_list(buf, pos)?);
668 if den.is_zero() {
669 return None;
670 }
671 Some(Hyperreal::make(num, den))
672 }
673}
674
675#[cfg(test)]
676#[path = "../../logicaffeine_tests/unit/logicaffeine_base/hyperreal/tests.rs"]
677mod tests;