Skip to content

robin-busaragroup/better.aika

Repository files navigation

Aika

Deterministic discrete-event, multi-agent simulation engine. Stable single-threaded core (aika-st) and experimental optimistic parallel engine (aika-mt-hybrid). Includes a builder API for ergonomic agent wiring.

Goals:

  • Reproducible: Same seed => identical execution order (time, seq_no, agent_id).
  • Efficient short-term scheduling via hierarchical timing wheel + overflow heap.
  • Extensible toward optimistic parallel (Time Warp) execution.
  • Small, explicit core types (no macro DSL) with predictable memory layout.

ASCII Architecture

+------------------+        +-------------------+
|  aika-core       |<-------|  user agents      |
|  types & traits  |        +-------------------+
+---------+--------+
          |                (feature mt-hybrid)
          v
+---------+---------+    +----------------------+
|  aika-st (wheel)  |    |  aika-mt-hybrid      |
|  deterministic    |    |  optimistic (Time W) |
+-------------------+    +----------------------+

Quickstart

Run an example:

cargo run -p aika-st --example ping_pong

Add to your project (path dependency while unpublished):

[dependencies]
aika-core = { path = "../better.aika/aika-core" }
aika-st = { path = "../better.aika/aika-st" }

Define a Pod + Zeroable message and an Agent implementing step:

#[repr(C)] #[derive(Copy,Clone,bytemuck::Zeroable,bytemuck::Pod)]
struct Msg { x: u32 }

struct Counter { left: u32 }
impl aika_core::Agent<aika_core::DEFAULT_SLOTS, Msg> for Counter {
    fn step(&mut self, cx:&mut aika_core::Cx<aika_core::DEFAULT_SLOTS, Msg>, id: aika_core::AgentId) -> aika_core::Action<Msg> {
        while let Some(_m)=cx.inbox.pop() {}
        if self.left==0 { return aika_core::Action::Sleep(100); }
        self.left -=1;
        cx.send(id, Msg { x: self.left }, 1); // self message in 1 tick
        aika_core::Action::Sleep(1)
    }
}

Build a world using the builder (avoids manual downcasts / unsafe wiring):

use aika_st::engine::{WorldBuilder};
let mut builder = WorldBuilder::<Msg>::new(10_000, 42);
let a = builder.add(Counter { left: 10 });
let mut world = builder.build();
world.run();
println!("finished at t={} seed=42", world.now());

Determinism

World assigns a monotonically increasing sequence number (per world) to each scheduled event; ordering keys are (time, seq_no, agent_id) ensuring total order and stable replay with same seed. Each agent receives its own RNG seeded by world_seed ^ agent_id.

Time vs commit_time

time: scheduled execution tick. commit_time: (future) irrevocable time after GVT advancement in optimistic engine. In the single-threaded engine they are identical.

Mailboxes

Each agent owns a bounded ring inbox (RingBuf). During a step, an agent drains its inbox, may emit outbound messages via cx.send(to, msg, after). The engine converts these into internal Deliver events and immediately wakes the recipient if appropriate.

Overflow behavior: pushing to a full mailbox returns an error (currently ignored by the engine). Tests assert mailbox length never exceeds capacity so excess messages are safely dropped. Future versions may surface metrics or backpressure hooks.

Planned optimistic parallel (mt-hybrid feature)

Time Warp style optimistic processing (feature gated):

  • Per-thread local time + event queues
  • Straggler detection => rollback via snapshot restore + anti-messages
  • Global Virtual Time (GVT) reduction for fossil collection Current implementation is a stub exposing a config and monotonic GVT placeholder.

PHOLD workload

The phold_st example and Criterion bench implement a configurable PHOLD-like workload stressing scheduling & message delivery. Historically env vars were used; now a lightweight CLI is provided (still honors env defaults):

cargo run -p aika-st --features metrics --example phold_st -- \
    32 20000 \              # positional: <agents> <terminal_time>
    --remote 0.12 \         # probability remote send
    --fanout 6 \            # max fanout per processed message
    --stim 0.08 \           # idle self-stim probability
    --metrics \             # enable metrics capture (requires 'metrics' feature)
    --runs 5 \              # repeat simulation N times aggregating histograms
    --top 12 \              # print top N busiest slots
    --metrics-json phold_runs.json \  # JSON metrics artifact (auto timestamp if omitted)
    --metrics-csv phold_hist.csv      # CSV aggregated histogram

When --metrics is active (and the metrics feature enabled) the example collects per-tier 256-slot occupancy histograms (4 tiers) for the hierarchical timing wheel. Across multiple --runs it aggregates:

  • Aggregate histogram (sum of per-run counts)
  • Busiest slots (top N by count) with percentage of total
  • Full descending cumulative distribution (CDF) across all non-zero slots
  • Per-slot probability variance across runs (sparse: only non-zero variance entries)
  • Peak queued events, processed event count, total delivered messages

Artifacts:

  • JSON (--metrics-json or default phold_metrics_<epoch>.json): structured runs array + aggregate + busiest + full CDF + prob_variance
  • CSV (--metrics-csv): rows tier,slot,count,prob for non-zero counts in aggregate

Example to just view busiest slots once:

cargo run -p aika-st --features metrics --example phold_st -- --metrics 16 10000 --top 10

Per-agent event distribution stats are printed for the first run (or the only run) to gauge load balance.

Benchmark sidecar metrics

cargo bench -p aika-st --features metrics --bench phold_bench produces normal Criterion reports plus two sidecar files at repo root:

  • criterion_engine_metrics.jsonl – JSON lines with selected engine metrics per benchmark iteration (append-only)
  • criterion_engine_metrics_summary.json – overwritten concise summary (processed, peak_queued, busiest slots) suitable for light HTML embedding/post-processing.

Integrate into custom reporting by reading the JSONL after the benchmark run and merging with Criterion's HTML (e.g. inject via a small script that copies summary data into report/index.html).

Deliver batching & mailbox drops

The single-thread engine batches outbound messages produced in one agent step by (target, delivery_time). Instead of scheduling N individual Deliver events (each immediately followed by a wake Continue), it groups them into a single DeliverBatch where possible, reducing scheduling overhead and total processed events under bursty fanout. A global runtime toggle is exposed for tests: aika_st::engine::set_batching_enabled(bool) (default on). The PHOLD JSON artifacts now include mailbox_drops reporting how many incoming messages were dropped due to full fixed-size inboxes (DEFAULT_SLOTS). This helps identify pathological hotspots or insufficient mailbox capacity.

Benchmark (HTML reports under target/criterion):

cargo bench -p aika-st --bench phold_bench

License

Dual-licensed MIT OR Apache-2.0.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors