Skip to main content

logicaffeine_base/
nested.rs

1//! Germs of several infinitesimals: a value of `dx`, declared much smaller than `dt`, known by its
2//! expansion in `dx` whose coefficients are values of `dt` and of the infinitesimals larger than it,
3//! not all of them exact — `exp(dt + dx) = Σ (e^dt/k!)·dx^k`, `ln(1 + dt + dx)`, the state a loop
4//! stepped by `dx` leaves when its rate is a value of `dt`.
5//!
6//! A [`NestedGerm`] holds such a value as the recipe that built it and its [`Expansion`] over
7//! [`Number`] coefficients, each computed once as a comparison or a printed term asks. The functions
8//! are the recurrences one infinitesimal's germs follow ([`crate::germ`]), one level down: `exp(g)` for
9//! a `g` with no negative order starts at `e^{g_0}`, a coefficient's own exponential; `ln(g)` for a `g`
10//! of order `0` with a positive leading coefficient starts at `ln(g_0)`. What is no germ is refused by
11//! name: the exponential of a value infinite in the smallest infinitesimal lies beyond every power of
12//! it, and the logarithm of a value of nonzero order carries the logarithm of that infinitesimal.
13//!
14//! Signs are read from the first nonzero coefficient, through [`crate::germ::SIGN_ORDERS`] orders: a
15//! coefficient that cannot be told from zero, or a germ whose first orders all vanish, is a loud
16//! error, never a guess. Two germs whose recipes are the same are the same value, so a term both sides
17//! of a difference share cancels exactly.
18
19use std::sync::Arc;
20
21use crate::closure::AlgValue;
22use crate::expansion::{Coefficient, Expansion, ExpansionError, Source};
23use crate::germ::{Germ, SIGN_ORDERS};
24use crate::multi::Multi;
25use crate::nested_flow::{FlowSpec, MatrixExpSpec, PowerSpec};
26use crate::number::Number;
27use crate::numeric::Rational;
28use crate::puiseux::{expansion_term, remainder, remainder_after};
29use crate::tower::{self, ExactRef, HyperForm, Scale, TowerValue};
30
31/// How a [`NestedGerm`] was built.
32#[derive(Clone, Debug)]
33pub(crate) enum GermRecipe {
34    /// An exact value of the infinitesimal and the larger ones, read by its expansion.
35    Exact(Multi),
36    /// A germ of the infinitesimal alone, its coefficients computable reals.
37    Germ(Germ),
38    /// A value of the larger infinitesimals, or a computable real: constant in this one.
39    Constant(Number),
40    Add(Arc<NestedGerm>, Arc<NestedGerm>),
41    Sub(Arc<NestedGerm>, Arc<NestedGerm>),
42    Neg(Arc<NestedGerm>),
43    Mul(Arc<NestedGerm>, Arc<NestedGerm>),
44    Recip(Arc<NestedGerm>),
45    Root(Arc<NestedGerm>, u32),
46    Exp(Arc<NestedGerm>),
47    Ln(Arc<NestedGerm>),
48    /// An entry of the exponential of a matrix of numbers ([`crate::nested_flow::matrix_exp`]).
49    MatrixExp(Arc<MatrixExpSpec>, usize, usize),
50    /// A component of a flow whose rates are values of larger infinitesimals.
51    Flow(Arc<FlowSpec>, usize),
52    /// A component of a power whose rates are values of this, a smaller infinitesimal.
53    Power(Arc<PowerSpec>, usize),
54}
55
56/// A germ of several infinitesimals at its smallest. See the module documentation.
57#[derive(Clone, Debug)]
58pub struct NestedGerm {
59    pub(crate) scale: Arc<Scale>,
60    pub(crate) recipe: GermRecipe,
61    pub(crate) expansion: Expansion<Number>,
62}
63
64/// An exact value's expansion read with [`Number`] coefficients.
65#[derive(Debug)]
66struct ExactSource(Expansion<Multi>);
67
68impl Source<Number> for ExactSource {
69    fn coefficient(&self, k: i64) -> Result<Number, ExpansionError> {
70        Ok(tower::from_multi(self.0.coefficient(k)?))
71    }
72}
73
74/// A germ of one infinitesimal read with [`Number`] coefficients.
75#[derive(Debug)]
76struct GermSource(Germ);
77
78impl Source<Number> for GermSource {
79    fn coefficient(&self, k: i64) -> Result<Number, ExpansionError> {
80        self.0
81            .coefficient(k)
82            .map(tower::from_exact_real)
83            .map_err(|e| ExpansionError::Coefficient(e.to_string()))
84    }
85}
86
87/// The exponent of a nonzero number's leading term in its smallest infinitesimal, with that
88/// infinitesimal; `None` for a standard number. Refused when the term cannot be found.
89pub(crate) fn leading_order(n: &Number) -> Result<Option<(Arc<Scale>, Rational)>, String> {
90    let Some(ExactRef::Hyper(h)) = n.exact() else {
91        return Ok(None);
92    };
93    let order = match &h.value {
94        HyperForm::Rational(r) => Rational::from_i64(r.valuation().expect("a hyperreal held as one is nonzero")),
95        HyperForm::Puiseux(p) => {
96            Rational::from_ratio_i64(p.order(), i64::from(p.ramification())).expect("q is positive")
97        }
98        HyperForm::Germ(g) => {
99            let (k, _) = g.leading().map_err(|e| e.to_string())?;
100            Rational::from_ratio_i64(k, i64::from(g.ramification())).expect("q is positive")
101        }
102        HyperForm::Multi(m) | HyperForm::Integer(m) => m.leading().expect("a value of several infinitesimals is nonzero").0,
103        HyperForm::Nested(g) => g.order()?,
104    };
105    Ok(Some((h.scale.clone(), order)))
106}
107
108/// The coefficient of a nonzero number's leading term in its smallest infinitesimal: a value of the
109/// larger ones.
110fn leading_coefficient(n: &Number) -> Result<Number, String> {
111    let Some(ExactRef::Hyper(h)) = n.exact() else {
112        return Ok(n.clone());
113    };
114    Ok(match &h.value {
115        HyperForm::Rational(r) => tower::from_algebraic(r.leading_coefficient().expect("nonzero")),
116        HyperForm::Puiseux(p) => {
117            tower::from_algebraic(p.coefficient(p.order()))
118        }
119        HyperForm::Germ(g) => tower::from_exact_real(g.leading().map_err(|e| e.to_string())?.1),
120        HyperForm::Multi(m) | HyperForm::Integer(m) => tower::from_multi(m.leading().expect("nonzero").1),
121        HyperForm::Nested(g) => g.leading()?.1,
122    })
123}
124
125/// Every infinitesimal a number involves.
126fn number_scales(n: &Number) -> Vec<Arc<Scale>> {
127    match n.exact() {
128        Some(ExactRef::Hyper(h)) => match &h.value {
129            HyperForm::Multi(m) | HyperForm::Integer(m) => m.scales(),
130            HyperForm::Nested(g) => g.scales(),
131            _ => vec![h.scale.clone()],
132        },
133        Some(ExactRef::Natural(m)) => vec![m.scale.clone()],
134        _ => Vec::new(),
135    }
136}
137
138/// The infinitesimal `scale` itself, as a number.
139fn generator(scale: &Arc<Scale>) -> Number {
140    Number::hyper(tower::HyperValue {
141        value: HyperForm::Rational(crate::hyperreal::Hyperreal::dt()),
142        scale: scale.clone(),
143    })
144}
145
146impl NestedGerm {
147    /// Every infinitesimal the value involves: its own and those of its recipe's leaves.
148    pub(crate) fn scales(&self) -> Vec<Arc<Scale>> {
149        let mut out = vec![self.scale.clone()];
150        self.recipe_scales(&mut out);
151        out
152    }
153
154    fn recipe_scales(&self, out: &mut Vec<Arc<Scale>>) {
155        let push = |s: Arc<Scale>, out: &mut Vec<Arc<Scale>>| {
156            if !out.iter().any(|t| t.same_infinitesimal(&s)) {
157                out.push(s);
158            }
159        };
160        match &self.recipe {
161            GermRecipe::Exact(m) => m.scales().into_iter().for_each(|s| push(s, out)),
162            GermRecipe::Germ(_) => {}
163            GermRecipe::Constant(n) => number_scales(n).into_iter().for_each(|s| push(s, out)),
164            GermRecipe::Add(a, b) | GermRecipe::Sub(a, b) | GermRecipe::Mul(a, b) => {
165                a.recipe_scales(out);
166                b.recipe_scales(out);
167            }
168            GermRecipe::Neg(a)
169            | GermRecipe::Recip(a)
170            | GermRecipe::Root(a, _)
171            | GermRecipe::Exp(a)
172            | GermRecipe::Ln(a) => a.recipe_scales(out),
173            GermRecipe::MatrixExp(spec, _, _) => {
174                spec.a.iter().flatten().flat_map(number_scales).for_each(|s| push(s, out))
175            }
176            GermRecipe::Flow(spec, _) => spec
177                .a
178                .iter()
179                .flatten()
180                .chain(spec.start.iter())
181                .flat_map(number_scales)
182                .for_each(|s| push(s, out)),
183            GermRecipe::Power(spec, _) => spec
184                .a
185                .iter()
186                .flatten()
187                .chain(spec.start.iter())
188                .chain(std::iter::once(&spec.count))
189                .flat_map(number_scales)
190                .for_each(|s| push(s, out)),
191        }
192    }
193
194    /// Whether two germs were built by the same recipe, which makes them the same value.
195    pub(crate) fn same_recipe(a: &NestedGerm, b: &NestedGerm) -> bool {
196        if !a.scale.same_infinitesimal(&b.scale) {
197            return false;
198        }
199        match (&a.recipe, &b.recipe) {
200            (GermRecipe::Exact(x), GermRecipe::Exact(y)) => x == y,
201            (GermRecipe::Germ(x), GermRecipe::Germ(y)) => {
202                use crate::exact_codec::ExactCodec;
203                x.is_transmissible() && y.is_transmissible() && x.to_bytes() == y.to_bytes()
204            }
205            (GermRecipe::Constant(x), GermRecipe::Constant(y)) => x.values_equal(y),
206            (GermRecipe::Add(p, q), GermRecipe::Add(r, s))
207            | (GermRecipe::Sub(p, q), GermRecipe::Sub(r, s))
208            | (GermRecipe::Mul(p, q), GermRecipe::Mul(r, s)) => {
209                NestedGerm::same_recipe(p, r) && NestedGerm::same_recipe(q, s)
210            }
211            (GermRecipe::Neg(p), GermRecipe::Neg(r))
212            | (GermRecipe::Recip(p), GermRecipe::Recip(r))
213            | (GermRecipe::Exp(p), GermRecipe::Exp(r))
214            | (GermRecipe::Ln(p), GermRecipe::Ln(r)) => NestedGerm::same_recipe(p, r),
215            (GermRecipe::Root(p, n), GermRecipe::Root(r, m)) => n == m && NestedGerm::same_recipe(p, r),
216            (GermRecipe::MatrixExp(p, i, j), GermRecipe::MatrixExp(r, k, l)) => Arc::ptr_eq(p, r) && (i, j) == (k, l),
217            (GermRecipe::Flow(p, i), GermRecipe::Flow(r, k)) => Arc::ptr_eq(p, r) && i == k,
218            (GermRecipe::Power(p, i), GermRecipe::Power(r, k)) => Arc::ptr_eq(p, r) && i == k,
219            _ => false,
220        }
221    }
222
223    /// A germ whose expansion a source gives.
224    pub(crate) fn from_source(scale: &Arc<Scale>, recipe: GermRecipe, expansion: Expansion<Number>) -> NestedGerm {
225        NestedGerm::of(scale, recipe, expansion)
226    }
227
228    fn of(scale: &Arc<Scale>, recipe: GermRecipe, expansion: Expansion<Number>) -> NestedGerm {
229        NestedGerm {
230            scale: scale.clone(),
231            recipe,
232            expansion,
233        }
234    }
235
236    /// An exact value of `scale` and the larger infinitesimals, read as a germ at `scale`.
237    pub(crate) fn exact(m: &Multi, scale: &Arc<Scale>) -> NestedGerm {
238        match m.top() {
239            Some(t) if t.same_infinitesimal(scale) => {
240                let expansion = AlgValue::at(m, scale).expansion;
241                let (floor, q) = (expansion.floor(), expansion.ramification());
242                NestedGerm::of(scale, GermRecipe::Exact(m.clone()), Expansion::source(Arc::new(ExactSource(expansion)), floor, q))
243            }
244            _ => NestedGerm::constant(tower::from_multi(m.clone()), scale),
245        }
246    }
247
248    /// A germ of `scale` alone, read with [`Number`] coefficients.
249    pub(crate) fn germ(g: &Germ, scale: &Arc<Scale>) -> NestedGerm {
250        let expansion = Expansion::source(Arc::new(GermSource(g.clone())), g.floor(), g.ramification());
251        NestedGerm::of(scale, GermRecipe::Germ(g.clone()), expansion)
252    }
253
254    /// A number constant in `scale`.
255    pub(crate) fn constant(n: Number, scale: &Arc<Scale>) -> NestedGerm {
256        NestedGerm::of(scale, GermRecipe::Constant(n.clone()), Expansion::constant(n))
257    }
258
259    fn arc(self) -> Arc<NestedGerm> {
260        Arc::new(self)
261    }
262
263    pub(crate) fn add(a: &Arc<NestedGerm>, b: &Arc<NestedGerm>) -> NestedGerm {
264        NestedGerm::of(&a.scale, GermRecipe::Add(a.clone(), b.clone()), a.expansion.add(&b.expansion))
265    }
266
267    pub(crate) fn sub(a: &Arc<NestedGerm>, b: &Arc<NestedGerm>) -> NestedGerm {
268        NestedGerm::of(&a.scale, GermRecipe::Sub(a.clone(), b.clone()), a.expansion.sub(&b.expansion))
269    }
270
271    pub(crate) fn neg(a: &Arc<NestedGerm>) -> NestedGerm {
272        NestedGerm::of(&a.scale, GermRecipe::Neg(a.clone()), a.expansion.neg())
273    }
274
275    pub(crate) fn mul(a: &Arc<NestedGerm>, b: &Arc<NestedGerm>) -> NestedGerm {
276        NestedGerm::of(&a.scale, GermRecipe::Mul(a.clone(), b.clone()), a.expansion.mul(&b.expansion))
277    }
278
279    /// The orders a sign or a leading term looks through.
280    fn reach(&self) -> i64 {
281        self.expansion.floor() + SIGN_ORDERS * i64::from(self.expansion.ramification())
282    }
283
284    /// The leading term: its order on the expansion's grid and its coefficient; refused when the first
285    /// orders all vanish or a coefficient cannot be told from zero.
286    pub(crate) fn leading(&self) -> Result<(i64, Number), String> {
287        match self.expansion.leading_through(self.reach()) {
288            Ok(Some(term)) => Ok(term),
289            Ok(None) => Err(crate::germ::GermError::Flat.to_string()),
290            Err(e) => Err(e.to_string()),
291        }
292    }
293
294    /// The leading order in the infinitesimal's own units.
295    pub(crate) fn order(&self) -> Result<Rational, String> {
296        let (k, _) = self.leading()?;
297        Ok(Rational::from_ratio_i64(k, i64::from(self.expansion.ramification())).expect("q is positive"))
298    }
299
300    pub(crate) fn sign(&self) -> Result<i32, String> {
301        let (_, c) = self.leading()?;
302        c.sign().map_err(|e| e.to_string())
303    }
304
305    pub(crate) fn recip(a: &Arc<NestedGerm>) -> Result<NestedGerm, String> {
306        let (k, c) = a.leading()?;
307        Ok(NestedGerm::of(&a.scale, GermRecipe::Recip(a.clone()), a.expansion.recip(k, c)))
308    }
309
310    /// The real `n`-th root, the nonnegative one for an even `n`.
311    pub(crate) fn root(a: &Arc<NestedGerm>, n: u32) -> Result<NestedGerm, String> {
312        if n == 1 {
313            return Ok((**a).clone());
314        }
315        let (k, c) = a.leading()?;
316        let expansion = a.expansion.root(n, k, c).map_err(|e| e.to_string())?;
317        Ok(NestedGerm::of(&a.scale, GermRecipe::Root(a.clone(), n), expansion))
318    }
319
320    /// `a^k` for an integer `k`, by repeated squaring.
321    pub(crate) fn pow(a: &Arc<NestedGerm>, k: i32) -> Result<NestedGerm, String> {
322        let mut base = if k < 0 { NestedGerm::recip(a)?.arc() } else { a.clone() };
323        let mut acc: Option<Arc<NestedGerm>> = None;
324        let mut e = k.unsigned_abs();
325        if e == 0 {
326            return Ok(NestedGerm::constant(Number::one(), &a.scale));
327        }
328        while e > 0 {
329            if e & 1 == 1 {
330                acc = Some(match acc {
331                    None => base.clone(),
332                    Some(x) => NestedGerm::mul(&x, &base).arc(),
333                });
334            }
335            e >>= 1;
336            if e > 0 {
337                base = NestedGerm::mul(&base, &base).arc();
338            }
339        }
340        Ok((*acc.expect("k ≠ 0")).clone())
341    }
342
343    /// `exp(a)`, refused for a value infinite in the infinitesimal, whose exponential lies beyond every
344    /// power of it; `shown` is how the value prints.
345    pub(crate) fn exp(a: &Arc<NestedGerm>, shown: &str) -> Result<NestedGerm, String> {
346        let (k, _) = a.leading()?;
347        if k < 0 {
348            return Err(format!(
349                "the exponential of {shown} is beyond every power of {}",
350                a.scale.name
351            ));
352        }
353        Ok(NestedGerm::of(&a.scale, GermRecipe::Exp(a.clone()), a.expansion.exp()))
354    }
355
356    /// The natural logarithm of a positive value of order `0` whose leading coefficient has a
357    /// logarithm; a value of another order, or whose leading coefficient is one, carries the logarithm
358    /// of that infinitesimal. `shown` is how the value prints.
359    pub(crate) fn ln(a: &Arc<NestedGerm>, shown: &str) -> Result<NestedGerm, String> {
360        let (k, c) = a.leading()?;
361        if c.sign().map_err(|e| e.to_string())? < 0 {
362            return Err("logarithm of a nonpositive number".to_string());
363        }
364        let carries = |infinite: bool, name: &str| {
365            format!(
366                "the logarithm of {shown} carries ln({}{name})",
367                if infinite { "1/" } else { "" }
368            )
369        };
370        if k != 0 {
371            return Err(carries(k < 0, &a.scale.name));
372        }
373        let mut lead = c.clone();
374        while let Some((scale, order)) = leading_order(&lead)? {
375            if !order.is_zero() {
376                return Err(carries(order.is_negative(), &scale.name));
377            }
378            lead = leading_coefficient(&lead)?;
379        }
380        Ok(NestedGerm::of(&a.scale, GermRecipe::Ln(a.clone()), a.expansion.ln(c)))
381    }
382
383    /// What the value is infinitely close to from the standpoint of `at` (the standard part with no
384    /// `at`): the leading coefficient's own, when the value is limited there; `None` when infinite.
385    pub(crate) fn standard_part_at(&self, at: Option<&Arc<Scale>>) -> Result<Option<Number>, String> {
386        if let Some(at) = at {
387            if self.scale.order(at).expect("the standpoint is ordered with the value's infinitesimals")
388                != std::cmp::Ordering::Less
389            {
390                return Ok(Some(Number::hyper(tower::HyperValue {
391                    value: HyperForm::Nested(Arc::new(self.clone())),
392                    scale: self.scale.clone(),
393                })));
394            }
395        }
396        let first = self.expansion.floor();
397        for k in first..0 {
398            let c = self.expansion.coefficient(k).map_err(|e| e.to_string())?;
399            if !c.is_zero().map_err(|e| e.to_string())? {
400                return Ok(None);
401            }
402        }
403        let c0 = self.expansion.coefficient(0).map_err(|e| e.to_string())?;
404        match at {
405            None => tower::standard_part(&c0).map(Some),
406            Some(scale) => tower::standard_part_relative(&c0, &generator(scale)).map(Some),
407        }
408    }
409
410    /// The two leading terms and, from the order after the last one shown, what is left; each
411    /// coefficient prints as its own value, parenthesized when it carries a remainder of its own.
412    pub(crate) fn render(&self) -> String {
413        let name = &self.scale.name;
414        let q = i64::from(self.expansion.ramification());
415        let reach = self.reach();
416        let mut terms: Vec<(i64, Number)> = Vec::new();
417        let mut k = self.expansion.floor();
418        let mut next: Option<i64> = None;
419        while k <= reach {
420            match self.expansion.coefficient(k) {
421                Ok(c) => match c.is_zero() {
422                    Ok(true) => {}
423                    Ok(false) if terms.len() < 2 => terms.push((k, c)),
424                    Ok(false) => {
425                        next = Some(k);
426                        break;
427                    }
428                    Err(_) => break,
429                },
430                Err(_) => break,
431            }
432            k += 1;
433        }
434        let exponent = |k: i64| Rational::from_ratio_i64(k, q).expect("q is positive");
435        let mut out = String::new();
436        for (i, (k, c)) in terms.iter().enumerate() {
437            let text = c.display_string();
438            let text = if *k == 0 && text.contains("O(") { format!("({text})") } else { text };
439            let t = expansion_term(name, &text, &exponent(*k));
440            match (i, t.strip_prefix('-')) {
441                (0, _) => out.push_str(&t),
442                (_, Some(rest)) => {
443                    out.push_str(" - ");
444                    out.push_str(rest);
445                }
446                (_, None) => {
447                    out.push_str(" + ");
448                    out.push_str(&t);
449                }
450            }
451        }
452        let shown: Vec<Rational> = terms.iter().map(|(k, _)| exponent(*k)).collect();
453        let rest = match (shown.is_empty(), next) {
454            (true, _) => remainder(name, &exponent(k)),
455            (false, Some(n)) => remainder(name, &remainder_after(&shown, &exponent(n))),
456            (false, None) => remainder(name, &remainder_after(&shown, &exponent(k))),
457        };
458        if out.is_empty() {
459            rest
460        } else {
461            format!("{out} + {rest}")
462        }
463    }
464}
465
466// ── bytes ─────────────────────────────────────────────────────────────────────────────────────
467
468impl NestedGerm {
469    /// Whether the germ can travel as data: every leaf of its recipe can.
470    pub(crate) fn is_transmissible(&self) -> bool {
471        match &self.recipe {
472            GermRecipe::Exact(_) => true,
473            GermRecipe::Germ(g) => g.is_transmissible(),
474            GermRecipe::Constant(n) => tower::transmissible(n),
475            GermRecipe::Add(a, b) | GermRecipe::Sub(a, b) | GermRecipe::Mul(a, b) => {
476                a.is_transmissible() && b.is_transmissible()
477            }
478            GermRecipe::Neg(a)
479            | GermRecipe::Recip(a)
480            | GermRecipe::Root(a, _)
481            | GermRecipe::Exp(a)
482            | GermRecipe::Ln(a) => a.is_transmissible(),
483            GermRecipe::MatrixExp(spec, _, _) => spec.a.iter().flatten().all(tower::transmissible),
484            GermRecipe::Flow(spec, _) => spec.a.iter().flatten().chain(spec.start.iter()).all(tower::transmissible),
485            GermRecipe::Power(spec, _) => spec
486                .a
487                .iter()
488                .flatten()
489                .chain(spec.start.iter())
490                .chain(std::iter::once(&spec.count))
491                .all(tower::transmissible),
492        }
493    }
494
495    /// The germ's bytes: its infinitesimal by name and declaration, then its recipe — a tag and its
496    /// parts: `0` an exact value, `1` a germ of the infinitesimal alone, `2` a constant (a computable
497    /// real or a value of the larger infinitesimals), `3` a sum, `4` a difference, `5` a negation, `6` a
498    /// product, `7` a reciprocal, `8` a root and its index, `9` an exponential, `10` a logarithm. Each
499    /// leaf writes itself whole, its own infinitesimals with it.
500    pub(crate) fn to_bytes(&self) -> Vec<u8> {
501        use crate::exact_codec::write_len;
502        let mut out = Vec::new();
503        write_len(self.scale.name.len(), &mut out);
504        out.extend_from_slice(self.scale.name.as_bytes());
505        let scale = self.scale.to_bytes();
506        write_len(scale.len(), &mut out);
507        out.extend_from_slice(&scale);
508        self.write_recipe(&mut out);
509        out
510    }
511
512    fn write_recipe(&self, out: &mut Vec<u8>) {
513        use crate::exact_codec::{write_len, ExactCodec};
514        let blob = |bytes: Vec<u8>, out: &mut Vec<u8>| {
515            write_len(bytes.len(), out);
516            out.extend_from_slice(&bytes);
517        };
518        match &self.recipe {
519            GermRecipe::Exact(m) => {
520                out.push(0);
521                blob(m.to_bytes(), out);
522            }
523            GermRecipe::Germ(g) => {
524                out.push(1);
525                blob(g.to_bytes(), out);
526            }
527            GermRecipe::Constant(n) => {
528                out.push(2);
529                blob(number_bytes(n), out);
530            }
531            GermRecipe::Add(a, b) | GermRecipe::Sub(a, b) | GermRecipe::Mul(a, b) => {
532                out.push(match &self.recipe {
533                    GermRecipe::Add(..) => 3,
534                    GermRecipe::Sub(..) => 4,
535                    _ => 6,
536                });
537                a.write_recipe(out);
538                b.write_recipe(out);
539            }
540            GermRecipe::Neg(a) => {
541                out.push(5);
542                a.write_recipe(out);
543            }
544            GermRecipe::Recip(a) => {
545                out.push(7);
546                a.write_recipe(out);
547            }
548            GermRecipe::Root(a, n) => {
549                out.push(8);
550                a.write_recipe(out);
551                u64::from(*n).write(out);
552            }
553            GermRecipe::Exp(a) => {
554                out.push(9);
555                a.write_recipe(out);
556            }
557            GermRecipe::Ln(a) => {
558                out.push(10);
559                a.write_recipe(out);
560            }
561            GermRecipe::MatrixExp(spec, i, j) => {
562                out.push(11);
563                write_matrix(&spec.a, out);
564                spec.t.write(out);
565                (*i as u64).write(out);
566                (*j as u64).write(out);
567            }
568            GermRecipe::Flow(spec, i) => {
569                out.push(12);
570                write_matrix(&spec.a, out);
571                spec.t.write(out);
572                spec.delta.write(out);
573                write_numbers(&spec.start, out);
574                (*i as u64).write(out);
575            }
576            GermRecipe::Power(spec, i) => {
577                out.push(13);
578                write_matrix(&spec.a, out);
579                blob(number_bytes(&spec.count), out);
580                write_numbers(&spec.start, out);
581                (*i as u64).write(out);
582            }
583        }
584    }
585
586    /// The germ [`NestedGerm::to_bytes`] wrote; `None` for a forged or non-canonical encoding.
587    pub(crate) fn from_bytes(buf: &[u8]) -> Option<NestedGerm> {
588        use crate::exact_codec::{read_len, MAX_WIRE_LEN};
589        let mut pos = 0;
590        let n = read_len(buf, &mut pos, MAX_WIRE_LEN)?;
591        let name = std::str::from_utf8(buf.get(pos..pos.checked_add(n)?)?).ok()?.to_string();
592        pos += n;
593        let n = read_len(buf, &mut pos, MAX_WIRE_LEN)?;
594        let scale = Scale::from_bytes(&name, buf.get(pos..pos.checked_add(n)?)?)?;
595        pos += n;
596        let germ = NestedGerm::read_recipe(buf, &mut pos, &scale, 0)?;
597        (pos == buf.len() && germ.to_bytes() == buf).then_some(germ)
598    }
599
600    fn read_recipe(buf: &[u8], pos: &mut usize, scale: &Arc<Scale>, depth: usize) -> Option<NestedGerm> {
601        use crate::exact_codec::{read_byte, read_len, ExactCodec, MAX_WIRE_DEGREE, MAX_WIRE_DEPTH, MAX_WIRE_LEN};
602        if depth > MAX_WIRE_DEPTH {
603            return None;
604        }
605        let blob = |pos: &mut usize| -> Option<Vec<u8>> {
606            let n = read_len(buf, pos, MAX_WIRE_LEN)?;
607            let bytes = buf.get(*pos..pos.checked_add(n)?)?.to_vec();
608            *pos += n;
609            Some(bytes)
610        };
611        let sub = |pos: &mut usize| NestedGerm::read_recipe(buf, pos, scale, depth + 1).map(Arc::new);
612        Some(match read_byte(buf, pos)? {
613            0 => NestedGerm::exact(&Multi::from_bytes(&blob(pos)?)?, scale),
614            1 => NestedGerm::germ(&Germ::from_bytes(&blob(pos)?)?, scale),
615            2 => NestedGerm::constant(number_from_bytes(&blob(pos)?)?, scale),
616            3 => {
617                let a = sub(pos)?;
618                NestedGerm::add(&a, &sub(pos)?)
619            }
620            4 => {
621                let a = sub(pos)?;
622                NestedGerm::sub(&a, &sub(pos)?)
623            }
624            5 => NestedGerm::neg(&sub(pos)?),
625            6 => {
626                let a = sub(pos)?;
627                NestedGerm::mul(&a, &sub(pos)?)
628            }
629            7 => NestedGerm::recip(&sub(pos)?).ok()?,
630            8 => {
631                let a = sub(pos)?;
632                let n = u32::try_from(u64::read(buf, pos)?).ok().filter(|n| (2..=MAX_WIRE_DEGREE as u32).contains(n))?;
633                NestedGerm::root(&a, n).ok()?
634            }
635            9 => NestedGerm::exp(&sub(pos)?, "").ok()?,
636            10 => NestedGerm::ln(&sub(pos)?, "").ok()?,
637            11 => {
638                let a = read_matrix(buf, pos)?;
639                let t = Rational::read(buf, pos)?;
640                let (i, j) = (read_index(buf, pos, a.len())?, read_index(buf, pos, a.len())?);
641                let e = crate::nested_flow::matrix_exp(&a, &t).ok()?;
642                nested_of(&e[i][j], scale)?
643            }
644            12 => {
645                let a = read_matrix(buf, pos)?;
646                let t = Rational::read(buf, pos)?;
647                let delta = Rational::read(buf, pos)?;
648                let start = read_numbers(buf, pos)?;
649                let i = read_index(buf, pos, a.len())?;
650                let state = crate::nested_flow::flow(scale, &a, t, delta, &start).ok()?;
651                nested_of(state.get(i)?, scale)?
652            }
653            13 => {
654                let a = read_matrix(buf, pos)?;
655                let count = number_from_bytes(&blob(pos)?)?;
656                let start = read_numbers(buf, pos)?;
657                let i = read_index(buf, pos, a.len())?;
658                let state = crate::nested_flow::power(scale, &a, count, &start).ok()?;
659                nested_of(state.get(i)?, scale)?
660            }
661            _ => return None,
662        })
663    }
664}
665
666/// The germ a number holds, at `scale`.
667fn nested_of(n: &Number, scale: &Arc<Scale>) -> Option<NestedGerm> {
668    match n.exact()? {
669        ExactRef::Hyper(h) => match &h.value {
670            HyperForm::Nested(g) if g.scale.same_infinitesimal(scale) => Some((**g).clone()),
671            _ => None,
672        },
673        _ => None,
674    }
675}
676
677fn write_numbers(numbers: &[Number], out: &mut Vec<u8>) {
678    use crate::exact_codec::write_len;
679    write_len(numbers.len(), out);
680    for n in numbers {
681        let bytes = number_bytes(n);
682        write_len(bytes.len(), out);
683        out.extend_from_slice(&bytes);
684    }
685}
686
687fn write_matrix(a: &[Vec<Number>], out: &mut Vec<u8>) {
688    use crate::exact_codec::write_len;
689    write_len(a.len(), out);
690    for row in a {
691        write_numbers(row, out);
692    }
693}
694
695fn read_numbers(buf: &[u8], pos: &mut usize) -> Option<Vec<Number>> {
696    use crate::exact_codec::{read_len, MAX_WIRE_DEGREE, MAX_WIRE_LEN};
697    let n = read_len(buf, pos, MAX_WIRE_DEGREE)?;
698    let mut out = Vec::with_capacity(n);
699    for _ in 0..n {
700        let len = read_len(buf, pos, MAX_WIRE_LEN)?;
701        let bytes = buf.get(*pos..pos.checked_add(len)?)?;
702        *pos += len;
703        out.push(number_from_bytes(bytes)?);
704    }
705    Some(out)
706}
707
708fn read_matrix(buf: &[u8], pos: &mut usize) -> Option<Vec<Vec<Number>>> {
709    use crate::exact_codec::{read_len, MAX_WIRE_DEGREE};
710    let n = read_len(buf, pos, MAX_WIRE_DEGREE).filter(|n| *n > 0)?;
711    let mut rows = Vec::with_capacity(n);
712    for _ in 0..n {
713        let row = read_numbers(buf, pos)?;
714        if row.len() != n {
715            return None;
716        }
717        rows.push(row);
718    }
719    Some(rows)
720}
721
722fn read_index(buf: &[u8], pos: &mut usize, below: usize) -> Option<usize> {
723    use crate::exact_codec::ExactCodec;
724    usize::try_from(u64::read(buf, pos)?).ok().filter(|i| *i < below)
725}
726
727/// A number's canonical bytes: a tag, `0` a rational, `1` an exact value of one kind
728/// ([`tower::exact_bytes`]) and `2` a value of the infinitesimals ([`tower::scaled_bytes`]), then its
729/// parts.
730fn number_bytes(n: &Number) -> Vec<u8> {
731    use crate::exact_codec::{write_len, ExactCodec};
732    let mut out = Vec::new();
733    let blob = |bytes: &[u8], out: &mut Vec<u8>| {
734        write_len(bytes.len(), out);
735        out.extend_from_slice(bytes);
736    };
737    if let Some(r) = n.standard_rational() {
738        out.push(0);
739        r.write(&mut out);
740    } else if let Some((kind, bytes)) = tower::exact_bytes(n) {
741        out.push(1);
742        out.push(kind);
743        blob(&bytes, &mut out);
744    } else if let Some((kind, name, residues, bytes)) = tower::scaled_bytes(n) {
745        out.push(2);
746        out.push(kind);
747        blob(name.as_bytes(), &mut out);
748        blob(&residues, &mut out);
749        blob(&bytes, &mut out);
750    } else {
751        out.push(3);
752    }
753    out
754}
755
756fn number_from_bytes(buf: &[u8]) -> Option<Number> {
757    use crate::exact_codec::{read_byte, read_len, ExactCodec, MAX_WIRE_LEN};
758    let mut pos = 0;
759    let blob = |pos: &mut usize| -> Option<Vec<u8>> {
760        let n = read_len(buf, pos, MAX_WIRE_LEN)?;
761        let bytes = buf.get(*pos..pos.checked_add(n)?)?.to_vec();
762        *pos += n;
763        Some(bytes)
764    };
765    let n = match read_byte(buf, &mut pos)? {
766        0 => Number::from_rational(Rational::read(buf, &mut pos)?),
767        1 => {
768            let kind = read_byte(buf, &mut pos)?;
769            tower::from_exact_bytes(kind, &blob(&mut pos)?)?
770        }
771        2 => {
772            let kind = read_byte(buf, &mut pos)?;
773            let name = String::from_utf8(blob(&mut pos)?).ok()?;
774            let residues = blob(&mut pos)?;
775            tower::from_scaled_bytes(kind, &name, &residues, &blob(&mut pos)?)?
776        }
777        _ => return None,
778    };
779    (pos == buf.len()).then_some(n)
780}