Skip to main content

implicit_predicates_gen/bound/
axis.rs

1//! The per-axis form of the error bound of the filter.
2//!
3//! A form is multihomogeneous when every sum and difference combines nodes
4//! of the same degree in each axis (see [`DiffGraph::axis_degrees`]). For
5//! such a form the bound keeps one size and one uncertainty per axis
6//! instead of a single one. For a call, on each axis `k` (0, 1 and 2 for x,
7//! y and z), let `delta_k` be the largest magnitude of the computed leaves of
8//! that axis, `E_k` the sum of the two largest errors of the points on that
9//! axis, and `D_k = delta_k + E_k`. A node has a degree vector
10//! `g = (g_0, g_1, g_2)`; write `D^g = D_0^g_0 * D_1^g_1 * D_2^g_2` and `e_k`
11//! for the unit vector of axis `k`. Every node gets exact coefficients `m`,
12//! `c`, `alpha` and one `beta_k` per axis such that
13//!
14//! ```text
15//! |true value|      <= m * D^g
16//! |computed value|  <= c * D^g
17//! |computed - true| <= alpha * D^g + sum over k of beta_k * E_k * D^(g - e_k)
18//! ```
19//!
20//! with `beta_k = 0` whenever `g_k = 0`. The rules are those of the global
21//! form ([`filter_bound`]) with `beta` split by axis:
22//!
23//! - a leaf of axis `k` has `g = e_k`, `m = 1 + u`, `c = 1`, `alpha = u`,
24//!   `beta_k = 1` and the other two `beta` zero: its computed value is at
25//!   most `delta_k <= D_k`, and it differs from the true one by the rounding
26//!   of the subtraction, at most `u delta_k`, plus the errors of its two
27//!   points on axis `k`, at most `E_k`;
28//! - a negation is exact and keeps the coefficients of its operand;
29//! - a sum or difference of `x` and `y`, of equal degree vectors, has the
30//!   `m`, `c` and `alpha` of the global form and `beta_k = beta_x,k +
31//!   beta_y,k`;
32//! - a product of `x` and `y` adds the degree vectors, has the `m`, `c` and
33//!   `alpha` of the global form and `beta_k = c_x beta_y,k + m_y beta_x,k`,
34//!   from `x~ y~ - x y = x~ (y~ - y) + y (x~ - x)`: the factor
35//!   `D^(g_y - e_k)` of an error term of `y` times the `D^(g_x)` of `x~`
36//!   gives `D^(g - e_k)`.
37//!
38//! The root gives `epsilon = alpha` and `kappa_k = beta_k`. The rules for
39//! `m`, `c` and `alpha` are those of the global form, and those for `beta`
40//! are linear with the same coefficients, so `epsilon` is the `epsilon` of
41//! the global form and the `kappa_k` add up to its `kappa`.
42//!
43//! # Range of validity
44//!
45//! The node rules assume that no value overflows and that every rounding
46//! error is at most `u` times the computed magnitude. With `G` the degree
47//! vector of the root and `K` its total degree (the degree of the global
48//! form), let `d_min = 2^j_min` and `d_max = 2^j_max` be the powers of two
49//! that [`FilterBound::constants`] gives for the same graph: `j_max` the
50//! largest `j >= 1` with `c_x * 2^(j |g_x|) <= 2^1022` at every node `x`,
51//! where `|g_x|` is the total degree of `x`, and `j_min` the smallest
52//! `j <= -1` with, for `P = 2^j`,
53//!
54//! 1. `epsilon * P^K * 2^-32 >= N * K * c_max * 2^-1074`, with `N` the number
55//!    of products and `c_max` the largest `c`, and
56//! 2. `epsilon * P^K >= 2^-1021`.
57//!
58//! **Claim.** Let the filter compute `d_k = delta_k + E_k` in `f64` on every
59//! axis `k` with `G_k >= 1` and accept the bound only when
60//! `d_min <= d_k <= d_max` on each of them, with `E_k` finite, nonnegative and
61//! not below the true sum of the two largest errors. Let it evaluate the
62//! bound in `f64` as a sum, in any order, of the terms
63//! `EPSILON * D^G` and, for each axis with `G_k >= 1`,
64//! `KAPPA_k * E_k * D^(G - e_k)`, each term a chain of products that starts
65//! with its constant and multiplies the factors in any order, with the `d_k`
66//! in place of the `D_k` and the emitted constants `EPSILON` and `KAPPA_k`,
67//! at least `1 + 2^-30` times the exact ones. Then the computed bound is at
68//! least the error of the computed value of the form. The axes with
69//! `G_k = 0` take no part: no leaf and no factor uses them.
70//!
71//! **Proof.** Write `D_k = d_k / (1 - u)`. A sum of nonnegative values rounds
72//! down by at most a factor `1 - u` (it is exact when the result is
73//! subnormal), so `D_k >= delta_k + E_k`. The node bounds hold for any
74//! `D_k >= delta_k + E_k`, since they only grow with `D_k`, so they hold with
75//! these `D_k`, and `d_k >= d_min` gives `D_k >= d_min`. Let `B` be the exact
76//! bound `epsilon * D^G + sum over k of kappa_k * E_k * D^(G - e_k)` with
77//! these `D_k`.
78//!
79//! - Overflow. Every computed node is at most `c_x * (delta + E)^(g_x)`, and
80//!   on each axis `delta_k + E_k <= D_k <= d_max / (1 - u)`, so it is at most
81//!   `c_x * 2^(j_max |g_x|) * (1 - u)^-|g_x| <= 2^1022 * (1 - u)^-1022`, below
82//!   `2^1023`: no node overflows. An overflow in the evaluation of the bound
83//!   gives an infinite bound, or a NaN one when an infinite partial product
84//!   is then multiplied by an `E_k` of zero; either only makes the filter
85//!   abstain, since it decides on `|value| > bound`, false for both.
86//! - Underflow of the form. A sum or difference whose result is subnormal is
87//!   exact; a product that underflows errs by at most `2^-1075` in absolute
88//!   terms; allow `2^-1074`. As in the global form, that error reaches the
89//!   root multiplied by at most `p'(c_x)` times the product of the bounds on
90//!   the other factors met on the way, where `p(t)` is `c_root` computed by
91//!   the rules with `c_x` replaced by `t`. Degree vectors add along products,
92//!   so the other factors on any path from `x` to the root have degree vector
93//!   `G - g_x` and their bounds multiply to `D^(G - g_x)`; and as in the
94//!   global form `p'(c_x) <= K * c_max`. So the total underflow error is at
95//!   most `U = sum over the products x of K * c_max * 2^-1074 * D^(G - g_x)`.
96//!   On every axis with `g_x,k >= 1` the root has `G_k >= 1`, so `D_k >= P`
97//!   with `P = d_min < 1`: the factors with `D_k >= 1` are at least 1 and the
98//!   others at least `P`, so `D^(g_x) >= P^|g_x| >= P^K`, and
99//!   `D^(G - g_x) <= D^G / P^K`. Condition 1 then gives
100//!   `U <= N * K * c_max * 2^-1074 * D^G / P^K <= 2^-32 * epsilon * D^G
101//!   <= 2^-32 * B`. Terms of second order in these errors are smaller by a
102//!   factor of about `2^-1000` and are absorbed by the same slack, as in the
103//!   global form. The computed value of the form errs by at most
104//!   `(1 + 2^-32) * B`.
105//! - Rounding of the bound. Each term is a chain of `K` products of
106//!   nonnegative values, and its factors `d_k` are `(1 - u) D_k`, exactly.
107//!   While no partial product is subnormal, the computed term is at least the
108//!   exact term with the `D_k` times `(1 - u)^(2K)`: `K` roundings and at most
109//!   `K` uses of a `d_k`. The terms are summed with at most 3 more roundings,
110//!   so the computed bound is at least `(1 + 2^-30) (1 - u)^(2K + 3) B`,
111//!   which is at least `(1 + 2^-31) B` for `K <= 1022`, as in the global
112//!   form, less the losses to underflow.
113//! - Underflow of the bound. A partial product of the term of `EPSILON` is
114//!   `EPSILON` times at most `K` of the `d_k`, each `d_k >= P` or above 1,
115//!   so before rounding it is at least `epsilon * P^K`, at least `2^-1021`
116//!   by condition 2, and after at most `K` roundings at least
117//!   `(1 - u)^K * 2^-1021 > 2^-1022`: those partial products are normal. A
118//!   partial product of the term of `KAPPA_k` may underflow, and loses at
119//!   most `2^-1075`, which the remaining factors then multiply. Those factors
120//!   are `d` values and perhaps `E_k <= D_k`, so the loss is at most
121//!   `2^-1075 * (1 + u)^K * D^G / Q`, where `Q` is the product of the `D_k`
122//!   of the factors already multiplied, counting `D_k` for `E_k`. As above,
123//!   `Q >= P^K`, and condition 2 gives `1 / P^K <= 2^1021 * epsilon`, so the
124//!   loss is at most `2^-54 * (1 + u)^K * epsilon * D^G <= 2^-53 * B`. At
125//!   most `3K` partial products can underflow, so the losses are at most
126//!   `3 * 1022 * 2^-53 * B < 2^-41 * B`.
127//!
128//! The computed bound is then at least `(1 + 2^-31 - 2^-41) * B`, more than
129//! the error `(1 + 2^-32) * B`. The conditions on `P` are those of the global
130//! form with `k = K`, so `d_min` and `d_max` are the same two powers of two.
131//!
132//! A condition on the product of the `D_k` alone is not enough: with
133//! `D_y = D_z = 2^-520` and `D_x = 2^100` the product is far from the limits,
134//! but a product of a leaf of y and a leaf of z is subnormal, and its
135//! rounding error, multiplied by a leaf of x, exceeds the bound. The proof
136//! above needs `D^(g_x) >= P^K` at every product, which bounding each `D_k`
137//! separately gives.
138//!
139//! An axis with `D_k = 0` (every leaf of the axis is zero and `E_k = 0`) is
140//! below the range; [`AxisBound::certain_zero`] says when the value of the
141//! form is then exactly zero.
142
143use super::{
144    FilterBound, check_budget, close_above, d_max_exponent, d_min_exponent, filter_bound,
145    underflow_message,
146};
147use crate::differences::{DiffGraph, DiffNode};
148use crate::dyadic::Dyadic;
149use crate::error::Error;
150use crate::graph::NodeId;
151use crate::parse::Position;
152
153/// The form of the error bound of the filter of a predicate.
154#[derive(Clone, Debug)]
155pub enum FilterForm {
156    /// One size and one uncertainty per axis, for a multihomogeneous form.
157    PerAxis(AxisBound),
158    /// One size and one uncertainty for all the axes.
159    Global(FilterBound),
160}
161
162/// Derives the error bound of the `f64` evaluation of `graph`: the per-axis
163/// form when every term has the same degree in each axis, and the global
164/// form of [`filter_bound`] otherwise.
165///
166/// The per-axis bound follows the rules of the global form with one `beta`
167/// per axis; the documentation of [`AxisBound`] gives them, and its range of
168/// validity.
169///
170/// # Errors
171///
172/// The errors of [`filter_bound`], with the same limits, for either form:
173/// the per-axis form has the same degrees, the same `m`, `c` and `alpha`,
174/// and the same range as the global one.
175pub fn filter_form(graph: &DiffGraph, file: &str, position: Position) -> Result<FilterForm, Error> {
176    match graph.node_axis_degrees() {
177        Some(degrees) => axis_bound(graph, &degrees, file, position).map(FilterForm::PerAxis),
178        None => filter_bound(graph, file, position).map(FilterForm::Global),
179    }
180}
181
182/// The bounds of one node of degree vector `degrees`, in terms of the `D_k`
183/// and `E_k`: `|true value| <= m * D^g`, `|computed value| <= c * D^g` and
184/// `|computed - true| <= alpha * D^g + sum of beta_k * E_k * D^(g - e_k)`.
185#[derive(Clone, Debug)]
186pub(crate) struct AxisNode {
187    pub(crate) degrees: [u32; 3],
188    pub(crate) m: Dyadic,
189    pub(crate) c: Dyadic,
190    pub(crate) alpha: Dyadic,
191    pub(crate) beta: [Dyadic; 3],
192}
193
194/// The exact per-axis error bound of the `f64` evaluation of a
195/// multihomogeneous difference form.
196///
197/// For a call, with `D_k = delta_k + E_k` on each axis (see the
198/// documentation of this module, in the source, for the definitions and the
199/// proof), the `f64` value of the form differs from the exact value at the
200/// exact points by at most
201/// `epsilon * D^G + sum over k of kappa_k * E_k * D^(G - e_k)`, where `G` is
202/// [`AxisBound::degrees`], while on every axis of positive degree the size
203/// computed in `f64`, `d_k = delta_k + E_k`, is in the range of
204/// [`AxisBound::constants`].
205#[derive(Clone, Debug)]
206pub struct AxisBound {
207    /// The degree `G_k` of the form in the differences of each axis.
208    pub degrees: [u32; 3],
209    /// `epsilon / u`, with `u = 2^-53`, as an exact fraction.
210    pub epsilon_in_u: String,
211    /// `kappa_k` of each axis as an exact fraction; zero on an axis of
212    /// degree zero.
213    pub kappa_exact: [String; 3],
214    nodes: Vec<AxisNode>,
215    root: NodeId,
216    d_min_exponent: i64,
217    d_max_exponent: i64,
218}
219
220/// The constants of the per-axis filter of a predicate, as `f64`.
221///
222/// The filter decides when `d_min <= d_k <= d_max` on every axis `k` with
223/// `degrees[k] >= 1`, where `d_k = delta_k + E_k` is computed in `f64`, and
224/// `|value| > epsilon * d^G + sum over k of kappa[k] * E_k * d^(G - e_k)`,
225/// evaluated as a sum of chains of products, each starting with its
226/// constant.
227#[derive(Clone, Copy, Debug, PartialEq)]
228pub struct AxisConstants {
229    /// The degree `G_k` of the form in each axis.
230    pub degrees: [u32; 3],
231    /// The coefficient of `D^G`, rounded up.
232    pub epsilon: f64,
233    /// The coefficient of `E_k * D^(G - e_k)` of each axis, rounded up; zero
234    /// on an axis of degree zero.
235    pub kappa: [f64; 3],
236    /// The smallest `d_k`, computed in `f64` as `delta_k + E_k`, on each axis
237    /// of positive degree, for which the bound covers underflow: a power of
238    /// two below 1.
239    pub d_min: f64,
240    /// The largest `d_k`, computed in `f64` as `delta_k + E_k`, on each axis
241    /// of positive degree, for which no node overflows: a power of two above
242    /// 1.
243    pub d_max: f64,
244}
245
246/// Applies the per-axis rules to `graph`, whose nodes have the degree
247/// vectors `degrees`.
248fn axis_bound(
249    graph: &DiffGraph,
250    degrees: &[[u32; 3]],
251    file: &str,
252    position: Position,
253) -> Result<AxisBound, Error> {
254    // The per-axis coefficients are those of the global form, with `beta`
255    // split in three parts that are each at most the global `beta`: the
256    // same estimate bounds their size.
257    check_budget(graph, file, position)?;
258    let u = Dyadic::pow2(-53);
259    let zero = Dyadic::integer(0);
260    let one = Dyadic::integer(1);
261    let one_plus_u = &one + &u;
262    let mut nodes: Vec<AxisNode> = Vec::with_capacity(graph.nodes.len());
263    for (node, &g) in graph.nodes.iter().zip(degrees) {
264        let bound = match *node {
265            DiffNode::Leaf { axis, .. } => AxisNode {
266                degrees: g,
267                m: one_plus_u.clone(),
268                c: one.clone(),
269                alpha: u.clone(),
270                beta: [0, 1, 2].map(|k| {
271                    if k == usize::from(axis) {
272                        one.clone()
273                    } else {
274                        zero.clone()
275                    }
276                }),
277            },
278            DiffNode::Neg(x) => nodes[x].clone(),
279            DiffNode::Add(x, y) | DiffNode::Sub(x, y) => {
280                let (x, y) = (&nodes[x], &nodes[y]);
281                let c_sum = &x.c + &y.c;
282                AxisNode {
283                    degrees: g,
284                    m: &x.m + &y.m,
285                    c: &c_sum * &one_plus_u,
286                    alpha: &(&x.alpha + &y.alpha) + &(&u * &c_sum),
287                    beta: [0, 1, 2].map(|k| &x.beta[k] + &y.beta[k]),
288                }
289            }
290            DiffNode::Mul(x, y) => {
291                let (x, y) = (&nodes[x], &nodes[y]);
292                let c_product = &x.c * &y.c;
293                AxisNode {
294                    degrees: g,
295                    m: &x.m * &y.m,
296                    c: &c_product * &one_plus_u,
297                    alpha: &(&(&x.c * &y.alpha) + &(&y.m * &x.alpha)) + &(&u * &c_product),
298                    beta: [0, 1, 2].map(|k| &(&x.c * &y.beta[k]) + &(&y.m * &x.beta[k])),
299                }
300            }
301        };
302        nodes.push(bound);
303    }
304    let products = graph
305        .nodes
306        .iter()
307        .filter(|n| matches!(n, DiffNode::Mul(..)))
308        .count();
309    // As in the global form, the budget allows fewer than 2^19 nodes.
310    let products = u32::try_from(products).expect("fewer than 2^32 products");
311    let root = &nodes[graph.root];
312    let total = graph.degrees[graph.root];
313    let d_max_exponent = d_max_exponent(nodes.iter().zip(&graph.degrees).map(|(n, &g)| (&n.c, g)))
314        .map_err(|degree| Error::at(file, position, super::overflow_message(degree)))?;
315    let c_max = nodes
316        .iter()
317        .map(|n| &n.c)
318        .max()
319        .expect("a graph has a root");
320    let d_min_exponent = d_min_exponent(&root.alpha, c_max, total, products)
321        .ok_or_else(|| Error::at(file, position, underflow_message(total)))?;
322    Ok(AxisBound {
323        degrees: root.degrees,
324        epsilon_in_u: (&root.alpha * &Dyadic::pow2(53)).fraction(),
325        kappa_exact: [0, 1, 2].map(|k| root.beta[k].fraction()),
326        nodes,
327        root: graph.root,
328        d_min_exponent,
329        d_max_exponent,
330    })
331}
332
333impl AxisBound {
334    /// Whether `n u <= epsilon < n u (1 + 2^-40)`, exactly.
335    pub fn epsilon_is_u_times(&self, n: u64) -> bool {
336        let target = &Dyadic::integer(n) * &Dyadic::pow2(-53);
337        close_above(&self.nodes[self.root].alpha, &target)
338    }
339
340    /// Whether `n <= kappa_k < n (1 + 2^-40)` on the axis `axis`, exactly;
341    /// false for an axis above 2.
342    pub fn kappa_is(&self, axis: usize, n: u64) -> bool {
343        self.nodes[self.root]
344            .beta
345            .get(axis)
346            .is_some_and(|beta| close_above(beta, &Dyadic::integer(n)))
347    }
348
349    /// Whether every term has degree at least 1 in every axis, so that the
350    /// form is exactly zero when every leaf of one axis is exactly zero: a
351    /// sum of nodes of positive degree in that axis is zero when both are,
352    /// and a product has a factor of positive degree in it.
353    pub fn certain_zero(&self) -> bool {
354        self.degrees.iter().all(|&g| g >= 1)
355    }
356
357    /// The constants of the filter.
358    ///
359    /// `epsilon` and each `kappa` are the exact values times `1 + 2^-30`,
360    /// rounded up. `d_min` and `d_max` are the powers of two of the range of
361    /// validity, derived in the documentation of this module (in the source):
362    /// those of the global form of the same graph.
363    pub fn constants(&self) -> AxisConstants {
364        let inflate = &Dyadic::integer(1) + &Dyadic::pow2(-30);
365        let root = &self.nodes[self.root];
366        AxisConstants {
367            degrees: self.degrees,
368            epsilon: (&root.alpha * &inflate).round_up(),
369            kappa: [0, 1, 2].map(|k| (&root.beta[k] * &inflate).round_up()),
370            d_min: Dyadic::pow2(self.d_min_exponent).round_up(),
371            d_max: Dyadic::pow2(self.d_max_exponent).round_up(),
372        }
373    }
374}
375
376#[cfg(test)]
377#[path = "axis_tests.rs"]
378mod tests;