sim-lib-interference-core 0.1.0

Validated physical boundary types for coherent scalar wave-field studies.
Documentation
//! End-to-end conformance for sampling and work preflight.

use sim_lib_interference_core::{
    Emitter, FieldAmplitude, Hertz, InterferenceError, InterferenceProblem, MetresPerSecond,
    NepersPerMetre, Point3M, PositiveMetres, Radians, RequestPreflight, SamplingPlane,
    SamplingPolicy, SamplingThresholds, SamplingVerdict, ScalarMedium, SourceSet, UnitVector3,
    WorkBudget, WorkEstimate, WorkMetric,
};

fn forward_plane() -> Emitter {
    Emitter::ForwardPlane {
        id: "plane".to_owned(),
        through: Point3M::from_metres(0.0, 0.0, 0.0).unwrap(),
        direction: UnitVector3::new(0.0, 0.0, 1.0).unwrap(),
        amplitude: FieldAmplitude::new(1.0).unwrap(),
        phase: Radians::new(0.0).unwrap(),
    }
}

fn point(z_m: f64) -> Emitter {
    Emitter::Point {
        id: "point".to_owned(),
        position: Point3M::from_metres(0.5, 0.5, z_m).unwrap(),
        amplitude_at_reference: FieldAmplitude::new(1.0).unwrap(),
        phase: Radians::new(0.0).unwrap(),
    }
}

fn air_problem(frequency_hz: f64, emitter: Emitter) -> InterferenceProblem {
    InterferenceProblem::new(
        Hertz::new(frequency_hz).unwrap(),
        ScalarMedium::new(
            MetresPerSecond::new(343.0).unwrap(),
            NepersPerMetre::new(0.0).unwrap(),
        ),
        SourceSet::new(vec![emitter]).unwrap(),
        PositiveMetres::new(0.001).unwrap(),
    )
}

fn plane(extent_m: f64, cells_per_axis: usize) -> Result<SamplingPlane, InterferenceError> {
    SamplingPlane::new(
        Point3M::from_metres(0.0, 0.0, 0.0).unwrap(),
        UnitVector3::new(1.0, 0.0, 0.0).unwrap(),
        UnitVector3::new(0.0, 1.0, 0.0).unwrap(),
        PositiveMetres::new(extent_m).unwrap(),
        PositiveMetres::new(extent_m).unwrap(),
        cells_per_axis,
        cells_per_axis,
    )
}

fn strict_preflight(
    problem: &InterferenceProblem,
    plane: &SamplingPlane,
) -> Result<RequestPreflight, InterferenceError> {
    RequestPreflight::admit(
        problem,
        plane,
        SamplingPolicy::Strict,
        SamplingThresholds::default(),
        WorkBudget::default(),
    )
}

#[test]
fn forty_kilohertz_air_on_two_metres_at_two_hundred_cells_refuses() {
    let error = strict_preflight(
        &air_problem(40_000.0, forward_plane()),
        &plane(2.0, 200).unwrap(),
    )
    .unwrap_err();
    let InterferenceError::SamplingRefused { certificate } = error else {
        panic!("expected strict sampling refusal");
    };

    assert_eq!(certificate.verdict, SamplingVerdict::Aliased);
    let expected_carrier_samples = (343.0 / 40_000.0) / (2.0 / 200.0);
    assert_eq!(
        certificate.samples_per_wavelength_u,
        expected_carrier_samples
    );
    assert_eq!(
        certificate.samples_per_power_fringe_u,
        expected_carrier_samples / 2.0
    );
}

#[test]
fn forty_kilohertz_air_on_a_resolved_grid_passes() {
    let preflight = strict_preflight(
        &air_problem(40_000.0, forward_plane()),
        &plane(2.0, 2_048).unwrap(),
    )
    .unwrap();

    assert_eq!(
        preflight.sampling_certificate.verdict,
        SamplingVerdict::Resolved
    );
    assert!(preflight.sampling_certificate.samples_per_wavelength_u > 8.0);
    assert_eq!(preflight.work_estimate.cells, 2_048 * 2_048);
}

#[test]
fn near_source_envelope_under_resolution_refuses_despite_carrier_resolution() {
    let error = strict_preflight(
        &air_problem(1_000.0, point(0.01)),
        &plane(1.0, 256).unwrap(),
    )
    .unwrap_err();
    let InterferenceError::SamplingRefused { certificate } = error else {
        panic!("expected envelope sampling refusal");
    };

    assert!(certificate.samples_per_wavelength_u > 8.0);
    assert!(certificate.max_envelope_fraction_per_cell > 0.10);
    assert_eq!(certificate.verdict, SamplingVerdict::Aliased);
}

#[test]
fn arithmetic_overflow_is_refused_by_allocation_free_constructors() {
    assert_eq!(
        SamplingPlane::new(
            Point3M::from_metres(0.0, 0.0, 0.0).unwrap(),
            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(),
            usize::MAX,
            2,
        ),
        Err(InterferenceError::SamplingCellCountOverflow {
            rows: usize::MAX,
            columns: 2,
        })
    );
    assert_eq!(
        WorkEstimate::new(u64::MAX, 2),
        Err(InterferenceError::WorkEstimateOverflow {
            metric: WorkMetric::EmitterEvaluations,
        })
    );
}