Skip to main content

implicit_predicates_gen/point/
mod.rs

1//! Point types: how a point is computed from its parameters, and which
2//! parameters are allowed.
3//!
4//! A point type is read from a declaration in a `.pred` file by
5//! [`parse_points`]:
6//!
7//! ```text
8//! point Explicit(p: F64x3) = p;
9//!
10//! point Lnc(p: F64x3, q: F64x3, t: f64) = q + t*(p - q)
11//!     where 0 <= t, t <= 1;
12//! ```
13//!
14//! # Representation
15//!
16//! The three coordinates of the point are held in one [`PointGraph`], in which
17//! equal subexpressions share a node, also across coordinates: the scalar `t`
18//! of the example is one node used by the three of them. Every operation on an
19//! `F64x3` was split per coordinate when the declaration was read, so each node
20//! is an operation on `f64` values, which is what the later steps evaluate, one
21//! operation at a time, with an error bound for each. Negation is the explicit
22//! node [`PointNode::Neg`] rather than `0 - x`, because it is exact.
23//!
24//! The `where` clause is a list of [`Constraint`]s, each one `lhs <= rhs`
25//! between two [`LinearSum`]s of `f64` parameters, named by index, and an
26//! integer constant: the form `a + b + 3`, ready to be emitted as code.
27
28mod error_bound;
29mod expr;
30mod parse;
31
32use crate::parse::Position;
33
34pub use error_bound::{ErrorBound, MAX_ROUNDS, error_bound};
35pub(crate) use error_bound::{ErrorProgram, Term};
36pub use parse::parse_points;
37pub(crate) use parse::read_declaration;
38
39/// The most nodes that the [`PointGraph`] of a point type may have for
40/// [`generate_points`](crate::generate_points) to emit it: distinct
41/// operations and inputs, counted once each over the three coordinates.
42pub const MAX_POINT_NODES: usize = 1024;
43
44/// The type of a parameter of a point type.
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum ParamType {
47    /// Three `f64` coordinates.
48    F64x3,
49    /// One `f64`.
50    F64,
51}
52
53/// A parameter of a point type.
54#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct PointParam {
56    /// Name of the parameter.
57    pub name: String,
58    /// Its type.
59    pub ty: ParamType,
60    /// Position of its name.
61    pub position: Position,
62}
63
64/// A node of a [`PointGraph`]: an operation on `f64` values.
65#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
66pub enum PointNode {
67    /// A coordinate of an `F64x3` parameter.
68    Coord {
69        /// Index of the parameter in [`PointType::params`].
70        param: usize,
71        /// Coordinate: 0, 1 and 2 are x, y and z.
72        axis: u8,
73    },
74    /// An `f64` parameter, by index.
75    Scalar(usize),
76    /// An integer constant.
77    Int(i64),
78    /// The negation of a node, which is exact.
79    Neg(usize),
80    /// Sum of two nodes.
81    Add(usize, usize),
82    /// Difference of two nodes.
83    Sub(usize, usize),
84    /// Product of two nodes.
85    Mul(usize, usize),
86}
87
88/// The three coordinates of a point as one graph of `f64` operations in which
89/// equal subexpressions share a node.
90///
91/// Children come before their parents and every node is reachable from one of
92/// the roots.
93#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct PointGraph {
95    /// The nodes, in dependency order. Their operands are indices into this
96    /// vector.
97    pub nodes: Vec<PointNode>,
98    /// The nodes holding the x, y and z coordinates of the point.
99    pub roots: [usize; 3],
100}
101
102/// A sum of `f64` parameters and an integer constant: the side of a
103/// [`Constraint`].
104#[derive(Clone, Debug, PartialEq, Eq)]
105pub struct LinearSum {
106    /// Indices of the `f64` parameters added, in the order written; one may
107    /// appear more than once.
108    pub params: Vec<usize>,
109    /// The sum of the integer literals.
110    pub constant: i64,
111}
112
113/// A comparison `lhs <= rhs` of the `where` clause of a point type.
114#[derive(Clone, Debug, PartialEq, Eq)]
115pub struct Constraint {
116    /// The left side.
117    pub lhs: LinearSum,
118    /// The right side.
119    pub rhs: LinearSum,
120    /// Position of the first token of the comparison.
121    pub position: Position,
122}
123
124/// A point type read from a definition file.
125#[derive(Clone, Debug, PartialEq, Eq)]
126pub struct PointType {
127    /// Name of the type.
128    pub name: String,
129    /// Position of its name.
130    pub position: Position,
131    /// The parameters, in order.
132    pub params: Vec<PointParam>,
133    /// The coordinates of the point, computed from the parameters.
134    pub graph: PointGraph,
135    /// The `where` clause: what the parameters must satisfy, all at once.
136    pub constraints: Vec<Constraint>,
137}
138
139impl PointType {
140    /// Tells whether this is an explicit point: one `F64x3` parameter,
141    /// returned as is, without constraints.
142    #[must_use]
143    pub fn is_explicit(&self) -> bool {
144        let [param] = self.params.as_slice() else {
145            return false;
146        };
147        param.ty == ParamType::F64x3
148            && self.constraints.is_empty()
149            && self.graph.roots.iter().enumerate().all(|(axis, &root)| {
150                self.graph.nodes[root]
151                    == PointNode::Coord {
152                        param: 0,
153                        axis: axis as u8,
154                    }
155            })
156    }
157}
158
159/// Builds a [`PointGraph`], giving equal nodes the same index.
160#[derive(Default)]
161pub(crate) struct PointBuilder {
162    nodes: Vec<PointNode>,
163    index: std::collections::BTreeMap<PointNode, usize>,
164}
165
166impl PointBuilder {
167    /// Returns the index of `node`, adding it if it is new.
168    pub(crate) fn intern(&mut self, node: PointNode) -> usize {
169        *self.index.entry(node).or_insert_with(|| {
170            self.nodes.push(node);
171            self.nodes.len() - 1
172        })
173    }
174
175    /// Keeps only the nodes reachable from `roots`, in their original order.
176    pub(crate) fn finish(self, roots: [usize; 3]) -> PointGraph {
177        let mut live = vec![false; self.nodes.len()];
178        for root in roots {
179            live[root] = true;
180        }
181        for id in (0..self.nodes.len()).rev() {
182            if !live[id] {
183                continue;
184            }
185            match self.nodes[id] {
186                PointNode::Neg(x) => live[x] = true,
187                PointNode::Add(x, y) | PointNode::Sub(x, y) | PointNode::Mul(x, y) => {
188                    live[x] = true;
189                    live[y] = true;
190                }
191                _ => {}
192            }
193        }
194        let mut new_id = vec![0; self.nodes.len()];
195        let mut nodes = Vec::new();
196        for (id, node) in self.nodes.iter().enumerate().filter(|&(id, _)| live[id]) {
197            new_id[id] = nodes.len();
198            nodes.push(match *node {
199                PointNode::Neg(x) => PointNode::Neg(new_id[x]),
200                PointNode::Add(x, y) => PointNode::Add(new_id[x], new_id[y]),
201                PointNode::Sub(x, y) => PointNode::Sub(new_id[x], new_id[y]),
202                PointNode::Mul(x, y) => PointNode::Mul(new_id[x], new_id[y]),
203                leaf => leaf,
204            });
205        }
206        PointGraph {
207            nodes,
208            roots: roots.map(|root| new_id[root]),
209        }
210    }
211}