ecological-model-core 0.7.0

Shared ecological initial states, interactions, trajectory observation, and terminal products
Documentation
use ecological_model_core::initial_state::{
    DistributionSource, INITIAL_STATE_FORMAT, InitialStateRecipe, InitializationMethod,
    load_verified_initial_state, persist_initial_state,
};
use physics_in_parallel::prelude::basic::{RngConfig, SquareLatticeConfig};
use scientific_workflow::prelude::basics::{ArtifactDisposition, ExecutionScope};

#[test]
fn recipe_is_reproducible_and_counts_are_exact() {
    let lattice = SquareLatticeConfig::periodic(&[9, 9]);
    let recipe = InitialStateRecipe::CenteredSeed {
        distribution: DistributionSource::Inline {
            weights: vec![0.7, 0.3],
        },
        seed_taxon: 1,
        seed_radius: 1,
        rng: RngConfig::new(Some(42), None),
    };
    let first = recipe.clone().create(lattice.clone(), 2).unwrap();
    let second = recipe.create(lattice, 2).unwrap();
    assert_eq!(first.space().data(), second.space().data());
    assert_eq!(first.counts().iter().sum::<usize>(), 81);
    assert_eq!(first.method(), InitializationMethod::CenteredSeed);
    assert_eq!(first.seed_taxon(), Some(1));
}

#[test]
fn balanced_uniform_has_the_minimum_possible_count_spread() {
    let recipe = InitialStateRecipe::BalancedUniform {
        rng: RngConfig::new(Some(1201), None),
    };
    let first = recipe
        .clone()
        .create(SquareLatticeConfig::periodic(&[5, 7]), 8)
        .unwrap();
    let second = recipe
        .create(SquareLatticeConfig::periodic(&[5, 7]), 8)
        .unwrap();
    assert_eq!(first.method(), InitializationMethod::BalancedUniform);
    assert_eq!(first.space().data(), second.space().data());
    assert_eq!(first.counts().iter().sum::<usize>(), 35);
    assert_eq!(first.counts().iter().max().unwrap(), &5);
    assert_eq!(first.counts().iter().min().unwrap(), &4);
    assert_eq!(first.frequencies().iter().sum::<f64>(), 1.0);
}

#[test]
fn dominant_recipe_uses_first_maximal_taxon() {
    let state = InitialStateRecipe::CenteredDominantSeed {
        distribution: DistributionSource::Inline {
            weights: vec![0.4, 0.4, 0.2],
        },
        seed_radius: 1,
        rng: RngConfig::new(Some(7), None),
    }
    .create(SquareLatticeConfig::periodic(&[9]), 3)
    .unwrap();
    assert_eq!(state.seed_taxon(), Some(0));
    assert_eq!(state.counts()[0], 3);
}

#[test]
fn verified_artifact_round_trip_is_exact() {
    let directory = tempfile::tempdir().unwrap();
    let scope = ExecutionScope::create_named(directory.path(), "execution").unwrap();
    let state = InitialStateRecipe::Random {
        distribution: DistributionSource::Uniform,
        rng: RngConfig::new(Some(903), None),
    }
    .create(SquareLatticeConfig::periodic(&[8]), 2)
    .unwrap();
    let persisted = persist_initial_state(&scope, &state).unwrap();
    assert_eq!(persisted.disposition(), ArtifactDisposition::Created);
    assert_eq!(persisted.descriptor().format(), INITIAL_STATE_FORMAT);
    let loaded = load_verified_initial_state(scope.directory(), persisted.descriptor()).unwrap();
    assert_eq!(loaded.space().data(), state.space().data());
    assert_eq!(loaded.counts(), state.counts());
}