physics_in_parallel 3.0.0

High-performance infrastructure for numerical simulations in physics
Documentation
use physics_in_parallel::engines::soa::{AttrId, AttrsCore, AttrsError, AttrsMeta, PhysObj};
use physics_in_parallel::math::tensor::rank_2::vector_list::VectorList;
use std::env;
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};

fn unique_tmp_dir(name: &str) -> std::path::PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system time should be after unix epoch")
        .as_nanos();
    env::temp_dir().join(format!("pip_{name}_{}_{}", std::process::id(), nanos))
}

#[test]
fn attrs_core_allocate_access_and_errors() {
    let mut core = AttrsCore::empty();
    core.allocate::<f64>("r", 2, 3).unwrap();

    assert_eq!(core.len(), 1);
    assert_eq!(core.n_objects(), Some(3));
    assert_eq!(core.dim_of("r").unwrap(), 2);

    core.set_vector_of::<f64>("r", 1, &[1.0, 2.0]).unwrap();
    assert_eq!(
        core.vector_of::<f64>("r", 1).unwrap(),
        [1.0, 2.0].as_slice()
    );

    let err = core.set_vector_of::<f64>("r", 1, &[1.0]).unwrap_err();
    assert_eq!(
        err,
        AttrsError::WrongVectorLen {
            expected: 2,
            got: 1
        }
    );

    let err = core.vector_of::<f64>("r", 99).unwrap_err();
    assert!(matches!(err, AttrsError::ObjOutOfBounds { .. }));

    let mut charge = VectorList::<f64>::empty(1, 3);
    charge.fill(-1.0);
    core.insert("q", charge).unwrap();
    core.rename("q", "charge").unwrap();
    assert!(core.contains("charge"));
    core.remove("charge").unwrap();
    assert!(!core.contains("charge"));
}

#[test]
fn attrs_core_type_mismatch_and_phys_obj_serialize() {
    let mut core = AttrsCore::empty();
    core.allocate::<f64>("r", 2, 2).unwrap();

    let err = core.get::<i64>("r").unwrap_err();
    assert!(matches!(err, AttrsError::WrongType { .. }));

    let obj = PhysObj {
        meta: AttrsMeta {
            id: 7 as AttrId,
            label: "unit_test".to_string(),
            comment: "serialize smoke".to_string(),
        },
        core,
    };

    let json = obj.serialize().unwrap();
    let value: serde_json::Value = serde_json::from_str(&json).unwrap();

    assert_eq!(value["meta"]["id"], 7);
    assert_eq!(value["meta"]["label"], "unit_test");
    assert_eq!(value["core"]["num_attrs"], 1);
}

#[test]
fn phys_obj_constructors_metadata_and_save_to_json_are_consistent() {
    let empty = PhysObj::empty();
    assert_eq!(empty.meta, AttrsMeta::empty());
    assert!(empty.core.is_empty());
    assert_eq!(empty.core.n_objects(), None);

    let meta = AttrsMeta::new(11, "particles", "test state");
    assert_eq!(meta.id, 11);
    assert_eq!(meta.label, "particles");
    assert_eq!(meta.comment, "test state");

    let mut core = AttrsCore::empty();
    core.allocate::<f64>("r", 3, 2).unwrap();
    core.allocate::<i64>("species", 1, 2).unwrap();
    core.set_vector_of::<f64>("r", 0, &[1.0, 2.0, 3.0]).unwrap();
    core.set_vector_of::<i64>("species", 1, &[4]).unwrap();

    let labels: Vec<&str> = core.labels().collect();
    assert_eq!(labels.len(), 2);
    assert!(labels.contains(&"r"));
    assert!(labels.contains(&"species"));
    assert_eq!(
        core.type_name_of("r").unwrap(),
        std::any::type_name::<f64>()
    );
    assert_eq!(
        core.type_name_of("species").unwrap(),
        std::any::type_name::<i64>()
    );

    let obj = PhysObj::new(meta, core);
    let out_dir = unique_tmp_dir("phys_obj_save");
    obj.save_to_json(&out_dir, "state.json")
        .expect("PhysObj::save_to_json should create output");

    let raw =
        fs::read_to_string(out_dir.join("state.json")).expect("saved json should be readable");
    let value: serde_json::Value = serde_json::from_str(&raw).expect("saved json should parse");
    assert_eq!(value["meta"]["id"], 11);
    assert_eq!(value["meta"]["label"], "particles");
    assert_eq!(value["core"]["num_attrs"], 2);
    assert_eq!(value["core"]["n_objects"], 2);

    fs::remove_dir_all(out_dir).expect("cleanup saved PhysObj json directory");
}

#[test]
fn attrs_core_rejects_inconsistent_object_count() {
    let mut core = AttrsCore::empty();
    core.allocate::<f64>("r", 3, 2).unwrap();

    let mismatched = VectorList::<f64>::empty(3, 3);
    let err = core.insert("v", mismatched).unwrap_err();
    assert_eq!(
        err,
        AttrsError::InconsistentObjectCount {
            label: "v".to_string(),
            expected: 2,
            got: 3,
        }
    );
}

#[test]
fn attrs_core_auto_generated_ids_are_stable_and_optional_for_users() {
    let mut core = AttrsCore::empty();
    core.allocate::<f64>("r", 3, 2).unwrap();
    core.allocate::<f64>("v", 3, 2).unwrap();
    core.allocate::<i64>("species", 1, 2).unwrap();

    let r_id = core.id_of("r").unwrap();
    let v_id = core.id_of("v").unwrap();
    let species_id = core.id_of("species").unwrap();
    assert_ne!(r_id, v_id);
    assert_ne!(v_id, species_id);

    assert_eq!(core.label_of(r_id).unwrap(), "r");
    assert_eq!(core.dim_of_id(v_id).unwrap(), 3);
    assert_eq!(
        core.type_name_of_id(species_id).unwrap(),
        std::any::type_name::<i64>()
    );

    core.get_by_id_mut::<f64>(r_id)
        .unwrap()
        .set_vec(1, &[4.0, 5.0, 6.0]);
    assert_eq!(
        core.get_by_id::<f64>(r_id).unwrap().get_vec(1),
        [4.0, 5.0, 6.0].as_slice()
    );
    assert_eq!(
        core.vector_of::<f64>("r", 1).unwrap(),
        [4.0, 5.0, 6.0].as_slice()
    );
}

#[test]
fn attrs_core_rename_preserves_id_and_remove_invalidates_only_removed_id() {
    let mut core = AttrsCore::empty();
    core.allocate::<f64>("r", 2, 2).unwrap();
    core.allocate::<f64>("v", 2, 2).unwrap();

    let r_id = core.id_of("r").unwrap();
    let v_id = core.id_of("v").unwrap();

    core.rename("r", "position").unwrap();
    assert_eq!(core.id_of("position").unwrap(), r_id);
    assert!(matches!(
        core.id_of("r").unwrap_err(),
        AttrsError::UnknownLabel { .. }
    ));
    assert_eq!(core.label_of(r_id).unwrap(), "position");

    core.remove("position").unwrap();
    assert!(matches!(
        core.label_of(r_id).unwrap_err(),
        AttrsError::UnknownId { .. }
    ));
    assert_eq!(core.label_of(v_id).unwrap(), "v");

    core.allocate::<f64>("a", 2, 2).unwrap();
    let a_id = core.id_of("a").unwrap();
    assert_ne!(a_id, r_id, "removed attribute id should not be reused");
}