sim-lib-interference-core 0.1.0

Validated physical boundary types for coherent scalar wave-field studies.
Documentation
use sim_lib_interference_core::{
    Emitter, FieldAmplitude, Hertz, InterferenceProblem, MetresPerSecond, NepersPerMetre, Point3M,
    PositiveMetres, Radians, SamplingCertificate, SamplingPlane, SamplingPolicy,
    SamplingThresholds, SamplingVerdict, ScalarMedium, SourceSet, UnitVector3,
};

fn plane(extent_m: f64, cells_per_axis: usize) -> SamplingPlane {
    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,
    )
    .unwrap()
}

fn problem(frequency_hz: f64, sources: Vec<Emitter>) -> InterferenceProblem {
    InterferenceProblem::new(
        Hertz::new(frequency_hz).unwrap(),
        ScalarMedium::new(
            MetresPerSecond::new(343.0).unwrap(),
            NepersPerMetre::new(0.0).unwrap(),
        ),
        SourceSet::new(sources).unwrap(),
        PositiveMetres::new(0.001).unwrap(),
    )
}

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(),
    }
}

#[test]
fn certificate_records_carrier_and_doubled_power_bandwidth() {
    let certificate =
        SamplingCertificate::measure(&problem(40_000.0, vec![forward_plane()]), &plane(2.0, 200))
            .unwrap();

    assert_eq!(certificate.wavelength_m, 343.0 / 40_000.0);
    assert_eq!(
        certificate.samples_per_power_fringe_u,
        certificate.samples_per_wavelength_u / 2.0
    );
    assert_eq!(
        certificate.samples_per_power_fringe_v,
        certificate.samples_per_wavelength_v / 2.0
    );
    assert_eq!(certificate.nearest_point_source_distance_m, None);
    assert_eq!(certificate.max_envelope_fraction_per_cell, 0.0);
    assert_eq!(certificate.verdict, SamplingVerdict::Aliased);
}

#[test]
fn nearest_point_source_and_conservative_envelope_are_measured() {
    let certificate =
        SamplingCertificate::measure(&problem(1_000.0, vec![point(1.0)]), &plane(1.0, 100))
            .unwrap();

    assert_eq!(certificate.nearest_point_source_distance_m, Some(1.0));
    let diagonal = 0.01_f64.hypot(0.01);
    let expected = diagonal;
    assert_eq!(certificate.max_envelope_fraction_per_cell, expected);
    assert_eq!(certificate.verdict, SamplingVerdict::Resolved);
}

#[test]
fn coarse_near_source_sampling_has_a_finite_conservative_bound() {
    let certificate =
        SamplingCertificate::measure(&problem(1_000.0, vec![point(0.1)]), &plane(1.0, 1)).unwrap();

    assert_eq!(certificate.nearest_point_source_distance_m, Some(0.1));
    assert_eq!(
        certificate.max_envelope_fraction_per_cell,
        1.0_f64.hypot(1.0) / 0.1
    );
    assert_eq!(certificate.verdict, SamplingVerdict::Aliased);
}

#[test]
fn a_point_source_touching_the_plane_has_no_finite_envelope_certificate() {
    assert!(matches!(
        SamplingCertificate::measure(&problem(1_000.0, vec![point(0.0)]), &plane(1.0, 100)),
        Err(
            sim_lib_interference_core::InterferenceError::UnboundedSamplingEnvelope {
                nearest_point_source_distance_m: 0.0,
                ..
            }
        )
    ));
}

#[test]
fn explicit_threshold_records_control_all_three_verdicts() {
    let problem = problem(40_000.0, vec![forward_plane()]);
    let marginal = SamplingCertificate::measure(&problem, &plane(2.0, 1_000)).unwrap();
    let resolved = SamplingCertificate::measure(&problem, &plane(2.0, 2_000)).unwrap();

    assert_eq!(
        SamplingThresholds::default().resolved_min_samples_per_wavelength,
        8.0
    );
    assert_eq!(
        SamplingThresholds::default().marginal_min_samples_per_wavelength,
        4.0
    );
    assert_eq!(marginal.verdict, SamplingVerdict::Marginal);
    assert_eq!(resolved.verdict, SamplingVerdict::Resolved);

    let relaxed = SamplingThresholds::new(4.0, 2.0, 0.10, 0.20).unwrap();
    let reclassified =
        SamplingCertificate::measure_with_thresholds(&problem, &plane(2.0, 1_000), relaxed)
            .unwrap();
    assert_eq!(reclassified.thresholds, relaxed);
    assert_eq!(reclassified.verdict, SamplingVerdict::Resolved);
}

#[test]
fn thresholds_are_validated_and_strict_is_fail_closed() {
    assert!(SamplingThresholds::new(4.0, 8.0, 0.05, 0.10).is_err());
    assert!(SamplingThresholds::new(8.0, 4.0, 0.20, 0.10).is_err());
    assert!(SamplingThresholds::new(f64::NAN, 4.0, 0.05, 0.10).is_err());
    let invalid_record = SamplingThresholds {
        resolved_min_samples_per_wavelength: f64::NAN,
        ..SamplingThresholds::default()
    };
    assert!(
        SamplingCertificate::measure_with_thresholds(
            &problem(1_000.0, vec![forward_plane()]),
            &plane(1.0, 100),
            invalid_record,
        )
        .is_err()
    );

    let aliased =
        SamplingCertificate::measure(&problem(40_000.0, vec![forward_plane()]), &plane(2.0, 200))
            .unwrap();
    assert_eq!(SamplingPolicy::default(), SamplingPolicy::Strict);
    assert_eq!(
        SamplingPolicy::Strict.admit(&aliased),
        Err(
            sim_lib_interference_core::InterferenceError::SamplingRefused {
                certificate: aliased,
            }
        )
    );
    assert_eq!(SamplingPolicy::Annotate.admit(&aliased), Ok(()));
}