use std::fmt::Write;
use super::program::{Program, Scalar, Step};
use super::{comment, literal};
pub(crate) enum Check {
Coordinates(&'static str),
Differences(&'static str),
}
pub(crate) struct Stage<'a> {
pub(crate) name: &'static str,
pub(crate) doc: &'a str,
pub(crate) program: &'a Program,
pub(crate) check: Check,
}
pub(crate) fn write_stage(out: &mut String, stage: &Stage, points: usize) {
let program = stage.program;
comment(out, "///", 0, stage.doc);
out.push_str("#[cold]\n#[inline(never)]\n");
writeln!(
out,
"pub fn {}(p: [&[f64; 3]; {points}]) -> Option<Sign> {{",
stage.name
)
.unwrap();
let (scaled, spare) = program.product_scratch();
let footprint = program.footprint();
let scratch = if scaled > 0 {
format!(
", {} of them the scratch of the products of two expansions",
scaled + spare
)
} else {
String::new()
};
comment(
out,
"//",
4,
&format!(
"Stack: {footprint} f64 ({} bytes) of expansions{scratch}.",
8 * footprint
),
);
let is_difference = |step: &&Step| matches!(step, Step::Difference { .. });
match stage.check {
Check::Coordinates(range) => {
let coordinates: Vec<String> = program
.coordinates
.iter()
.map(|&(point, axis)| format!("p[{point}][{axis}]"))
.collect();
writeln!(
out,
"let coordinates = [{}];\n\
if !coordinates.into_iter().all(|x| in_range(x, {range})) {{\n\
return None;\n}}",
coordinates.join(", ")
)
.unwrap();
}
Check::Differences(range) => {
let mut differences = Vec::new();
for step in program.steps.iter().filter(is_difference) {
write_step(out, step, program);
if let Step::Difference { node, .. } = step {
differences.push(*node);
}
}
let list = |prefix: &str| {
let names: Vec<String> =
differences.iter().map(|n| format!("{prefix}{n}")).collect();
names.join(", ")
};
comment(
out,
"//",
4,
"Each difference must be exact, and within the range.",
);
writeln!(
out,
"let errors = [{}];\n\
let differences = [{}];\n\
if !(errors.into_iter().all(|r| r == 0.0)\n\
&& differences.into_iter().all(|x| in_range(x, {range})))\n\
{{\nreturn None;\n}}",
list("r"),
list("v")
)
.unwrap();
}
}
if scaled > 0 {
writeln!(
out,
"let mut scaled = [0.0; {scaled}];\nlet mut spare = [0.0; {spare}];"
)
.unwrap();
}
for step in program.steps.iter().filter(|s| !is_difference(s)) {
write_step(out, step, program);
}
writeln!(
out,
"Some(expansion::sign(&e{0}[..l{0}]))\n}}\n",
program.root
)
.unwrap();
}
fn write_step(out: &mut String, step: &Step, program: &Program) {
let slice = |n: usize| format!("&e{n}[..l{n}]");
if let Some(node) = step.expansion() {
if !matches!(step, Step::Zero { .. }) {
writeln!(
out,
"let mut e{node} = [0.0; {}];",
program.capacities[node]
)
.unwrap();
}
}
let line = match *step {
Step::Difference {
node,
axis,
minuend,
subtrahend,
} => format!(
"let (v{node}, r{node}) = eft::two_diff(p[{minuend}][{axis}], p[{subtrahend}][{axis}]);"
),
Step::NegateScalar { node, x } => format!("let v{node} = -{};", scalar(x)),
Step::Zero { node } => format!("let e{node}: [f64; 0] = [];\nlet l{node} = 0;"),
Step::FromF64 { node, x } => {
format!(
"let l{node} = expansion::from_f64({}, &mut e{node});",
scalar(x)
)
}
Step::FromTwoDiff { node, a, b } => format!(
"let l{node} = expansion::from_two_diff({}, {}, &mut e{node});",
scalar(a),
scalar(b)
),
Step::FromTwoProduct { node, a, b } => format!(
"let l{node} = expansion::from_two_product({}, {}, &mut e{node});",
scalar(a),
scalar(b)
),
Step::Scale { node, e, b } => format!(
"let l{node} = expansion::scale({}, {}, &mut e{node});",
slice(e),
scalar(b)
),
Step::Product { node, x, y } => format!(
"let l{node} = product({}, {}, &mut scaled, &mut spare, &mut e{node});",
slice(x),
slice(y)
),
Step::Sum { node, x, y } => format!(
"let l{node} = expansion::sum({}, {}, &mut e{node});",
slice(x),
slice(y)
),
Step::Diff { node, x, y } => format!(
"let l{node} = expansion::diff({}, {}, &mut e{node});",
slice(x),
slice(y)
),
Step::Negate { node, x } => format!(
"e{node}[..l{x}].copy_from_slice({});\n\
expansion::negate(&mut e{node}[..l{x}]);\n\
let l{node} = l{x};",
slice(x)
),
};
out.push_str(&line);
out.push('\n');
}
fn scalar(x: Scalar) -> String {
match x {
Scalar::Coordinate { point, axis } => format!("p[{point}][{axis}]"),
Scalar::Local(node) => format!("v{node}"),
Scalar::Constant(c) => literal(c),
}
}
pub(crate) fn write_in_range(out: &mut String) {
comment(
out,
"///",
0,
"Whether `x` is zero or has a magnitude in `min..=max`; false for NaN and \
infinities.",
);
out.push_str(
"fn in_range(x: f64, (min, max): (f64, f64)) -> bool {\n\
x == 0.0 || (min..=max).contains(&x.abs())\n\
}\n\n",
);
}
pub(crate) fn write_product(out: &mut String) {
comment(
out,
"///",
0,
"Writes the expansion of `x * y` to `out` and returns its length: the sum of \
the scalings of `y` by each component of `x`, each scaling written to \
`scaled`, the partial sums alternating between `spare` and `out`.",
);
out.push_str(
"fn product(x: &[f64], y: &[f64], scaled: &mut [f64], spare: &mut [f64], \
out: &mut [f64]) -> usize {\n\
let Some((&first, rest)) = x.split_first() else {\n\
return 0;\n\
};\n\
// Start so that the last partial sum is written to `out`.\n\
let (mut partial, mut next) = if rest.len() % 2 == 0 { (out, spare) } else { (spare, out) };\n\
let mut length = expansion::scale(y, first, partial);\n\
for &component in rest {\n\
let scaled_length = expansion::scale(y, component, scaled);\n\
length = expansion::sum(&partial[..length], &scaled[..scaled_length], next);\n\
core::mem::swap(&mut partial, &mut next);\n\
}\n\
length\n\
}\n\n",
);
}