implicit_predicates_gen/lib.rs
1//! Code generator for exact geometric predicates.
2//!
3//! Turns the definition of a predicate or of a point type into specialized
4//! Rust code for the `implicit-predicates` crates.
5//!
6//! **Status: early development.** [`generate`] takes the definitions of a
7//! `.pred` file and returns, for each predicate, the Rust module of its
8//! stages: a floating-point filter, exact stages on floating-point
9//! expansions, and the chain that runs them (see [`generate`] for the items
10//! of the module), and the module of its checks for tests: an exact
11//! evaluator and inputs at the edge of the error bound of the filter. The
12//! `implicit-predicates-gen` command line tool writes those modules for a
13//! directory of definitions, formatted with `rustfmt`, or checks with
14//! `--check` that the files are up to date; its README gives the command
15//! that regenerates the code of the `implicit-predicates` crate.
16//!
17//! The steps of the generation are public as well. [`parse`] takes a `.pred`
18//! file, written in a small subset of Rust, and returns each [`Predicate`] as
19//! an expression [`Graph`] in which equal subexpressions share a node.
20//! Mistakes in the file are reported as an [`Error`] that carries the position
21//! of the offending token.
22//!
23//! A definition is a function over `Point3` parameters returning `Sign`. Its
24//! body holds `let` bindings and a final expression built from `+`, `-` (binary
25//! and unary), `*`, parentheses, integer literals, the coordinates `p.x`,
26//! `p.y`, `p.z` of a parameter and the names of earlier bindings. A second
27//! function with the same name and parameters, marked `#[exact]`, gives the
28//! form used for exact evaluation. Comments cost nothing; doc comments
29//! (`///`, `//!`, `/** */`, `/*! */`) are rejected, and so are predicate
30//! names that cannot name a module, such as keywords.
31//!
32//! A `.pred` file may also declare point types, which [`parse_points`] reads
33//! and [`parse`] skips: the word `point`, a name, parameters of type `F64x3`
34//! or `f64`, `=`, an expression and `;`, as in
35//! `point Lnc(p: F64x3, q: F64x3, t: f64) = q + t*(p - q) where 0 <= t, t <= 1;`.
36//! The expression uses `+`, `-`, `*`, parentheses, integer literals and the
37//! parameters; operations on an `F64x3` are per coordinate, a product has at
38//! least one `f64` factor, and the value is an `F64x3`. The optional `where`
39//! clause is a list of comparisons `<=` between sums of `f64` parameters and
40//! integers. A [`PointType`] holds the three coordinates as one
41//! [`PointGraph`] of `f64` operations in which equal subexpressions share a
42//! node, and the clause as a list of [`Constraint`]s. The limit on brackets
43//! and the guarantee of never panicking are those of [`parse`].
44//! [`generate_points`] emits the module of the point types of a file, with
45//! their constructors, their error bounds and their exact coordinates;
46//! [`error_bound`] derives the rule of the error bound of each coordinate,
47//! and documents why it is an upper bound. [`generate_points`] has budgets
48//! too: [`MAX_ROUNDS`] roundings on a chain of operations, and
49//! [`MAX_POINT_NODES`] distinct operations and inputs in the coordinates of a
50//! point, which bounds the size of the emitted code.
51//!
52//! The only limit of [`parse`] is the nesting of brackets, at most 32 deep,
53//! which keeps the stack small: sums, products and runs of unary minus are
54//! read in loops, so [`parse`] cannot panic or overflow the stack of the
55//! calling thread, even in a debug build with a 1 MiB stack, and it accepts
56//! a sum of any length. The later steps have budgets instead: a sum written
57//! as one chain of more than about 1,560 terms is rejected by
58//! [`filter_bound`], because the exact coefficients of its error bound grow
59//! with the length of the chain, an expression too large to expand is
60//! rejected by [`canonical`], and [`generate`] rejects main forms of more
61//! than 256 distinct subexpressions, for which the search of the edge cases
62//! of the checks would be slow. Each reports the limit it hit, before doing
63//! the work; the work section of [`generate`] lists every budget.
64//!
65//! Two analyses work on the graphs. [`check_same_polynomial`] expands the
66//! main and the exact form of a predicate with [`canonical`] and requires
67//! them to be the same, nonzero polynomial. [`difference_graph`] checks that
68//! a form is homogeneous in differences of point coordinates, that is, built
69//! from differences of one coordinate of two points, combined by sums of
70//! terms of equal degree and by products, and returns it as a [`DiffGraph`]
71//! with the degree of every node.
72//!
73//! [`filter_bound`] derives, in exact arithmetic, the error bound of
74//! evaluating a [`DiffGraph`] in `f64`: with `D` the largest computed
75//! difference plus the two largest errors of the points, and `E` the sum of
76//! those two errors, the computed value differs from the exact one by at most
77//! `(epsilon * D + kappa * E) * D^(k - 1)`, where `k` is the degree.
78//! [`FilterBound::constants`] gives `epsilon` and `kappa` rounded up to `f64`,
79//! and the range of `D`, between two powers of two, in which overflow and
80//! underflow cannot invalidate the bound. [`filter_form`] chooses the
81//! per-axis form instead when every term has the same degree in each axis
82//! ([`DiffGraph::axis_degrees`]): an [`AxisBound`] keeps one size and one
83//! uncertainty per axis, with the same `epsilon`, one `kappa` per axis that
84//! add up to the global `kappa`, and the same range for each axis.
85//!
86//! [`exact_plan_raw`] and [`exact_plan_differences`] derive, for each exact
87//! stage, the range of input magnitudes for which every exact product of the
88//! arithmetic core stays in its domain and no value overflows, and the
89//! worst-case length of every temporary; [`generate`] sizes the stack
90//! buffers of the emitted stages from them. [`exact_plan_spans_translated`]
91//! and [`exact_plan_spans_untranslated`] derive the same for the stages over
92//! arena spans, whose leaves are expansions of up to
93//! [`MAX_COORD_COMPONENTS`] components: the range bounds every nonzero
94//! component, so it does not depend on the type of the points.
95
96#![forbid(unsafe_code)]
97
98mod bound;
99mod cursor;
100mod differences;
101mod dyadic;
102mod emit;
103mod error;
104mod expr;
105mod graph;
106mod limits;
107mod parse;
108mod point;
109mod polynomial;
110mod range;
111
112pub use bound::{
113 AxisBound, AxisConstants, FilterBound, FilterConstants, FilterForm, filter_bound, filter_form,
114};
115pub use differences::{DiffGraph, DiffNode, difference_graph};
116pub use emit::{Generated, generate, generate_points};
117pub use error::Error;
118pub use graph::{Graph, Node, NodeId};
119pub use parse::{Position, Predicate, parse};
120pub use point::{
121 Constraint, ErrorBound, LinearSum, MAX_POINT_NODES, MAX_ROUNDS, ParamType, PointGraph,
122 PointNode, PointParam, PointType, error_bound, parse_points,
123};
124pub use polynomial::{Polynomial, canonical, canonical_of_differences, check_same_polynomial};
125pub use range::{
126 ExactPlan, ExactRange, Leaves, MAX_COORD_COMPONENTS, exact_plan_differences, exact_plan_raw,
127 exact_plan_spans_translated, exact_plan_spans_untranslated,
128};