axiolid_exact/tower.rs
1//! Nested square roots: values in a tower of adjoined radicals.
2//!
3//! [`crate::Root2`] holds one square root over plain numbers. Arc
4//! constructions need more: a hit on a circle is `a + b*sqrt(D)`, and a
5//! distance or a second construction from that hit takes another square
6//! root of an expression that already contains `sqrt(D)`. This module
7//! represents such values exactly.
8//!
9//! # Representation
10//!
11//! A [`Tower`] is a list of radicands `r_1, ..., r_k`, each built only
12//! from the radicals before it. A [`Nested`] value at level `k` is
13//! `a + b*sqrt(r_k)` with `a` and `b` at level `k - 1`, stored flat as a
14//! coefficient vector of length `2^k` (low half `a`, high half `b`).
15//!
16//! # Why no canonical form is needed
17//!
18//! Radicals need not be independent: `sqrt(8)` and `sqrt(2)` may both be
19//! adjoined, and then `sqrt(8) - 2*sqrt(2)` has non-zero coefficients but
20//! value zero. That is fine. Both the product rule
21//! `(a + b√r)(c + d√r) = (ac + bd·r) + (ad + bc)√r` and the sign rule below
22//! are identities about real numbers, true whatever the coefficients are.
23//! So signs are exact, including exact zeros, without ever reducing to a
24//! basis, which is what keeps this module small.
25//!
26//! # Sign
27//!
28//! `sign(a + b*sqrt(r))`, recursively by level: if `a` and `b*sqrt(r)`
29//! agree in sign (or one is zero) that is the answer; otherwise it is
30//! `sign(a) * sign(a^2 - b^2*r)`, a value one level down. Level 0 asks the
31//! arithmetic `T` directly, so the same code runs as the interval filter
32//! and as the exact fallback (see [`crate::certify()`]).
33//!
34//! # Cost
35//!
36//! Each level squares once, so the polynomial degree in the inputs, and
37//! with it exact-tier mantissa length, doubles per level; a product costs
38//! five products one level down. Depth is capped at [`MAX_DEPTH`].
39
40use axiolid_guarantees::Sign;
41
42use crate::arith::{sign_product, Arith};
43use crate::certify::ExactError;
44
45/// Most radicals one tower may hold. A product at depth `k` costs `5^k`
46/// base products, and exact mantissas grow `2^k`-fold in degree.
47pub const MAX_DEPTH: usize = 6;
48
49/// A value in a [`Tower`]: `2^level` coefficients over `T`.
50///
51/// Only meaningful together with the tower that made it.
52#[derive(Debug, Clone, PartialEq)]
53pub struct Nested<T> {
54 coeffs: Vec<T>,
55}
56
57impl<T> Nested<T> {
58 /// How many radicals this value can involve.
59 #[must_use]
60 pub fn level(&self) -> usize {
61 self.coeffs.len().trailing_zeros() as usize
62 }
63
64 /// The coefficients, low half first at every level.
65 #[must_use]
66 pub fn coeffs(&self) -> &[T] {
67 &self.coeffs
68 }
69}
70
71/// The radicals adjoined so far.
72#[derive(Debug, Clone, Default)]
73pub struct Tower<T> {
74 /// Radicand `i` (0-based), lifted to exactly level `i`.
75 radicands: Vec<Nested<T>>,
76}
77
78impl<T: Arith> Tower<T> {
79 /// An empty tower: values are plain `T`.
80 #[must_use]
81 pub fn new() -> Self {
82 Self {
83 radicands: Vec::new(),
84 }
85 }
86
87 /// Number of radicals adjoined.
88 #[must_use]
89 pub fn depth(&self) -> usize {
90 self.radicands.len()
91 }
92
93 /// A plain value.
94 #[must_use]
95 pub fn value(&self, value: T) -> Nested<T> {
96 Nested {
97 coeffs: vec![value],
98 }
99 }
100
101 /// The value of a finite `f64` (see [`Arith::from_f64`]).
102 #[must_use]
103 pub fn from_f64(&self, value: f64) -> Nested<T> {
104 self.value(T::from_f64(value))
105 }
106
107 /// Adjoin `sqrt(radicand)` and return it as a value.
108 ///
109 /// The radicand must not be negative. That is not checked here, since
110 /// the interval tier may be unable to tell; [`Tower::sign`] of any
111 /// value involving a negative radicand returns `None`, which the exact
112 /// tier reports as [`ExactError::Undefined`].
113 ///
114 /// # Errors
115 ///
116 /// [`ExactError::TooDeep`] beyond [`MAX_DEPTH`] radicals.
117 ///
118 /// # Panics
119 ///
120 /// If `radicand` came from a different tower (its level exceeds this
121 /// tower's depth).
122 pub fn sqrt(&mut self, radicand: &Nested<T>) -> Result<Nested<T>, ExactError> {
123 let level = self.depth();
124 if level >= MAX_DEPTH {
125 return Err(ExactError::TooDeep);
126 }
127 self.own(radicand);
128 self.radicands.push(self.lift(radicand, level));
129 let mut coeffs = vec![T::from_f64(0.0); 1 << (level + 1)];
130 coeffs[1 << level] = T::from_f64(1.0);
131 Ok(Nested { coeffs })
132 }
133
134 /// `x + y`.
135 #[must_use]
136 pub fn add(&self, x: &Nested<T>, y: &Nested<T>) -> Nested<T> {
137 self.zip(x, y, T::add)
138 }
139
140 /// `x - y`.
141 #[must_use]
142 pub fn sub(&self, x: &Nested<T>, y: &Nested<T>) -> Nested<T> {
143 self.zip(x, y, T::sub)
144 }
145
146 /// `-x`.
147 #[must_use]
148 pub fn neg(&self, x: &Nested<T>) -> Nested<T> {
149 self.own(x);
150 Nested {
151 coeffs: x.coeffs.iter().map(T::neg).collect(),
152 }
153 }
154
155 /// `x * y`.
156 #[must_use]
157 pub fn mul(&self, x: &Nested<T>, y: &Nested<T>) -> Nested<T> {
158 self.own(x);
159 self.own(y);
160 let level = x.level().max(y.level());
161 let (x, y) = (self.lift(x, level), self.lift(y, level));
162 Nested {
163 coeffs: self.mul_at(level, &x.coeffs, &y.coeffs),
164 }
165 }
166
167 /// The sign of `x`, or `None` when `T` cannot decide or a radicand
168 /// involved is negative.
169 ///
170 /// Approximate arithmetics first evaluate `x` numerically through
171 /// [`Arith::sqrt_enclosure`]; that decides whenever `x` is visibly away
172 /// from zero. Otherwise, and always for exact arithmetics, signs are
173 /// decided by case analysis on the coefficients.
174 #[must_use]
175 pub fn sign(&self, x: &Nested<T>) -> Option<Sign> {
176 self.own(x);
177 // An interval sign is sound whenever it is decided. A numeric
178 // Zero can only come from [0, 0], i.e. from coefficients that are
179 // literally zero, where Zero is also the exact answer.
180 if let Some(sign) = self
181 .numeric(x.level(), &x.coeffs)
182 .and_then(|value| value.sign())
183 {
184 return Some(sign);
185 }
186 self.sign_at(x.level(), &x.coeffs)
187 }
188
189 /// Numerical value of a coefficient vector, for arithmetics that can
190 /// enclose square roots. `None` for exact arithmetics, or when some
191 /// radicand might be negative.
192 fn numeric(&self, level: usize, x: &[T]) -> Option<T> {
193 if level == 0 {
194 return Some(x[0].clone());
195 }
196 let half = 1 << (level - 1);
197 let (a, b) = x.split_at(half);
198 let root = self
199 .numeric(level - 1, &self.radicands[level - 1].coeffs)?
200 .sqrt_enclosure()?;
201 let a = self.numeric(level - 1, a)?;
202 let b = self.numeric(level - 1, b)?;
203 Some(a.add(&b.mul(&root)))
204 }
205
206 /// The sign of `x - y`.
207 #[must_use]
208 pub fn cmp(&self, x: &Nested<T>, y: &Nested<T>) -> Option<Sign> {
209 self.sign(&self.sub(x, y))
210 }
211
212 fn own(&self, x: &Nested<T>) {
213 assert!(
214 x.level() <= self.depth(),
215 "a Nested value is only meaningful in the tower that made it"
216 );
217 }
218
219 /// `x` padded with zero coefficients to `level`.
220 fn lift(&self, x: &Nested<T>, level: usize) -> Nested<T> {
221 let mut coeffs = x.coeffs.clone();
222 coeffs.resize(1 << level, T::from_f64(0.0));
223 Nested { coeffs }
224 }
225
226 fn zip(&self, x: &Nested<T>, y: &Nested<T>, op: impl Fn(&T, &T) -> T) -> Nested<T> {
227 self.own(x);
228 self.own(y);
229 let level = x.level().max(y.level());
230 let (x, y) = (self.lift(x, level), self.lift(y, level));
231 Nested {
232 coeffs: x
233 .coeffs
234 .iter()
235 .zip(&y.coeffs)
236 .map(|(a, b)| op(a, b))
237 .collect(),
238 }
239 }
240
241 /// Product of two coefficient vectors of length `2^level`.
242 fn mul_at(&self, level: usize, x: &[T], y: &[T]) -> Vec<T> {
243 if level == 0 {
244 return vec![x[0].mul(&y[0])];
245 }
246 let half = 1 << (level - 1);
247 let (a, b) = x.split_at(half);
248 let (c, d) = y.split_at(half);
249 let radicand = &self.radicands[level - 1].coeffs;
250 let ac = self.mul_at(level - 1, a, c);
251 let bd = self.mul_at(level - 1, b, d);
252 let bdr = self.mul_at(level - 1, &bd, radicand);
253 let ad = self.mul_at(level - 1, a, d);
254 let bc = self.mul_at(level - 1, b, c);
255 let mut out: Vec<T> = ac.iter().zip(&bdr).map(|(p, q)| p.add(q)).collect();
256 out.extend(ad.iter().zip(&bc).map(|(p, q)| p.add(q)));
257 out
258 }
259
260 fn sign_at(&self, level: usize, x: &[T]) -> Option<Sign> {
261 if level == 0 {
262 return x[0].sign();
263 }
264 let half = 1 << (level - 1);
265 let (a, b) = x.split_at(half);
266 let radicand = &self.radicands[level - 1].coeffs;
267 let sr = self.sign_at(level - 1, radicand)?;
268 if sr == Sign::Negative {
269 return None;
270 }
271 let sa = self.sign_at(level - 1, a)?;
272 // b*sqrt(r) has the sign of b, unless r is zero.
273 let sb = if sr == Sign::Zero {
274 Sign::Zero
275 } else {
276 self.sign_at(level - 1, b)?
277 };
278 if sb == Sign::Zero {
279 return Some(sa);
280 }
281 if sa == Sign::Zero || sa == sb {
282 return Some(sb);
283 }
284 let a2 = self.mul_at(level - 1, a, a);
285 let b2 = self.mul_at(level - 1, b, b);
286 let b2r = self.mul_at(level - 1, &b2, radicand);
287 let diff: Vec<T> = a2.iter().zip(&b2r).map(|(p, q)| p.sub(q)).collect();
288 let dominance = self.sign_at(level - 1, &diff)?;
289 Some(sign_product(sa, dominance))
290 }
291}