physics_in_parallel 4.1.0-alpha

High-performance infrastructure for numerical simulations in physics
Documentation
use physics_in_parallel::prelude::basic::{
    Backend, PairGenerator, PairingMethod, ResolvedRng, RngMethod, SourceMode, SquareLattice,
    SquareLatticeGeometry, SquareLatticeInitMethod, VectorList, VectorSamplingMethod,
    sample_vectors,
};

fn indexed_rng(seed: u64) -> ResolvedRng {
    ResolvedRng::new(seed, RngMethod::IndexedSplitMix64)
}

#[test]
fn lattice_serde_preserves_geometry_values_and_rng_provenance() {
    let geometry = SquareLatticeGeometry::new(
        &[2, 3],
        physics_in_parallel::prelude::basic::BoundaryCondition::Periodic,
        Some(&[0.5, 2.0]),
    )
    .unwrap();
    let lattice = SquareLattice::new(
        geometry,
        SquareLatticeInitMethod::ShuffledValues {
            values: (0_i32..6).collect(),
            rng: indexed_rng(17),
        },
    )
    .unwrap();

    let json = serde_json::to_string(&lattice).unwrap();
    let restored: SquareLattice<i32> = serde_json::from_str(&json).unwrap();
    assert_eq!(restored.geometry(), lattice.geometry());
    assert_eq!(restored.data(), lattice.data());
    assert_eq!(
        restored.initialization_resolved_rng(),
        Some(indexed_rng(17))
    );
}

#[test]
fn pair_generation_uses_resolved_rng_and_universal_outputs() {
    let method = PairingMethod::IndependentUniform;
    let mut pairs = PairGenerator::new(&[4, 5], method, 8, indexed_rng(11)).unwrap();
    pairs.refresh_at(9);

    assert_eq!(pairs.generated_sweep(), Some(9));
    assert_eq!(pairs.resolved_rng(), indexed_rng(11));
    assert_eq!(pairs.sources().shape(), [8, 2]);
    assert_eq!(pairs.sources().backend(), Backend::Dense);
    assert_eq!(pairs.method(), method);

    let kernel_method = PairingMethod::Kernel {
        kernel: physics_in_parallel::prelude::basic::KernelType::NearestNeighbor { dimension: 2 },
        sources: SourceMode::Origin,
    };
    assert!(PairGenerator::new(&[4, 5], kernel_method, 8, indexed_rng(12)).is_ok());
}

#[test]
fn sampling_preserves_the_callers_backend() {
    let mut values = VectorList::<f64>::zeros(2, 4, Backend::Sparse).unwrap();
    assert_eq!(values.backend(), Backend::Sparse);
    sample_vectors(
        &mut values,
        VectorSamplingMethod::Uniform {
            low: 1.0,
            high: 2.0,
        },
        indexed_rng(23),
    )
    .unwrap();

    assert_eq!(values.backend(), Backend::Sparse);
    assert!(values.values().all(|value| (1.0..2.0).contains(&value)));
}

#[test]
fn boundary_geometry_and_reflection_parity() {
    use physics_in_parallel::space::{ContinuousBoundary, PeriodicBox, ReflectBox};
    assert!(PeriodicBox::new(&[], &[]).is_err());
    assert!(PeriodicBox::new(&[-f64::MAX], &[f64::MAX]).is_err());
    assert!(ReflectBox::new(&[0.0], &[f64::MAX]).is_err());
    let clamp = physics_in_parallel::space::ClampBox::new(&[-f64::MAX], &[f64::MAX]).unwrap();
    let mut position = [0.0];
    clamp.apply_position(&mut position).unwrap();
    assert_eq!(position, [0.0]);
    let boundary = ReflectBox::new(&[0.0], &[1.0]).unwrap();
    for (input, expected, sign) in [
        (-3.0, 1.0, -1.0),
        (-2.0, 0.0, 1.0),
        (-1.0, 1.0, -1.0),
        (-0.25, 0.25, -1.0),
        (0.0, 0.0, 1.0),
        (1.0, 1.0, 1.0),
        (1.25, 0.75, -1.0),
        (2.0, 0.0, -1.0),
        (3.0, 1.0, 1.0),
        (4.0, 0.0, -1.0),
    ] {
        let mut r = [input];
        let mut v = [2.0];
        boundary.apply_position_velocity(&mut r, &mut v).unwrap();
        assert_eq!(r, [expected]);
        assert_eq!(v, [2.0 * sign]);
        let mut position_only = [input];
        boundary.apply_position(&mut position_only).unwrap();
        assert_eq!(position_only, r);
    }
    let periodic = PeriodicBox::new(&[-1e308], &[-0.5e308]).unwrap();
    let mut distant = [f64::MAX];
    periodic.apply_position(&mut distant).unwrap();
    assert!(distant[0].is_finite() && (-1e308..-0.5e308).contains(&distant[0]));
}

#[test]
fn contiguous_laplacian_matches_neighbor_reference() {
    use physics_in_parallel::prelude::basic::BoundaryCondition;
    for shape in [
        &[1][..],
        &[2],
        &[7],
        &[1, 3],
        &[4, 5],
        &[3, 2, 4],
        &[2, 1, 3, 2],
    ] {
        for boundary in [
            BoundaryCondition::Periodic,
            BoundaryCondition::Reflective,
            BoundaryCondition::Neumann,
        ] {
            let spacing: Vec<_> = (0..shape.len()).map(|axis| 0.5 + axis as f64).collect();
            let geometry = SquareLatticeGeometry::new(shape, boundary, Some(&spacing)).unwrap();
            for components in [1, 2, 3, 8, 17] {
                let input: Vec<_> = (0..geometry.num_sites() * components)
                    .map(|i| (i as f64 * 0.13).sin())
                    .collect();
                let mut output = vec![99.0; input.len()];
                geometry.laplacian(&input, components, &mut output).unwrap();
                for site in 0..geometry.num_sites() {
                    for component in 0..components {
                        let mut expected = 0.0;
                        for (axis, spacing) in spacing.iter().enumerate() {
                            let plus = geometry.neighbor(site as isize, axis, 1).unwrap();
                            let minus = geometry.neighbor(site as isize, axis, -1).unwrap();
                            expected += (input[plus * components + component]
                                + input[minus * components + component]
                                - 2.0 * input[site * components + component])
                                * (1.0 / (spacing * spacing));
                        }
                        assert_eq!(
                            output[site * components + component].to_bits(),
                            expected.to_bits()
                        );
                    }
                }
            }
        }
    }
}