Skip to main content

implicit_predicates_gen/
bound.rs

1//! The error bound of the floating-point filter of a predicate.
2//!
3//! The filter evaluates the difference form in `f64` and accepts the sign of
4//! the result when its magnitude exceeds a bound on the rounding error. The
5//! bound is derived here, node by node, in exact arithmetic.
6
7use crate::differences::{DiffGraph, DiffNode};
8use crate::dyadic::Dyadic;
9use crate::error::Error;
10use crate::graph::NodeId;
11use crate::parse::Position;
12
13/// The exponent of the limit on the magnitude of every computed node.
14const OVERFLOW_EXPONENT: i64 = 1022;
15
16/// The highest degree the filter accepts. Every `c` is at least 1, so a
17/// node of higher degree exceeds `2^1022` for every `D > 1`.
18pub(crate) const MAX_DEGREE: u32 = 1022;
19
20/// The most bits that the exact coefficients of all the nodes may take
21/// together, by the estimate of [`coefficient_bits`].
22pub(crate) const MAX_COEFFICIENT_BITS: u64 = 1 << 26;
23
24/// The bounds of one node of degree `g`, in terms of `D` and `E`:
25/// `|true value| <= m * D^g`, `|computed value| <= c * D^g` and
26/// `|computed - true| <= (alpha * D + beta * E) * D^(g - 1)`.
27#[derive(Clone, Debug)]
28pub(crate) struct NodeBound {
29    pub(crate) degree: u32,
30    pub(crate) m: Dyadic,
31    pub(crate) c: Dyadic,
32    pub(crate) alpha: Dyadic,
33    pub(crate) beta: Dyadic,
34}
35
36/// The exact error bound of the `f64` evaluation of a difference form.
37///
38/// For a call, let `delta` be the largest magnitude of the computed leaves
39/// (the differences of the coordinates of the approximations of the points),
40/// `E` the sum of the two largest errors of the approximations, and
41/// `D = delta + E`. The `f64` value of the form differs from the exact value
42/// at the exact points by at most `(epsilon * D + kappa * E) * D^(k - 1)`,
43/// where `k` is the degree, as long as no product underflows; see
44/// [`FilterBound::constants`] for the range in which underflow and overflow
45/// are accounted for.
46#[derive(Clone, Debug)]
47pub struct FilterBound {
48    /// The degree `k` of the form in the differences.
49    pub degree: u32,
50    /// `epsilon / u`, with `u = 2^-53`, as an exact fraction.
51    pub epsilon_in_u: String,
52    /// `kappa` as an exact fraction.
53    pub kappa_exact: String,
54    /// The number of products `N` in the form; a product used several times
55    /// is counted once.
56    pub products: u32,
57    nodes: Vec<NodeBound>,
58    root: NodeId,
59    d_min_exponent: i64,
60    d_max_exponent: i64,
61}
62
63/// The constants of the filter of a predicate, as `f64`.
64///
65/// The filter decides when `d_min <= D <= d_max` and
66/// `|value| > (epsilon * D + kappa * E) * D^(degree - 1)`.
67#[derive(Clone, Copy, Debug, PartialEq)]
68pub struct FilterConstants {
69    /// The degree `k` of the form.
70    pub degree: u32,
71    /// The coefficient of `D^k`, rounded up.
72    pub epsilon: f64,
73    /// The coefficient of `E * D^(k - 1)`, rounded up.
74    pub kappa: f64,
75    /// The smallest `D` for which the bound covers underflow: a power of two
76    /// below 1.
77    pub d_min: f64,
78    /// The largest `D` for which no node overflows: a power of two above 1.
79    pub d_max: f64,
80}
81
82/// Derives the error bound of the `f64` evaluation of `graph`.
83///
84/// With `u = 2^-53`, every node of degree `g` gets coefficients `m`, `c`,
85/// `alpha` and `beta` such that `|true value| <= m * D^g`,
86/// `|computed value| <= c * D^g` and
87/// `|computed - true| <= (alpha * D + beta * E) * D^(g - 1)`:
88///
89/// - a leaf has `g = 1`, `m = 1 + u`, `c = 1`, `alpha = u`, `beta = 1`;
90/// - a negation is exact and keeps the coefficients of its operand;
91/// - the sum or difference of `x` and `y`, of equal degree, has
92///   `m = m_x + m_y`, `c = (c_x + c_y)(1 + u)`,
93///   `alpha = alpha_x + alpha_y + u (c_x + c_y)` and `beta = beta_x + beta_y`;
94/// - the product of `x` and `y` has `g = g_x + g_y`, `m = m_x m_y`,
95///   `c = c_x c_y (1 + u)`, `alpha = c_x alpha_y + m_y alpha_x + u c_x c_y`
96///   and `beta = c_x beta_y + m_y beta_x`.
97///
98/// The root gives `epsilon = alpha` and `kappa = beta`. Every node is
99/// analysed once, in the order of the graph, so a shared subexpression counts
100/// as the single computation it is.
101///
102/// # Errors
103///
104/// Returns an error, at `position` (the name of the predicate) in `file`,
105/// when the degree is so high that no `D > 1` keeps every computed node
106/// below `2^1022`, or that no `D < 1` keeps the bound clear of underflow (see
107/// [`FilterBound::constants`]), and when the exact coefficients would take
108/// more than `2^26` bits in all. The coefficients of a sum are about 53
109/// bits longer than those of its operands, so a sum written as one chain of
110/// more than about 1,560 terms reaches that limit; the same terms summed in a
111/// balanced tree, with `let`, need far fewer bits. The size is estimated
112/// node by node before any coefficient is computed, so a definition that
113/// is too large fails quickly.
114pub fn filter_bound(
115    graph: &DiffGraph,
116    file: &str,
117    position: Position,
118) -> Result<FilterBound, Error> {
119    check_budget(graph, file, position)?;
120    let u = Dyadic::pow2(-53);
121    let one = Dyadic::integer(1);
122    let one_plus_u = &one + &u;
123    let mut nodes: Vec<NodeBound> = Vec::with_capacity(graph.nodes.len());
124    for (id, node) in graph.nodes.iter().enumerate() {
125        let bound = match *node {
126            DiffNode::Leaf { .. } => NodeBound {
127                degree: 1,
128                m: one_plus_u.clone(),
129                c: one.clone(),
130                alpha: u.clone(),
131                beta: one.clone(),
132            },
133            DiffNode::Neg(x) => nodes[x].clone(),
134            DiffNode::Add(x, y) | DiffNode::Sub(x, y) => {
135                let (x, y) = (&nodes[x], &nodes[y]);
136                let c_sum = &x.c + &y.c;
137                NodeBound {
138                    degree: x.degree,
139                    m: &x.m + &y.m,
140                    c: &c_sum * &one_plus_u,
141                    alpha: &(&x.alpha + &y.alpha) + &(&u * &c_sum),
142                    beta: &x.beta + &y.beta,
143                }
144            }
145            DiffNode::Mul(x, y) => {
146                let (x, y) = (&nodes[x], &nodes[y]);
147                let c_product = &x.c * &y.c;
148                NodeBound {
149                    degree: x.degree + y.degree,
150                    m: &x.m * &y.m,
151                    c: &c_product * &one_plus_u,
152                    alpha: &(&(&x.c * &y.alpha) + &(&y.m * &x.alpha)) + &(&u * &c_product),
153                    beta: &(&x.c * &y.beta) + &(&y.m * &x.beta),
154                }
155            }
156        };
157        debug_assert_eq!(bound.degree, graph.degrees[id]);
158        nodes.push(bound);
159    }
160    let products = graph
161        .nodes
162        .iter()
163        .filter(|n| matches!(n, DiffNode::Mul(..)))
164        .count();
165    let root = &nodes[graph.root];
166    let degree = root.degree;
167    // Every node takes at least 55 bits, so the budget allows fewer than
168    // 2^19 nodes.
169    let products = u32::try_from(products).expect("fewer than 2^32 products");
170    let d_max_exponent = d_max_exponent(nodes.iter().map(|n| (&n.c, n.degree)))
171        .map_err(|degree| Error::at(file, position, overflow_message(degree)))?;
172    let c_max = nodes
173        .iter()
174        .map(|n| &n.c)
175        .max()
176        .expect("a graph has a root");
177    let d_min_exponent = d_min_exponent(&root.alpha, c_max, degree, products)
178        .ok_or_else(|| Error::at(file, position, underflow_message(degree)))?;
179    Ok(FilterBound {
180        degree,
181        epsilon_in_u: (&root.alpha * &Dyadic::pow2(53)).fraction(),
182        kappa_exact: root.beta.fraction(),
183        products,
184        nodes,
185        root: graph.root,
186        d_min_exponent,
187        d_max_exponent,
188    })
189}
190
191/// An upper bound on the bits of all the exact coefficients of `graph`
192/// together, found without computing them, saturated at `u64::MAX`.
193///
194/// Every coefficient of a node is `P(u)` for a polynomial `P` with
195/// nonnegative integer coefficients, so it is an integer times `2^(-53 d)`,
196/// with `d` the degree of `P`, of magnitude at most `P(1)`: its mantissa
197/// has at most `53 d + log2 P(1) + 1` bits. Following the rules of
198/// [`filter_bound`], that is at most 55 bits for a leaf, the operand's
199/// for a negation, 55 more than the larger operand's for a sum or
200/// difference, and 55 more than the two operands' together for a product.
201/// The result is the sum over the nodes.
202fn coefficient_bits(graph: &DiffGraph) -> u64 {
203    let mut bits: Vec<u64> = Vec::with_capacity(graph.nodes.len());
204    for node in &graph.nodes {
205        bits.push(match *node {
206            DiffNode::Leaf { .. } => 55,
207            DiffNode::Neg(x) => bits[x],
208            DiffNode::Add(x, y) | DiffNode::Sub(x, y) => bits[x].max(bits[y]).saturating_add(55),
209            DiffNode::Mul(x, y) => bits[x].saturating_add(bits[y]).saturating_add(55),
210        });
211    }
212    bits.iter().fold(0u64, |total, &b| total.saturating_add(b))
213}
214
215/// Checks, before any coefficient is computed, the two limits that do not
216/// depend on the coefficients: the degree of every node and the size of
217/// the exact coefficients.
218fn check_budget(graph: &DiffGraph, file: &str, position: Position) -> Result<(), Error> {
219    if let Some(&degree) = graph.degrees.iter().filter(|&&g| g > MAX_DEGREE).max() {
220        return Err(Error::at(file, position, overflow_message(degree)));
221    }
222    if coefficient_bits(graph) > MAX_COEFFICIENT_BITS {
223        return Err(Error::at(
224            file,
225            position,
226            format!(
227                "the expression is too large for the error bound of the filter: its exact \
228                 coefficients would take more than 2^{} bits in all; a long sum written as \
229                 one chain is the usual cause, and the same terms summed in a balanced \
230                 tree with `let` need far fewer",
231                MAX_COEFFICIENT_BITS.ilog2()
232            ),
233        ));
234    }
235    Ok(())
236}
237
238fn underflow_message(degree: u32) -> String {
239    format!(
240        "the degree {degree} is too high for the floating-point filter: \
241         no scale of the inputs below 1 keeps its error bound clear of underflow"
242    )
243}
244
245fn overflow_message(degree: u32) -> String {
246    format!(
247        "the degree {degree} is too high for the floating-point filter: \
248         no scale of the inputs above 1 keeps every computed value below 2^{OVERFLOW_EXPONENT}"
249    )
250}
251
252impl FilterBound {
253    /// Whether `n u <= epsilon < n u (1 + 2^-40)`, exactly.
254    ///
255    /// The exact `epsilon` is slightly above an integer multiple of `u`,
256    /// because of the terms in `u^2` and beyond.
257    pub fn epsilon_is_u_times(&self, n: u64) -> bool {
258        let target = &Dyadic::integer(n) * &Dyadic::pow2(-53);
259        close_above(&self.nodes[self.root].alpha, &target)
260    }
261
262    /// Whether `n <= kappa < n (1 + 2^-40)`, exactly.
263    pub fn kappa_is(&self, n: u64) -> bool {
264        close_above(&self.nodes[self.root].beta, &Dyadic::integer(n))
265    }
266
267    /// The constants of the filter.
268    ///
269    /// `epsilon` and `kappa` are the exact values times `1 + 2^-30`, rounded
270    /// up; the inflation covers the rounding of the evaluation of the bound
271    /// itself and an allowance for products that underflow. `d_min` is the
272    /// smallest power of two below 1 for which that allowance covers
273    /// underflow and the bound stays a normal number, and `d_max` the
274    /// largest above 1 for which no computed node exceeds `2^1022`; both
275    /// exist, or [`filter_bound`] would have returned an error.
276    pub fn constants(&self) -> FilterConstants {
277        let inflate = &Dyadic::integer(1) + &Dyadic::pow2(-30);
278        let root = &self.nodes[self.root];
279        FilterConstants {
280            degree: self.degree,
281            epsilon: (&root.alpha * &inflate).round_up(),
282            kappa: (&root.beta * &inflate).round_up(),
283            d_min: Dyadic::pow2(self.d_min_exponent).round_up(),
284            d_max: Dyadic::pow2(self.d_max_exponent).round_up(),
285        }
286    }
287}
288
289/// The exponent of `d_max`: the largest `j >= 1` such that
290/// `c_x * 2^(j g_x) <= 2^1022` at every node `x`.
291///
292/// For `D <= d_max` every computed node then has magnitude at most
293/// `c_x * D^(g_x) <= 2^1022` (for `D < 1`, `D^(g_x) <= 1 < d_max^(g_x)`),
294/// a quarter of the overflow threshold `2^1024`, so no intermediate value
295/// and not the result overflows. An overflow in the evaluation of the bound
296/// itself gives an infinite bound, or a NaN one when an infinite partial
297/// product is then multiplied by an uncertainty of zero; either only makes
298/// the filter abstain, since it decides on `|value| > bound`, false for both.
299///
300/// The nodes are given as their `c` and their degree `g`. Returns the degree
301/// of a node that overflows for every `j >= 1` if there is no such `j`.
302fn d_max_exponent<'a>(nodes: impl Iterator<Item = (&'a Dyadic, u32)>) -> Result<i64, u32> {
303    // `c_x * 2^(j g) <= 2^1022` iff `j g <= 1022 - s` with `s` the
304    // smallest integer such that `c_x <= 2^s`.
305    let (j, degree) = nodes
306        .map(|(c, degree)| {
307            let j = (OVERFLOW_EXPONENT - c.ceil_log2()).div_euclid(i64::from(degree));
308            (j, degree)
309        })
310        .min()
311        .expect("a graph has a root");
312    if j >= 1 { Ok(j) } else { Err(degree) }
313}
314
315/// The exponent of `d_min`: the smallest `j <= -1` such that `P = 2^j`
316/// satisfies
317///
318/// 1. `epsilon * P^k * 2^-32 >= N * k * c_max * 2^-1074`, with `N` the
319///    number of products and `c_max` the largest `c` of any node, and
320/// 2. `epsilon * P^k >= 2^-1021`.
321///
322/// Both then hold for every larger `j`.
323///
324/// Why this is sound, for `D >= d_min` and with `B` the rounding-error
325/// bound `(epsilon * D + kappa * E) * D^(k - 1)`:
326///
327/// - The node rules assume that every rounding error is at most `u` times
328///   the computed magnitude. A sum or difference whose result is
329///   subnormal is exact, but a product that underflows can err by up to
330///   `2^-1075` in absolute terms; allow `2^-1074` per product.
331/// - An absolute error at a product `x` reaches the root multiplied by
332///   the derivative of the computation with respect to `x`: a sum over
333///   the paths from `x` to the root of the products of the other factors
334///   met on the way, each with its `(1 + u)` rounding. Every factor is
335///   bounded by its `c * D^g`, so the derivative is at most
336///   `p'(c_x) * D^(k - g_x)`, where `p(t)` is `c_root` computed by the
337///   rules with `c_x` replaced by `t`. That polynomial has nonnegative
338///   coefficients and degree at most `k / g_x`, since degrees add along
339///   products, so `c_x p'(c_x) <= (k / g_x) p(c_x)`; with `c_x >= 1` and
340///   `g_x >= 2` the derivative is at most `k * c_max * D^(k - g_x)`.
341///   Terms of second order in these errors are products of two such
342///   errors, each at most `2^-1074` times factors bounded as above, so they
343///   are smaller than the first-order terms by a factor of about `2^-1000`
344///   and are absorbed by the slack of `2^-32` of the bound below.
345/// - The total underflow error is therefore
346///   `U <= N * k * c_max * 2^-1074 * max(1, D^k)`. For `d_min <= D <= 1`,
347///   condition 1 gives `U <= 2^-32 * epsilon * d_min^k
348///   <= 2^-32 * epsilon * D^k`; for `D > 1` it gives the same, because
349///   condition 1 holds with `d_min^k < 1`. So `U <= 2^-32 * B`, and the
350///   value of the form errs by at most `(1 + 2^-32) * B`.
351/// - The emitted constants are at least `(1 + 2^-30)` times the exact
352///   ones, so the exact value of the emitted bound is at least
353///   `(1 + 2^-30) * B`. Its `f64` evaluation, `d = delta + e`, then
354///   `(EPSILON * d + KAPPA * e) * d * ... * d` with `k - 1` products by
355///   `d`, takes `k + 3` operations, each of which can round down by a
356///   factor `1 - u`. The rounding of `d` enters the bound once for each of
357///   the `k` times `d` is used, so the computed bound is at least
358///   `(1 - u)^(2k + 2)` times its exact value: `k + 3` factors for the
359///   operations and `k - 1` more for the uses of `d` beyond the first
360///   (`e` is an upper bound on `E`, rounded up by the caller, so it adds
361///   none; a caller that rounded it to nearest instead would add one factor
362///   for each use of `e` and of `d`, `3k + 3` in all, within `2k + 8` only
363///   for `k <= 5`). That holds while no partial result is subnormal:
364///   condition 2 keeps every partial result built from `EPSILON * d` at least
365///   `epsilon * min(1, D)^k >= 2^-1021`, and the term `KAPPA * e`, which
366///   may underflow, loses at most `2^-1075`, a fraction `2^-54` of the sum
367///   it joins. The constants were derived for up to `2k + 8` such factors,
368///   and `(1 + 2^-30)(1 - u)^(2k + 8) >= 1 + 2^-31` for `k <= 1022` (higher
369///   degrees are rejected), so the computed bound is at least
370///   `(1 + 2^-31) * B`, which exceeds the error `(1 + 2^-32) * B`.
371///
372/// Returns `None` if no `j <= -1` satisfies both.
373fn d_min_exponent(epsilon: &Dyadic, c_max: &Dyadic, degree: u32, products: u32) -> Option<i64> {
374    let factor = Dyadic::integer(u64::from(products) * u64::from(degree));
375    let underflow = &(&factor * c_max) * &Dyadic::pow2(-1074 + 32);
376    let normal = Dyadic::pow2(-1021);
377    let k = i64::from(degree);
378    let covers = |j: i64| {
379        let scaled = epsilon * &Dyadic::pow2(j * k);
380        scaled >= underflow && scaled >= normal
381    };
382    if !covers(-1) {
383        return None;
384    }
385    let mut j = -1;
386    while covers(j - 1) {
387        j -= 1;
388    }
389    Some(j)
390}
391
392/// Whether `target <= value < target * (1 + 2^-40)`.
393fn close_above(value: &Dyadic, target: &Dyadic) -> bool {
394    let limit = target * &(&Dyadic::integer(1) + &Dyadic::pow2(-40));
395    target <= value && *value < limit
396}
397
398#[cfg(test)]
399mod tests;
400
401mod axis;
402
403pub use axis::{AxisBound, AxisConstants, FilterForm, filter_form};