use std::fmt::Write;
use super::statements::{node_name, node_statements};
use super::{comment, literal, power_of_two};
use crate::bound::{AxisConstants, FilterConstants};
use crate::differences::{DiffGraph, DiffNode};
use crate::graph::NodeId;
#[derive(Clone, Copy, Debug)]
pub(crate) enum FilterKind {
Global(FilterConstants),
PerAxis(AxisConstants, bool),
}
pub(crate) fn write_constants(out: &mut String, c: &FilterConstants) {
let constant = |out: &mut String, doc: &str, name: &str, ty: &str, value: String| {
comment(out, "///", 0, doc);
writeln!(out, "pub const {name}: {ty} = {value};\n").unwrap();
};
constant(
out,
"The degree `k` of the predicate in the differences of coordinates.",
"DEGREE",
"u32",
c.degree.to_string(),
);
constant(
out,
"The coefficient of `D^k` in the error bound of [`filter`], \
`(EPSILON * D + KAPPA * E) * D^(k - 1)`, rounded up. `E` is the `e` given to \
the filter and `D` is `E` plus the largest magnitude of a difference of \
coordinates.",
"EPSILON",
"f64",
literal(c.epsilon),
);
constant(
out,
"The coefficient of `E * D^(k - 1)` in the error bound of [`filter`], rounded up.",
"KAPPA",
"f64",
literal(c.kappa),
);
constant(
out,
&format!(
"The smallest `D` for which [`filter`] decides, `2^{}`: below it, underflow \
could exceed the error bound.",
power_of_two(c.d_min)
),
"D_MIN",
"f64",
literal(c.d_min),
);
constant(
out,
&format!(
"The largest `D` for which [`filter`] decides, `2^{}`: above it, a computed \
value could overflow.",
power_of_two(c.d_max)
),
"D_MAX",
"f64",
literal(c.d_max),
);
}
pub(crate) fn bound_operations(degree: u32) -> u32 {
4 + (degree - 1)
}
pub(crate) fn bound_rounding_factors(degree: u32) -> u32 {
bound_operations(degree) + (degree - 1)
}
pub(crate) fn bound_statements(degree: u32) -> String {
let mut bound = String::from("(EPSILON * d + KAPPA * e)");
for _ in 1..degree {
bound.push_str(" * d");
}
format!("let d = delta + e;\nlet bound = {bound};\n")
}
pub(crate) fn write_filter(
out: &mut String,
graph: &DiffGraph,
points: usize,
degree: u32,
) -> bool {
comment(
out,
"///",
0,
"The sign of the predicate from an evaluation of its main form in `f64`, \
when the rounding error cannot change it.\n\n\
The points given are approximations of exact points. `e` is the sum of the \
two largest per-point errors, where the error of a point is an upper bound on \
the absolute error of each coordinate of its approximation; `e` must be \
finite, non-negative and rounded upward, and it is `0.0` for explicit points. \
A NaN or negative `e` never decides.\n\n\
`None` means that this stage cannot decide: the value is too close to zero \
for its error bound, the scale of the differences is outside \
`D_MIN..=D_MAX`, `e` is negative or NaN, or an input is not finite. The \
result is never `Some(Sign::Zero)`.",
);
out.push_str("#[inline]\n");
writeln!(
out,
"pub fn filter(p: [&[f64; 3]; {points}], e: f64) -> Option<Sign> {{"
)
.unwrap();
for statement in node_statements(graph, "p", false) {
writeln!(out, "{statement}").unwrap();
}
let leaves: Vec<NodeId> = (0..graph.nodes.len())
.filter(|&id| matches!(graph.nodes[id], DiffNode::Leaf { .. }))
.collect();
comment(
out,
"//",
4,
"A NaN leaf can be skipped by the comparisons, but it makes `det` NaN.",
);
for (i, &leaf) in leaves.iter().enumerate() {
if i == 0 {
writeln!(out, "let delta = {}.abs();", node_name(graph, leaf)).unwrap();
} else {
writeln!(
out,
"let delta = max(delta, {}.abs());",
node_name(graph, leaf)
)
.unwrap();
}
}
comment(
out,
"//",
4,
&format!(
"The bound takes {} f64 operations; with the rounding of `d` counted at each \
of its {degree} uses, at most {} roundings reach it.",
bound_operations(degree),
bound_rounding_factors(degree)
),
);
out.push_str(&bound_statements(degree));
out.push_str(
"let decided = (e >= 0.0) & (D_MIN..=D_MAX).contains(&d) & (det.abs() > bound);\n\
let sign = if det > 0.0 { Sign::Positive } else { Sign::Negative };\n\
decided.then_some(sign)\n}\n\n",
);
leaves.len() > 1
}
pub(crate) fn write_max(out: &mut String) {
comment(
out,
"///",
0,
"The larger of `a` and `b`, or `b` if they are unordered.",
);
out.push_str(
"#[inline]\n\
fn max(a: f64, b: f64) -> f64 {\n\
if a > b { a } else { b }\n\
}\n\n",
);
}