Skip to main content

Crate henad

Crate henad 

Source
Expand description

§henad

Henad is a parallel agent-based modelling engine, built to run millions of agents at interactive speeds on one machine.

This is the crate that a program depends on. It re-exports the engine, the sweeps and the model authoring API under one module tree, with the example models, the app and the command line behind features.

FeatureAdds
example-modelsThe ten example models, at henad::models
appThe app as a library, at henad::app
cliThe command line as a library, at henad::cli, on native targets
testingThe checks a model’s tests run against its entry, at henad::testing

No feature is on by default, and no feature changes a result.

§A complete program

The program below builds an example model, runs it, reads its statistics, edits a parameter live, fires an action, runs a small sweep, reads the sweep back, rebuilds one run, and opens the app on it.

// Cargo.toml: henad = { version = "0.3", features = ["example-models", "app"] }
// Copy the template's profile block, `[profile.dev] opt-level = 1`, `[profile.dev.package."*"] opt-level = 2` and
// `[profile.release] opt-level = 2`. Without it a debug build runs the example kernels at opt-level 0, and a release
// build at 3 where Henad measures at 2. A crate that registers its own models also calls `henad_build::stamp_commit()`
// from build.rs, with henad-build under [build-dependencies]. This example registers no models.

use std::io::Write;
use std::ops::ControlFlow;
use std::path::Path;

use henad::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    henad::install_panic_hook();
    let models = henad::models::example_models();
    // A sweep rejects a folder that already holds results. Each run of the program starts from an empty folder.
    let folder = std::env::temp_dir().join("sir-rates");
    if folder.exists() {
        std::fs::remove_dir_all(&folder)?;
    }
    let replay = study(&models, &folder, &mut std::io::stdout())?;

    // Then open the app on the run the study rebuilt.
    let options = AppOptions::new(models, "SIR study", henad::build_info!()).opening(AppOpening::Run {
        replay,
        open_at: OpenAt::Start,
    });
    henad::app::run_native(options)?;
    Ok(())
}

/// Runs SIR from `models`, sweeps it into `folder` and rebuilds one run of the sweep, writes what it finds to `out`,
/// and returns the rebuilt run's replay.
fn study(models: &ModelSet, folder: &Path, out: &mut impl Write) -> Result<Replay, Box<dyn std::error::Error>> {
    let sir = models.get("sir").ok_or("the example set lacks SIR")?;

    // SIR on a 256 by 256 grid with seed 7. One cell in a thousand starts infected, an outbreak at tick 50 adds more,
    // and an infection rate of 0.05 spreads the epidemic slowly up to tick 100.
    let mut simulation = sir
        .setup()
        .set("grid_width", 256u32)?
        .set("grid_height", 256u32)?
        .set("initial_infected_pct", 0.001f32)?
        .set_text("infection_rate", "0.05")?
        .with_seed(7)
        .act_at("seed_outbreak", 50)?
        .build(None)?;
    simulation.run_to(100)?;
    let stats = simulation.stats()?;
    let susceptible = stats.scalar("Susceptible");
    writeln!(out, "tick {}: {susceptible:?} susceptible", stats.tick())?;

    // A more infectious variant arrives. A live edit raises the infection rate and a second outbreak seeds it. Up to
    // 400 more ticks follow, sampled every 20, stopping once nobody is infected.
    simulation.set_param("infection_rate", 0.3f32)?;
    simulation.act("seed_outbreak")?;
    let mut samples = Vec::new();
    let flow = simulation.run_sampled(500, 20, |sample| {
        samples.push((sample.tick(), sample.scalar("Susceptible"), sample.scalar("Infected")));
        match sample.scalar("Infected") {
            Some(infected) if infected < 1.0 => ControlFlow::Break(sample.tick()),
            _ => ControlFlow::Continue(()),
        }
    })?;
    for (tick, susceptible, infected) in samples {
        writeln!(out, "tick {tick}: {susceptible:?} susceptible, {infected:?} infected")?;
    }
    if let ControlFlow::Break(tick) = flow {
        writeln!(out, "the epidemic ended by tick {tick}")?;
    }

    // Three infection rates, four replicates each, written to `folder`.
    let loaded = LoadedSpec::parse(
        r#"
        model = "sir"
        [set]
        grid_width = 128
        grid_height = 128
        [run]
        steps = 300
        replicates = 4
        [[block]]
        design = "factorial"
        factors = [{ param = "infection_rate", values = [0.2, 0.3, 0.4] }]
        "#,
    )?;
    let mut options = SweepOptions::new(Provenance::new(henad::build_info!(), std::env::args().collect()));
    options.spec_source = loaded.spec_source.clone();
    options.apply_execution(&loaded.execution);
    let record = run_spec(
        sir,
        None,
        &loaded.spec,
        SweepOutput::Directory(folder.to_owned()),
        &options,
        &mut NoProgress,
    )?;
    let counts = &record.report.counts;
    writeln!(out, "{} of {} runs ok", counts.ok, counts.rows)?;

    // Read the folder back and rebuild run 5 headlessly.
    let results = ResultSet::open_dir(folder, 64 << 20)?;
    let replay = results.replay(sir.schema(), 5)?;
    let mut rebuilt = RunSetup::from_replay(sir, &replay)?.build(None)?;
    rebuilt.run_to(replay.ticks)?;
    let infected = rebuilt.stats()?.scalar("Infected");
    writeln!(out, "run 5 ends with {infected:?} infected")?;
    Ok(replay)
}

