cellular_raza 0.5.0

Cellular Agent-based Modeling from a Clean Slate
Documentation
use cellular_raza::building_blocks::{CartesianCuboid, NewtonDamped2D};
use cellular_raza::concepts::*;
use cellular_raza_building_blocks::CartesianSubDomain;
use cellular_raza_core::backend::chili::{Settings, SimulationError};
use cellular_raza_core::storage::{StorageBuilder, StorageInterfaceLoad, StorageOption};
use cellular_raza_core::time::FixedStepsize;

use nalgebra::Vector2;
use serde::{Deserialize, Serialize};

#[derive(Domain)]
struct MyDomain {
    #[DomainRngSeed]
    #[SortCells]
    cuboid: CartesianCuboid<f64, 2>,
    spring_strength: f64,
}

impl DomainCreateSubDomains<MySubDomain> for MyDomain {
    type VoxelIndex = [usize; 2];
    type SubDomainIndex = usize;

    fn create_subdomains(
        &self,
        n_subdomains: core::num::NonZeroUsize,
    ) -> Result<
        impl IntoIterator<Item = (Self::SubDomainIndex, MySubDomain, Vec<Self::VoxelIndex>)>,
        DecomposeError,
    > {
        Ok(self
            .cuboid
            .create_subdomains(n_subdomains)?
            .into_iter()
            .map(|(ind, subdomain, voxels)| {
                (
                    ind,
                    MySubDomain {
                        subdomain,
                        spring_strength: self.spring_strength,
                    },
                    voxels,
                )
            }))
    }
}

#[derive(SubDomain, Clone, Debug, Serialize)]
struct MySubDomain {
    #[Base]
    #[SortCells]
    #[Mechanics]
    subdomain: CartesianSubDomain<f64, 2>,
    spring_strength: f64,
}

impl SubDomainForce<Vector2<f64>, Vector2<f64>, Vector2<f64>, ()> for MySubDomain {
    fn calculate_custom_force(
        &self,
        pos: &Vector2<f64>,
        _vel: &Vector2<f64>,
        _: &(),
    ) -> Result<Vector2<f64>, cellular_raza_concepts::CalcError> {
        let force_x = -self.spring_strength * (pos.x - 0.0);
        let force_y = 0.0;
        Ok([force_x, force_y].into())
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, CellAgent)]
struct MyAgent(#[Mechanics] NewtonDamped2D);

impl InteractionInformation<()> for MyAgent {
    fn get_interaction_information(&self) {}
}

#[test]
fn spring_single_particle() -> Result<(), SimulationError> {
    let spring_strength = 0.0032;
    let mass = 0.5;
    let dt = 0.001;
    let domain = MyDomain {
        cuboid: CartesianCuboid::from_boundaries_and_n_voxels([-77.0; 2], [77.0; 2], [3; 2])?,
        spring_strength,
    };
    let time = FixedStepsize::from_partial_save_interval(0.0, dt, 10.0, 0.1)?;
    let storage = StorageBuilder::new().priority([StorageOption::Memory]);
    let settings = Settings {
        time,
        storage,
        n_threads: 1.try_into().unwrap(),
        progressbar: None,
    };
    let x0 = 10.0;
    let agents = [MyAgent(NewtonDamped2D {
        pos: [x0, 0.0].into(),
        vel: [0.0, 0.0].into(),
        damping_constant: 0.0,
        mass,
    })];
    let storager = cellular_raza::core::backend::chili::run_simulation!(
        agents: agents,
        domain: domain,
        settings: settings,
        aspects: [Mechanics, DomainForce],
    )?;
    let hists = storager.cells.load_all_element_histories()?;
    let (_, history) = hists.into_iter().next().unwrap();
    let positions = history
        .into_iter()
        .map(|(iter, (cbox, _))| (iter, cbox.cell.pos()));
    let omega = (spring_strength / mass).sqrt();
    for (iter, pos) in positions {
        let exact = x0 * (omega * iter as f64 * dt).cos();
        assert!((pos.x - exact).abs() < 1e-3);
    }
    Ok(())
}