implicit-predicates-gen 0.0.2

Code generator for exact geometric predicates over explicit and implicit points
Documentation
//! Tests for the code generated for a predicate.

use implicit_predicates_core::eft::{PRODUCT_OPERAND_MAX, PRODUCT_OPERAND_MIN};
use implicit_predicates_gen::{
    ExactRange, FilterForm, Generated, Leaves, MAX_COORD_COMPONENTS, difference_graph,
    exact_plan_differences, exact_plan_raw, exact_plan_spans_translated,
    exact_plan_spans_untranslated, filter_bound, filter_form, generate, parse,
};
use proc_macro2::TokenStream;

const ORIENT3D: &str = include_str!("fixtures/orient3d.pred");
const INSPHERE: &str = include_str!("fixtures/insphere.pred");
const POINTS: &str = include_str!("fixtures/points.pred");

/// How the tests say to regenerate the code.
const REGENERATE: &str = "run the tests.";

fn generated(source: &str, file: &str) -> Generated {
    let mut all = generate(source, file, REGENERATE).unwrap();
    assert_eq!(all.len(), 1);
    all.remove(0)
}

/// The text after `pub const NAME: TYPE = ` up to the next `;`.
fn constant<'a>(code: &'a str, name: &str, ty: &str) -> &'a str {
    let prefix = format!("pub const {name}: {ty} = ");
    let start = code
        .find(&prefix)
        .unwrap_or_else(|| panic!("no constant {name}"))
        + prefix.len();
    let end = start + code[start..].find(';').unwrap();
    code[start..end].trim()
}

/// Parses a float literal of the output and checks it is exactly `expected`.
fn assert_literal(text: &str, expected: f64) {
    let value: f64 = text.parse().unwrap_or_else(|_| panic!("{text}"));
    assert_eq!(
        value.to_bits(),
        expected.to_bits(),
        "{text} is not {expected:e}"
    );
}

fn assert_range(code: &str, name: &str, range: ExactRange) {
    let text = constant(code, name, "(f64, f64)");
    let inner = text.strip_prefix('(').unwrap().strip_suffix(')').unwrap();
    let (min, max) = inner.split_once(',').unwrap();
    assert_literal(min.trim(), range.min);
    assert_literal(max.trim(), range.max);
}

#[test]
fn generation_is_deterministic() {
    for (source, file) in [
        (ORIENT3D, "predicates/orient3d.pred"),
        (INSPHERE, "predicates/insphere.pred"),
    ] {
        let first = generate(source, file, REGENERATE).unwrap();
        let second = generate(source, file, REGENERATE).unwrap();
        assert_eq!(first.len(), second.len());
        for (a, b) in first.iter().zip(&second) {
            assert_eq!(a.name, b.name);
            assert_eq!(a.stages, b.stages);
            assert_eq!(a.checks, b.checks);
        }
    }
}

#[test]
fn the_output_parses_as_rust() {
    // The output is lexed here; that it is valid Rust is shown by the command
    // line tool, which formats it with rustfmt, and by the generated file,
    // which is compiled as part of the workspace.
    for (source, file) in [
        (ORIENT3D, "predicates/orient3d.pred"),
        (INSPHERE, "predicates/insphere.pred"),
    ] {
        let g = generated(source, file);
        g.stages
            .parse::<TokenStream>()
            .unwrap_or_else(|e| panic!("{file}: {e}"));
    }
}

/// The literals of an array constant `[a, b, c]`.
fn array(text: &str) -> Vec<&str> {
    let inner = text.strip_prefix('[').unwrap().strip_suffix(']').unwrap();
    inner.split(',').map(str::trim).collect()
}

