Skip to main content

implicit_predicates_gen/emit/
points.rs

1//! Rust code for the point types of a file.
2//!
3//! The generated module holds `PointError`, one type per declaration with
4//! its constructors and its implementation of `Point`, and `AnyPoint`, an
5//! enum of all of them. An explicit type (one `F64x3` returned as is) is a
6//! transparent wrapper of `[f64; 3]`. Every other type stores its
7//! parameters, the coordinates computed in `f64` and their error bounds,
8//! computed by its constructor with the program of [`crate::error_bound`];
9//! its exact coordinates are written by [`super::point_exact`]. The checks
10//! module of [`super::point_checks`] gives the exact coordinates for tests.
11
12use std::fmt::Write;
13
14use super::point_checks::write_checks;
15use super::point_eval::{write_error_helpers, write_exact_helpers};
16use super::point_exact::ExactPlan;
17use super::point_rules::{check_names, check_size};
18use super::point_types::{write_any_point, write_explicit, write_implicit};
19use super::{Generated, comment, literal, notice};
20use crate::error::Error;
21use crate::point::{
22    ErrorProgram, LinearSum, ParamType, PointNode, PointType, error_bound, parse_points,
23};
24
25/// The largest graph whose coordinates the documentation spells out.
26pub(crate) const MAX_RENDERED_NODES: usize = 64;
27
28/// Generates the module of the point types declared in `source`, or `None`
29/// if it declares none. `file` and `regenerate` are as for
30/// [`generate`](super::generate).
31///
32/// The module, named `points`, holds `PointError`, the error of the
33/// constructors; for each declared type, a struct with `new`, which rejects
34/// inputs that are not finite and parameters outside the `where` clause,
35/// `new_unchecked`, which accepts anything, one accessor per parameter of an
36/// implicit type, and an implementation of `Point`; and `AnyPoint`, an enum
37/// with one variant per type, which implements `Point` too and converts
38/// from each type with `From`. Its checks module holds, for each type that
39/// is not explicit, `exact_<name>`, its exact coordinates evaluated with
40/// the exact numbers of `implicit-predicates-oracle`.
41///
42/// The constructor of a type that is not explicit evaluates its
43/// coordinates in `f64` with an error bound for each, by the rule of
44/// [`crate::error_bound`]; `Point::exact` writes them as expansions, and
45/// fails with `ExactError::OutOfRange` when an input is not finite, an
46/// operand of a sum of expansions reaches `2^1020`, or an operand of an
47/// exact product leaves the product domain of the core.
48///
49/// # Errors
50///
51/// A syntax error in a declaration; a point type whose name does not start
52/// with an uppercase letter; a parameter whose name does not start with a
53/// lowercase letter; a parameter or type with a name that the generated code
54/// uses; an integer that is not exactly an `f64`; an error bound with more
55/// roundings than [`crate::MAX_ROUNDS`]; exact coordinates whose temporaries
56/// take more than 4,096 `f64`; coordinates of more than
57/// [`crate::MAX_POINT_NODES`] distinct operations and inputs; or a `where`
58/// comparison that, after cancelling the parameters on both sides, is not
59/// one parameter against an integer of magnitude at most `2^53`, the only
60/// form emitted for now.
61pub fn generate_points(
62    source: &str,
63    file: &str,
64    regenerate: &str,
65) -> Result<Option<Generated>, Error> {
66    let points = parse_points(source, file)?;
67    if points.is_empty() {
68        return Ok(None);
69    }
70    let mut types = Vec::new();
71    for point in &points {
72        types.push(prepare(point, file, &types)?);
73    }
74    let notice = notice(file, regenerate);
75    let mut stages = String::new();
76    write_module(&mut stages, &types, &notice);
77    let mut checks = String::new();
78    write_checks(&mut checks, &types, &notice);
79    Ok(Some(Generated {
80        name: "points".to_string(),
81        stages,
82        checks,
83    }))
84}
85
86/// A point type with what its emission needs.
87pub(crate) struct TypeInfo<'a> {
88    pub(crate) point: &'a PointType,
89    /// For each parameter, its index among the parameters of its kind.
90    pub(crate) slot: Vec<usize>,
91    /// The number of `F64x3` and of `f64` parameters.
92    pub(crate) vectors: usize,
93    pub(crate) scalars: usize,
94    /// The name of its function in the checks.
95    pub(crate) check_name: String,
96    /// For a type that is not explicit, its error program, slack factor,
97    /// exact plan and the comparisons of its `where` clause.
98    pub(crate) implicit: Option<Implicit>,
99}
100
101pub(crate) struct Implicit {
102    pub(crate) program: ErrorProgram,
103    pub(crate) rounds: u32,
104    pub(crate) slack: f64,
105    pub(crate) exact: ExactPlan,
106    /// Each comparison of the `where` clause, as the parameter it bounds
107    /// and the bound.
108    pub(crate) bounds: Vec<(usize, Bound)>,
109}
110
111/// One comparison of a `where` clause, reduced to one parameter.
112#[derive(Clone, Copy)]
113pub(crate) enum Bound {
114    AtLeast(f64),
115    AtMost(f64),
116}
117
118impl TypeInfo<'_> {
119    /// The text of a leaf: `c[i][k]` for a coordinate, `s[i]` for an `f64`
120    /// parameter, a literal for an integer.
121    pub(crate) fn leaf(&self, node: PointNode) -> Option<String> {
122        match node {
123            PointNode::Coord { param, axis } => Some(format!("c[{}][{axis}]", self.slot[param])),
124            PointNode::Scalar(param) => Some(format!("s[{}]", self.slot[param])),
125            #[allow(clippy::cast_precision_loss)]
126            PointNode::Int(n) => Some(literal(n as f64)),
127            _ => None,
128        }
129    }
130
131    /// The parameters of `evaluate` and `write_exact`, after any others.
132    pub(crate) fn array_params(&self) -> String {
133        let mut text = format!("c: [[f64; 3]; {}]", self.vectors);
134        if self.scalars > 0 {
135            write!(text, ", s: [f64; {}]", self.scalars).unwrap();
136        }
137        text
138    }
139
140    /// The arguments that pass `values` (each parameter, by name or as
141    /// `self.name`) to `evaluate` or `write_exact`.
142    pub(crate) fn array_args(&self, prefix: &str) -> String {
143        let of = |ty: ParamType| {
144            let names: Vec<String> = (self.point.params.iter())
145                .filter(|p| p.ty == ty)
146                .map(|p| format!("{prefix}{}", p.name))
147                .collect();
148            format!("[{}]", names.join(", "))
149        };
150        let mut text = of(ParamType::F64x3);
151        if self.scalars > 0 {
152            write!(text, ", {}", of(ParamType::F64)).unwrap();
153        }
154        text
155    }
156}
157
158/// Checks `point` and derives what its emission needs; `earlier` are the
159/// types before it.
160fn prepare<'a>(
161    point: &'a PointType,
162    file: &str,
163    earlier: &[TypeInfo<'_>],
164) -> Result<TypeInfo<'a>, Error> {
165    check_names(point, file)?;
166    let check_name = format!("exact_{}", snake_case(&point.name));
167    if let Some(other) = earlier.iter().find(|t| t.check_name == check_name) {
168        return Err(Error::at(
169            file,
170            point.position,
171            format!(
172                "`{}` and `{}` would both be checked by `{check_name}`: rename one of them",
173                other.point.name, point.name
174            ),
175        ));
176    }
177    let mut counts = [0, 0];
178    let slot = (point.params.iter())
179        .map(|p| {
180            let kind = usize::from(p.ty == ParamType::F64);
181            counts[kind] += 1;
182            counts[kind] - 1
183        })
184        .collect();
185    let implicit = if point.is_explicit() {
186        None
187    } else {
188        let bound = error_bound(point, file)?;
189        let program = ErrorProgram::of(point);
190        Some(Implicit {
191            program,
192            rounds: bound.rounds.into_iter().max().unwrap_or(0),
193            slack: bound.slack,
194            exact: ExactPlan::of(point, file)?,
195            bounds: constraint_bounds(point, file)?,
196        })
197    };
198    // After the other checks, which report a long chain of roundings or
199    // large exact coordinates more precisely.
200    check_size(point, file)?;
201    Ok(TypeInfo {
202        point,
203        slot,
204        vectors: counts[0],
205        scalars: counts[1],
206        check_name,
207        implicit,
208    })
209}
210
211/// `CamelCase` as `camel_case`.
212fn snake_case(name: &str) -> String {
213    let mut out = String::new();
214    let mut previous_lower = false;
215    for c in name.chars() {
216        if c.is_uppercase() && previous_lower {
217            out.push('_');
218        }
219        previous_lower = c.is_lowercase() || c.is_ascii_digit();
220        out.extend(c.to_lowercase());
221    }
222    out
223}
224
225/// The comparisons of the `where` clause of `point`, each reduced to one
226/// parameter against an `f64` that is exactly the integer of the source.
227fn constraint_bounds(point: &PointType, file: &str) -> Result<Vec<(usize, Bound)>, Error> {
228    let mut bounds = Vec::new();
229    for constraint in &point.constraints {
230        // lhs <= rhs  <=>  sum of coefficient * parameter <= k.
231        let mut coefficients = vec![0i64; point.params.len()];
232        let count = |sum: &LinearSum, sign: i64, coefficients: &mut Vec<i64>| {
233            for &p in &sum.params {
234                coefficients[p] = coefficients[p].saturating_add(sign);
235            }
236        };
237        count(&constraint.lhs, 1, &mut coefficients);
238        count(&constraint.rhs, -1, &mut coefficients);
239        let k = i128::from(constraint.rhs.constant) - i128::from(constraint.lhs.constant);
240        let used: Vec<(usize, i64)> = (coefficients.iter().copied().enumerate())
241            .filter(|&(_, c)| c != 0)
242            .collect();
243        let limit = 1i128 << 53;
244        let bound = match used.as_slice() {
245            [(param, 1)] if k.abs() <= limit => (*param, Bound::AtMost(k as f64)),
246            [(param, -1)] if k.abs() <= limit => (*param, Bound::AtLeast(-k as f64)),
247            _ => {
248                return Err(Error::at(
249                    file,
250                    constraint.position,
251                    "only a comparison of one parameter with an integer of magnitude at most \
252                     2^53, once the parameters on both sides cancel, can be checked exactly \
253                     for now",
254                ));
255            }
256        };
257        bounds.push(bound);
258    }
259    Ok(bounds)
260}
261
262/// Writes the module of the point types `types`, starting with `notice`.
263fn write_module(out: &mut String, types: &[TypeInfo<'_>], notice: &str) {
264    out.push_str(notice);
265    let names: Vec<String> = types
266        .iter()
267        .map(|t| format!("[`{}`]", t.point.name))
268        .collect();
269    comment(
270        out,
271        "//!",
272        0,
273        &format!(
274            "The point types {}, and [`AnyPoint`], which holds a point of any of them.",
275            names.join(", ")
276        ),
277    );
278    let implicit: Vec<&Implicit> = types.iter().filter_map(|t| t.implicit.as_ref()).collect();
279    let mut core = vec!["ExactCoords", "ExactError", "Point", "Scratch"];
280    if !implicit.is_empty() {
281        core.extend(["Mark", "ScratchExhausted", "Span", "Weight"]);
282        if implicit.iter().any(|i| i.exact.uses_products()) {
283            core.push("eft");
284        }
285        core.push("expansion");
286    }
287    writeln!(
288        out,
289        "\nuse core::fmt;\n\nuse implicit_predicates_core::{{{}}};\n",
290        core.join(", ")
291    )
292    .unwrap();
293    write_error(out);
294    for info in types {
295        match &info.implicit {
296            None => write_explicit(out, info),
297            Some(implicit) => write_implicit(out, info, implicit),
298        }
299    }
300    write_any_point(out, types);
301    if !implicit.is_empty() {
302        write_error_helpers(out, &implicit);
303        write_exact_helpers(out, &implicit);
304    }
305}
306
307fn write_error(out: &mut String) {
308    out.push_str(
309        "/// Why the constructor of a point type rejected its inputs.\n\
310         #[derive(Clone, Copy, Debug, PartialEq, Eq)]\n\
311         pub enum PointError {\n\
312         /// An input is NaN or infinite.\n\
313         NonFinite,\n\
314         /// The parameters do not satisfy the conditions of the type.\n\
315         ParamOutOfRange,\n\
316         }\n\n\
317         impl fmt::Display for PointError {\n\
318         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\
319         f.write_str(match self {\n\
320         PointError::NonFinite => \"an input of the point is not finite\",\n\
321         PointError::ParamOutOfRange => \"a parameter of the point is out of range\",\n\
322         })\n}\n}\n\n\
323         impl core::error::Error for PointError {}\n\n",
324    );
325}