sim-lib-interference-solve 0.1.0

Deterministic CPU f64 reference solving for coherent scalar wave fields.
Documentation
use sim_lib_interference_core::{
    Emitter, FieldAmplitude, Hertz, InterferenceProblem, MetresPerSecond, NepersPerMetre, Point3M,
    PositiveMetres, Radians, SamplingPlane, ScalarMedium, SourceSet, UnitVector3,
};
use sim_lib_interference_solve::{HostPhasorField, ReferencePhasorSolver};

fn point(x: f64, y: f64, z: f64) -> Point3M {
    Point3M::from_metres(x, y, z).unwrap()
}

fn sources_in_order(order: [&str; 3]) -> SourceSet {
    let sources = order
        .into_iter()
        .map(|id| match id {
            "point" => Emitter::Point {
                id: id.to_owned(),
                position: point(-0.25, 0.75, -20.0),
                amplitude_at_reference: FieldAmplitude::new(7.0).unwrap(),
                phase: Radians::new(0.375).unwrap(),
            },
            "plane-a" => Emitter::ForwardPlane {
                id: id.to_owned(),
                through: point(0.0, 0.0, -2.0),
                direction: UnitVector3::new(0.0, 0.0, 1.0).unwrap(),
                amplitude: FieldAmplitude::new(1.25).unwrap(),
                phase: Radians::new(-0.625).unwrap(),
            },
            "plane-b" => Emitter::ForwardPlane {
                id: id.to_owned(),
                through: point(-1.0, 0.0, 0.0),
                direction: UnitVector3::new(1.0, 0.0, 0.0).unwrap(),
                amplitude: FieldAmplitude::new(0.75).unwrap(),
                phase: Radians::new(1.125).unwrap(),
            },
            unexpected => panic!("unexpected fixture source {unexpected}"),
        })
        .collect();
    SourceSet::new(sources).unwrap()
}

fn problem(order: [&str; 3]) -> InterferenceProblem {
    InterferenceProblem::new(
        Hertz::new(8.0).unwrap(),
        ScalarMedium::new(
            MetresPerSecond::new(343.0).unwrap(),
            NepersPerMetre::new(0.015).unwrap(),
        ),
        sources_in_order(order),
        PositiveMetres::new(0.01).unwrap(),
    )
}

fn plane() -> SamplingPlane {
    SamplingPlane::new(
        point(0.0, 0.0, 0.0),
        UnitVector3::new(1.0, 0.0, 0.0).unwrap(),
        UnitVector3::new(0.0, 1.0, 0.0).unwrap(),
        PositiveMetres::new(1.0).unwrap(),
        PositiveMetres::new(1.0).unwrap(),
        4,
        5,
    )
    .unwrap()
}

fn component_bits(field: &HostPhasorField) -> (Vec<u64>, Vec<u64>) {
    (
        field.real().iter().map(|value| value.to_bits()).collect(),
        field
            .imaginary()
            .iter()
            .map(|value| value.to_bits())
            .collect(),
    )
}

#[test]
fn repeated_reference_solves_are_bit_identical() {
    let problem = problem(["point", "plane-a", "plane-b"]);
    let plane = plane();

    let (first_field, first_evidence) = ReferencePhasorSolver::default()
        .solve(&problem, &plane)
        .unwrap();
    let (second_field, second_evidence) = ReferencePhasorSolver::default()
        .solve(&problem, &plane)
        .unwrap();

    assert_eq!(component_bits(&first_field), component_bits(&second_field));
    assert_eq!(first_evidence, second_evidence);
    assert_eq!(first_evidence.completed_cells(), 20);
    assert_eq!(first_evidence.completed_emitter_evaluations(), 60);
}

#[test]
fn source_input_permutations_canonicalize_to_identical_fields() {
    let plane = plane();
    let permutations = [
        ["point", "plane-a", "plane-b"],
        ["plane-b", "point", "plane-a"],
        ["plane-a", "plane-b", "point"],
    ];
    let baseline = ReferencePhasorSolver::default()
        .solve(&problem(permutations[0]), &plane)
        .unwrap();
    let baseline_bits = component_bits(&baseline.0);

    for order in permutations.into_iter().skip(1) {
        let solved = ReferencePhasorSolver::default()
            .solve(&problem(order), &plane)
            .unwrap();
        assert_eq!(component_bits(&solved.0), baseline_bits);
        assert_eq!(solved.1, baseline.1);
    }
}