The user guide covers the app, sweeps and writing your own models.

§License

Licensed under MIT or Apache-2.0, at your option.

Modules§

action
Action descriptors and the schedule of actions a run fires.
appapp
The app, opened over a host’s own model set.
authoring
Everything a model is written against: the model traits, their types, the helpers and the primitives.
benchmarkNon-WebAssembly
Benchmarks that time a model’s steps, as henad-cli does.
clicli and non-WebAssembly
The command line, run over a host’s own model set.
engine
Engine states a test or a host builds directly, without an entry.
explore
Sweeps, searches, result folders and replay.
gpu
The GPU device a model builds on, its sizing, and the headless device a program acquires.
modelsexample-models
The ten example models and the set that registers them.
params
Parameter descriptors and values, and the text form that --set accepts.
prelude
Items that a program imports whole to build, run and sweep models.
runner
Paced runners that step a model while a host draws it, and the snapshots they publish.
stats
Statistic descriptors and values, and the CSV writer for stat series.
testingtesting
Checks of a model entry’s contracts, for a model’s own tests.
views
Views of a CPU model’s state for a host to draw.

Macros§

actions
Declares a model’s actions and their indices in one place.
agent_lanes
Declares a model’s agent lanes.
buffers
Declares a model’s storage buffers and their indices in one place.
build_info
Returns the BuildInfo of the crate the macro expands in.
for_each_chunk_mut
Runs a body over each chunk of a mutable slice, in parallel.
include_shaders
Brings in the Rust that henad-build generated from the crate’s WGSL, as the modules shader_bindings and binding_decls.
params
Declares a model’s parameters and their indices in one place.

Structs§

BuildInfo
Identity of one compiled crate.
Fault
A failure Henad caught.
LaneSpec
One lane of a CPU agent model’s struct-of-arrays storage.
ModelEntry
One model a host can offer: its declarations, and the factory that builds it on any device.
ModelMetadata
Model metadata that the UI displays.
ModelSet
Models a host offers, each id once, in the order the host lists them.
ModelSetIter
Iterator over the entries of a ModelSet, in the set’s order.
ModelSource
Origin of a model’s code.
RunSetup
Values, seed and schedule for one build of an entry, each checked against its descriptors when set.
Simulation
One built model and its schedule, stepped by its caller.
SimulationViews
Views of one model, prepared together and borrowed from its state.
StatSample
One sample of a model’s stats.
TopologyHint
Display layers a model presents.

Enums§

Backend
Model backend.
ExportError
Reason Simulation::write_state wrote nothing, or stopped part way.
FaultKind
Kind of failure a Fault records.
ModelLookupError
Reason a host cannot run a model that it requested from a set.
ModelSetError
Reason a model set rejects an entry.
ModelState
A freshly built simulation state, tagged with the runner that can drive it.
SetupError
Reason a setup, a live edit or a live action was rejected.
Structure
Storage layout of a model, by backend and topology.

Constants§

ENGINE_BUILD
Henad’s own build, stamped by henad-explore’s build script over the engine’s crates.

Traits§

WasmNotSend
Send, relaxed to nothing on wasm with atomics.
WasmNotSync
Sync, relaxed to nothing on wasm with atomics.

Functions§

install_panic_hook
Installs a panic hook that records where each panic came from, then calls the hook that was already installed.