#[test]
fn the_constants_in_the_output_are_the_derived_ones() {
    let g = generated(ORIENT3D, "predicates/orient3d.pred");
    let code = &g.stages;
    let p = parse(ORIENT3D, "t.pred").unwrap().remove(0);
    let diff = difference_graph(&p.main, "t.pred", p.position).unwrap();
    let FilterForm::PerAxis(bound) = filter_form(&diff, "t.pred", p.position).unwrap() else {
        panic!("orient3d is multihomogeneous");
    };
    let constants = bound.constants();
    assert_eq!(constant(code, "DEGREES", "[u32; 3]"), "[1, 1, 1]");
    assert_eq!(constants.degrees, [1, 1, 1]);
    assert_literal(constant(code, "EPSILON", "f64"), constants.epsilon);
    let kappa = array(constant(code, "KAPPA", "[f64; 3]"));
    assert_eq!(kappa.len(), 3);
    for (text, &value) in kappa.iter().zip(&constants.kappa) {
        assert_literal(text, value);
    }
    assert_literal(constant(code, "D_MIN", "f64"), constants.d_min);
    assert_literal(constant(code, "D_MAX", "f64"), constants.d_max);
    // The exact derived values: no numeric test can tell a `KAPPA` of 6
    // from one of 3, the largest error reachable on one axis.
    assert!(bound.epsilon_is_u_times(46));
    for axis in 0..3 {
        assert!(bound.kappa_is(axis, 6), "axis {axis}");
        assert!(!bound.kappa_is(axis, 5) && !bound.kappa_is(axis, 7));
    }
    assert_eq!(constants.d_min, 2f64.powi(-324));
    assert_eq!(constants.d_max, 2f64.powi(339));
    // The same range and epsilon as the global form of the same graph.
    let global = filter_bound(&diff, "t.pred", p.position)
        .unwrap()
        .constants();
    assert_eq!(
        (global.d_min, global.d_max),
        (constants.d_min, constants.d_max)
    );
    assert_eq!(global.epsilon, constants.epsilon);
    let (min, max) = (PRODUCT_OPERAND_MIN, PRODUCT_OPERAND_MAX);
    let one = exact_plan_differences(&diff, Leaves::One, "t.pred", p.position, min, max).unwrap();
    let two = exact_plan_differences(&diff, Leaves::Two, "t.pred", p.position, min, max).unwrap();
    let raw = exact_plan_raw(
        p.exact.as_ref().unwrap(),
        "t.pred",
        p.exact_position.unwrap(),
        min,
        max,
    )
    .unwrap();
    assert_range(code, "DIFFERENCES_RANGE", one.range);
    assert_range(code, "TRANSLATED_RANGE", two.range);
    assert_range(code, "UNTRANSLATED_RANGE", raw.range);
    // The values themselves, as derived for orient3d.
    assert_eq!(one.range.min, 2f64.powi(-188));
    assert_eq!(one.range.max, 2f64.powi(239));
    assert_eq!(two.range.max, 2f64.powi(238));
    assert_eq!(raw.range.max, 2f64.powi(239));
}

#[test]
fn the_constants_of_a_global_filter_are_the_derived_ones() {
    let g = generated(INSPHERE, "predicates/insphere.pred");
    let code = &g.stages;
    let p = parse(INSPHERE, "t.pred").unwrap().remove(0);
    let diff = difference_graph(&p.main, "t.pred", p.position).unwrap();
    let FilterForm::Global(bound) = filter_form(&diff, "t.pred", p.position).unwrap() else {
        panic!("in-sphere is not multihomogeneous");
    };
    let constants = bound.constants();
    assert_eq!(constant(code, "DEGREE", "u32"), "5");
    assert_literal(constant(code, "EPSILON", "f64"), constants.epsilon);
    assert_literal(constant(code, "KAPPA", "f64"), constants.kappa);
    assert_literal(constant(code, "D_MIN", "f64"), constants.d_min);
    assert_literal(constant(code, "D_MAX", "f64"), constants.d_max);
    assert!(bound.kappa_is(360));
    assert!(!code.contains("DEGREES"));
}

