sim-lib-interference-solve 0.1.0

Deterministic CPU f64 reference solving for coherent scalar wave fields.
Documentation
use std::f64::consts::PI;

use sim_lib_interference_core::{SamplingCertificate, SamplingThresholds, SamplingVerdict};

use super::{LossClass, ScalarProjection, ScalarSample, project};
use crate::{HostPhasorField, Observable, ReductionRule};

fn certificate() -> SamplingCertificate {
    SamplingCertificate {
        thresholds: SamplingThresholds::default(),
        wavelength_m: 1.0,
        samples_per_wavelength_u: 16.0,
        samples_per_wavelength_v: 16.0,
        samples_per_power_fringe_u: 8.0,
        samples_per_power_fringe_v: 8.0,
        nearest_point_source_distance_m: None,
        max_envelope_fraction_per_cell: 0.0,
        verdict: SamplingVerdict::Resolved,
    }
}

fn field(real: Vec<f64>, imaginary: Vec<f64>) -> HostPhasorField {
    HostPhasorField::from_test_components(1, real.len(), real, imaginary)
}

#[test]
fn every_observable_has_its_declared_scalar_meaning() {
    let field = field(vec![3.0, -1.0], vec![4.0, 0.0]);
    let expected = [
        (
            Observable::Real,
            vec![ScalarSample::Value(3.0), ScalarSample::Value(-1.0)],
        ),
        (
            Observable::Imaginary,
            vec![ScalarSample::Value(4.0), ScalarSample::Value(0.0)],
        ),
        (
            Observable::Amplitude,
            vec![ScalarSample::Value(5.0), ScalarSample::Value(1.0)],
        ),
        (
            Observable::Phase,
            vec![
                ScalarSample::Value(4.0_f64.atan2(3.0)),
                ScalarSample::Value(-PI),
            ],
        ),
        (
            Observable::MagnitudeSquared,
            vec![ScalarSample::Value(25.0), ScalarSample::Value(1.0)],
        ),
        (
            Observable::Instant { wt: PI / 2.0 },
            vec![
                ScalarSample::Value(3.0 * (PI / 2.0).cos() + 4.0),
                ScalarSample::Value(-(PI / 2.0).cos()),
            ],
        ),
    ];

    for (observable, expected_samples) in expected {
        let projection = project(&field, certificate(), observable, 0.0).unwrap();
        assert_eq!(projection.samples(), expected_samples);
        assert_eq!(projection.certificate().observable(), observable);
        assert_eq!(projection.certificate().rule(), ReductionRule::Detail);
        assert_eq!(projection.certificate().loss_class(), LossClass::Lossless);
    }
}

#[test]
fn phase_at_or_below_floor_is_structurally_masked() {
    let field = field(vec![0.0, 0.3, 0.0], vec![0.0, 0.4, 0.6]);
    let projection = project(&field, certificate(), Observable::Phase, 0.5).unwrap();

    assert_eq!(
        projection.samples(),
        [
            ScalarSample::Masked,
            ScalarSample::Masked,
            ScalarSample::Value(PI / 2.0),
        ]
    );
    assert_eq!(projection.certificate().mask_count(), 2);
    assert!(
        !projection
            .samples()
            .iter()
            .any(|sample| matches!(sample, ScalarSample::Value(value) if *value == 0.0))
    );
}

#[test]
fn instant_at_zero_is_bit_identical_to_real() {
    let field = field(vec![-0.0, -3.5, 7.25], vec![9.0, -2.0, 4.0]);
    let real = project(&field, certificate(), Observable::Real, 0.0).unwrap();
    let instant = project(&field, certificate(), Observable::Instant { wt: 0.0 }, 0.0).unwrap();

    let bits = |projection: &ScalarProjection| {
        projection
            .samples()
            .iter()
            .map(|sample| match sample {
                ScalarSample::Value(value) => value.to_bits(),
                ScalarSample::Masked => panic!("real-valued projection cannot be masked"),
            })
            .collect::<Vec<_>>()
    };
    assert_eq!(bits(&instant), bits(&real));
}