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}