#[test]
fn the_header_says_do_not_edit() {
    let g = generated(ORIENT3D, "predicates/orient3d.pred");
    let mut lines = g.stages.lines();
    assert_eq!(
        lines.next(),
        Some("// Generated by implicit-predicates-gen from predicates/orient3d.pred. Do not edit.")
    );
    // The regeneration text given to `generate`, after "To regenerate,".
    assert_eq!(lines.next(), Some("// To regenerate, run the tests."));
}

#[test]
fn a_predicate_without_an_exact_form_has_no_untranslated_stage_and_chains_the_main_form() {
    let g = generated(INSPHERE, "predicates/insphere.pred");
    assert_eq!(g.name, "insphere");
    assert!(!g.stages.contains("expansions_untranslated"));
    assert!(!g.stages.contains("UNTRANSLATED_RANGE"));
    // No stage of in-sphere fits on the stack, so its chain takes an arena
    // and runs the translated form over spans after the filter.
    for stage in ["exact_differences", "expansions_translated("] {
        assert!(!g.stages.contains(stage), "{stage}");
    }
    assert!(!g.stages.contains("DIFFERENCES_RANGE"));
    assert!(!g.stages.contains("pub const TRANSLATED_RANGE"));
    assert!(g.stages.contains("pub fn expansions_translated_in("));
    let staged = &g.stages[g.stages.find("pub fn staged").unwrap()..];
    let signature = &staged[..staged.find('{').unwrap()];
    assert_eq!(
        signature,
        "pub fn staged(scratch: &mut Scratch<'_>, p: [&[f64; 3]; 5]) \
         -> Result<Option<Sign>, ScratchExhausted> "
    );
    let body = &staged[..staged.find("\n}\n\n").unwrap()];
    assert!(body.contains("filter(p, 0.0)"), "{body}");
    assert!(body.contains("staged_in_arena(scratch, p)"), "{body}");
    let arena = &g.stages[g.stages.find("fn staged_in_arena").unwrap()..];
    let arena = &arena[..arena.find("\n}\n\n").unwrap()];
    let order = [
        "let mark = scratch.mark();",
        "exact(scratch, p[0])?",
        "expansions_translated_in(scratch, q)?",
        "scratch.reset(mark);",
    ]
    .map(|text| {
        arena
            .rfind(text)
            .unwrap_or_else(|| panic!("{text}: {arena}"))
    });
    // The mark is taken before `run` is called, after it is defined.
    assert!(
        order[1] < order[0] && order[2] < order[0] && order[0] < order[3],
        "{arena}"
    );
}

#[test]
fn orient3d_keeps_its_three_stages_on_the_stack_and_its_chain() {
    let g = generated(ORIENT3D, "predicates/orient3d.pred");
    for stage in [
        "exact_differences",
        "expansions_translated",
        "expansions_untranslated",
        "expansions_translated_in",
        "expansions_untranslated_in",
    ] {
        assert!(g.stages.contains(&format!("pub fn {stage}(")), "{stage}");
    }
    assert!(
        g.stages
            .contains("pub fn staged(p: [&[f64; 3]; 4]) -> Option<Sign> {")
    );
    assert!(!g.stages.contains("staged_in_arena"));
}

#[test]
fn the_ranges_over_spans_in_the_output_are_the_derived_ones() {
    let g = generated(ORIENT3D, "predicates/orient3d.pred");
    let code = &g.stages;
    assert_eq!(constant(code, "MAX_COORD_COMPONENTS", "usize"), "16");
    assert_eq!(MAX_COORD_COMPONENTS, 16);
    let p = parse(ORIENT3D, "t.pred").unwrap().remove(0);
    let diff = difference_graph(&p.main, "t.pred", p.position).unwrap();
    let (min, max) = (PRODUCT_OPERAND_MIN, PRODUCT_OPERAND_MAX);
    let translated =
        exact_plan_spans_translated(&diff, 16, "t.pred", p.position, min, max).unwrap();
    let exact = p.exact.as_ref().unwrap();
    let untranslated =
        exact_plan_spans_untranslated(exact, 16, "t.pred", p.position, min, max).unwrap();
    assert_range(code, "SPANS_TRANSLATED_RANGE", translated.range);
    assert_range(code, "SPANS_UNTRANSLATED_RANGE", untranslated.range);
    // The values themselves, as derived for orient3d.
    assert_eq!(translated.range.min, 2f64.powi(-188));
    assert_eq!(translated.range.max, 2f64.powi(234));
    assert_eq!(untranslated.range.min, 2f64.powi(-188));
    assert_eq!(untranslated.range.max, 2f64.powi(235));
}

