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.
| Feature |
Adds |
example-models |
The ten example models, at henad::models |
app |
The app as a library, at henad::app |
cli |
The command line as a library, at henad::cli, on native targets |
testing |
The 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.
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();
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())?;
let options = AppOptions::new(models, "SIR study", henad::build_info!()).opening(AppOpening::Run {
replay,
open_at: OpenAt::Start,
});
henad::app::run_native(options)?;
Ok(())
}
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")?;
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())?;
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}")?;
}
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)?;
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.