#[test]
fn the_ranges_over_spans_do_not_depend_on_the_point_types_of_the_file() {
    // The same predicate, alone and in a file that declares point types.
    let alone = generated(ORIENT3D, "predicates/orient3d.pred");
    let with_points = format!("{POINTS}\n{ORIENT3D}");
    let mut all = generate(&with_points, "predicates/orient3d.pred", REGENERATE).unwrap();
    assert_eq!(all.len(), 1);
    let with_points = all.remove(0);
    for name in [
        "MAX_COORD_COMPONENTS",
        "SPANS_TRANSLATED_RANGE",
        "SPANS_UNTRANSLATED_RANGE",
    ] {
        let ty = if name == "MAX_COORD_COMPONENTS" {
            "usize"
        } else {
            "(f64, f64)"
        };
        assert_eq!(
            constant(&alone.stages, name, ty),
            constant(&with_points.stages, name, ty),
            "{name}"
        );
    }
    assert_eq!(alone.stages, with_points.stages);
}

#[test]
fn the_chain_tries_the_translated_form_before_the_exact_form() {
    let o = generated(ORIENT3D, "predicates/orient3d.pred");
    let staged = &o.stages[o.stages.find("pub fn staged").unwrap()..];
    let body = &staged[..staged.find("\n}").unwrap()];
    let order = [
        "filter(p, [0.0; 3])",
        "exact_differences(p)",
        "expansions_translated(p)",
        "expansions_untranslated(p)",
    ]
    .map(|stage| {
        body.find(stage)
            .unwrap_or_else(|| panic!("{stage}: {body}"))
    });
    assert!(order.is_sorted(), "{body}");
}

#[test]
fn shared_subexpressions_are_emitted_once() {
    // `c.y * d.z` is shared by two cofactors of the exact form of orient3d.
    let g = generated(ORIENT3D, "predicates/orient3d.pred");
    assert_eq!(
        g.stages
            .matches("expansion::from_two_product(p[2][1], p[3][2],")
            .count(),
        1
    );
    // In-sphere: the minor `cy * dz - cz * dy` is shared by two cofactors,
    // and the leaf `c.y - e.y` by many products.
    let s = generated(INSPHERE, "predicates/insphere.pred");
    let filter = &s.stages[s.stages.find("pub fn filter").unwrap()..];
    let filter = &filter[..filter.find("\n}").unwrap()];
    assert_eq!(filter.matches("p[2][1] - p[4][1]").count(), 1, "{filter}");
}

#[test]
fn a_definition_error_is_reported_with_its_position() {
    let e = generate(
        "fn p(a: Point3, b: Point3) -> Sign {\n    a.x * b.y\n}",
        "predicates/p.pred",
        REGENERATE,
    )
    .unwrap_err();
    assert_eq!(e.file, "predicates/p.pred");
    assert_eq!((e.line, e.column), (1, 4), "{e}");
}

#[test]
fn the_header_names_the_file_and_tells_how_to_regenerate() {
    let all = generate(
        ORIENT3D,
        "defs/o.pred",
        "run, from the root:\nmake predicates",
    )
    .unwrap();
    let expected = "// Generated by implicit-predicates-gen from defs/o.pred. Do not edit.\n\
                    // To regenerate, run, from the root:\n\
                    // make predicates\n\n";
    for code in [&all[0].stages, &all[0].checks] {
        assert!(code.starts_with(expected), "{}", &code[..200]);
    }
}