Public task and development documentation. Official evaluation boundary.

Aggregate and paired statistics (statistics version 1.0)

This document defines the statistical summaries that EMBERBench computes over evaluation results.

There is no EMBER score or global winner in this module. (The EMBER Score is a separate module: see scoring.md.) This module does not rank models, combine metrics or produce a leaderboard. Every number belongs to one named metric. Win/loss/tie counts exist per metric only.

Out of scope here: leaderboards, a composite score, model rankings, provider adapters, procedural generation and a CLI.

1. Fail-closed input

An evaluation result in memory is not trusted as given. Before any episode is used, both functions:

  1. rebuild the evaluation manifest from its dictionary and require that it is equal to the original;
  2. call the manifest check: the suite ID, version and hash and the simulator, observation and metrics versions must match the running code;
  3. rebuild the evaluation result, which repeats its structural checks (manifest order, no duplicates, every episode equal to its manifest entry, record headers equal to the manifest);
  4. for every episode: resolve its scenario from the suite by ID and content hash, recompute episode metrics from the run record (this authenticates the record by simulator replay), and require that the stored metrics equal the recomputed metrics exactly (canonical JSON).

Any failure raises a statistics input error (a subclass of evaluation error). Nothing is repaired, skipped or partly summarized.

The paired comparison first calls the result pairing. Results from different evaluation manifests raise an evaluation error before any replay.

2. Supported metrics and directions

Each metric is a per-episode scalar from episode metrics. The module rebuilds it as an exact Fraction from the integer counts in the metrics (see §10) and requires that the float of that fraction equals the stored value.

Metric Source field Kind Direction Exact definition (per episode) Undefined when
evacuated_rate safety.evacuated_rate outcome higher is better evacuated / population never
casualty_rate safety.casualty_rate outcome lower is better casualties / population never
stranded_rate safety.stranded_rate outcome lower is better stranded / population never
terminal_trapped_rate safety.terminal_trapped_rate outcome lower is better terminal_trapped_count / population never
mean_person_time_to_safety time.mean_person_time_to_safety outcome lower is better person_time_to_safety_total / population never
blocked_rate movement.blocked_rate behaviour lower is better blocked_moves / movement_attempts no movement attempts
congestion_block_rate movement.congestion_block_rate behaviour lower is better congestion_blocks / movement_attempts no movement attempts
aggregate_path_efficiency path_efficiency.aggregate_path_efficiency proxy higher is better total_static_lower_bound / total_cells_moved no measured movement
mean_exposure fire_exposure.mean_exposure proxy lower is better (Σ_d count_d / d) / active_person_states never
exits_used exits.exits_used descriptive none number of exits with n_e >= 1 never
max_exit_share exits.max_exit_share descriptive none max_e n_e / E E = 0
exit_balance exits.exit_balance descriptive none normalized Gini-Simpson index K = 1 or E = 0

The per-episode definitions are in metrics.md. Notes:

  • Kinds. Outcome metrics are terminal safety and time results. Behaviour metrics describe observed movement. Proxy metrics approximate a quantity that is not measured directly (path optimality, fire danger). Descriptive metrics describe the run and are not objectives.
  • Directions are used only to read the sign of a paired difference (§8). They do not make a metric an objective by itself.
  • mean_person_time_to_safety counts every non-evacuee at max_steps (censoring), so it mixes speed and safety. Read it with the safety rates.
  • blocked_rate and congestion_block_rate are lower-is-better per metric, but a run can lower them by not moving. Never read them alone.
  • Exit metrics have no direction. Equal exit use is often not optimal (one exit can be much closer, or threatened by fire). exit_balance is never an objective, and no win/loss/tie count exists for any exit metric.

3. Groups: overall, per scenario, per family

Each artifact holds the same summaries for three kinds of group:

Group Episodes Order
overall every scheduled episode —
scenarios every scheduled episode of one scenario suite manifest order
families every scheduled episode of every scenario in the family Scenario family declaration order

A scenario with several families contributes all of its episodes to every one of those families. Family views can therefore overlap, and the family counts do not add up to the overall counts. A family with no scheduled scenario is not listed.

Overall statistics use the episode observations directly. They are never an average of scenario or family means. With unequal group sizes (for example after missing episodes) the two are different, and only the episode-level value is reported. Each episode has equal weight; a scenario with more scheduled seeds has more weight in overall.

4. Descriptive statistics

For one metric in one group, let x_1, …, x_n be the defined values of the observed episodes, and x_(1) <= … <= x_(n) the same values in ascending order.

Field Formula Defined when
n number of defined values always
mean (Σ_i x_i) / n n >= 1
median the type-7 quantile at p = 1/2 (the mean of the two middle values when n is even) n >= 1
variance sample variance Σ_i (x_i - mean)² / (n - 1) n >= 2
std sqrt(variance) n >= 2
min, max x_(1), x_(n) n >= 1
quantiles type-7 quantiles at p25, p50, p75, p90, p95 n >= 1

An undefined statistic is null (None). It is never 0.

Quantile rule (Hyndman-Fan type 7)

With 0-based sorted values y[0] <= … <= y[n-1] and a level p in [0, 1]:

h = (n - 1) · p
j = floor(h)
g = h - j
Q(p) = y[j] + g · (y[j+1] - y[j])        (Q(p) = y[j] when g = 0)

This is the default of R (type = 7) and of NumPy (method = "linear"). The levels are fixed: p25 = 1/4, p50 = 1/2, p75 = 3/4, p90 = 9/10, p95 = 19/20. With n = 1 every quantile is the single value.

Example: for 1, 2, 3, 4 the quantiles are p25 = 1.75, p50 = 2.5, p75 = 3.25, p90 = 3.7, p95 = 3.85, and the sample variance is 5/3.

5. Robustness and completeness counts

Each aggregate group has a robustness object of raw episode counts. There are no rates here, so nothing hides the denominators.

Field Counts episodes …
scheduled in the manifest for this group
observed with an authenticated result
missing scheduled, with no result (scheduled - observed)
all_resolved observed, terminated with all_resolved
max_steps observed, terminated at the step limit
with_casualties observed, casualties > 0
with_stranded observed, stranded > 0
zero_evacuation observed, evacuated = 0

all_resolved + max_steps = observed. An episode can count in several of the last four rows.

6. Missing and undefined data

There are two different kinds of absent value. Both are reported; neither is imputed.

  • Missing episode. The manifest schedules it but the result has no entry for it (for example, an interrupted run). It is missing, not a model failure: it is not counted as a casualty, a timeout or zero evacuation. The artifact lists every missing episode key (missing_episodes), and every group gives missing_indices and robustness.missing.
  • Undefined metric. The episode has a result, but the metric is None for it (for example blocked_rate when nobody tried to move). The per-metric summary counts it in undefined.

For each metric summary:

scheduled = observed + missing
n         = observed - undefined

The statistics use the n defined values only. No value is filled in with zero, a mean, a worst case or a value from another episode.

7. Paired comparison

The paired comparison uses the result pairing for the exact episode join. An episode is paired only when both results contain the same key (manifest hash, index, scenario ID, scenario hash, seed). Unpaired episodes are never compared; they are listed in missing_from_a and missing_from_b.

For one metric in one group, with paired episodes k where the metric is defined for both A and B:

delta_k = value_A(k) - value_B(k)        (always A - B)

Each paired metric comparison reports:

Field Meaning
paired paired episodes in the group
missing_from_a, missing_from_b scheduled episodes in the group that A or B does not have
undefined paired episodes where the metric is None for A or for B
deltas every (episode index, delta_k), in manifest order
statistics §5 statistics of the deltas: mean, median, std, quantiles, … (n = paired - undefined)
mean_delta_ci §9 interval for the mean delta
delta_positive, delta_negative, delta_zero counts of delta > 0, < 0, = 0
a_better, b_better, ties win/loss/tie counts by the metric's direction

Win/loss/tie, per metric:

Direction a_better b_better ties
higher is better delta > 0 delta < 0 delta = 0
lower is better delta < 0 delta > 0 delta = 0
descriptive null null null

There is no count across metrics, no majority vote and no composite. Swapping A and B negates every delta and swaps a_better and b_better.

8. Confidence intervals

Each metric summary has mean_ci (aggregate) or mean_delta_ci (paired): a 95 % percentile bootstrap interval for the mean.

  1. Let x_1, …, x_n be the defined values (aggregate) or the paired deltas (paired). If n < 5 the interval is null.
  2. For each of R resamples, draw n indices uniformly with replacement from 0 … n-1 and take the mean of the selected values. In a paired comparison the resampling unit is the paired episode, so A and B stay together in the delta.
  3. Sort the R resample means. The bounds are their type-7 quantiles at 0.025 and 0.975.

R is resamples: the default is 2000; any integer in [100, 1 000 000] is accepted. All means are exact fractions; only the bounds become floats.

Determinism. The bootstrap never uses random, the time or OS entropy. Its stream seed is

seed = SHA-256( canonical JSON of {
    statistics_version, purpose ("aggregate_mean" or "paired_mean_delta"),
    evaluation_manifest_hash, result_digests, group [kind, name], metric, resamples } )

where each result digest is the SHA-256 of the canonical evaluation result (the artifact as saved). The 64-bit words come from SHAKE-256 in counter mode: block k is SHAKE-256("emberbench/statistics/bootstrap/v1\0" || seed || k) (k as 8 big-endian bytes), read as 512 big-endian unsigned 64-bit words. An index below n is drawn by rejection sampling (word < 2^64 - 2^64 mod n, then word mod n), so there is no modulo bias. The seed is in the output for audit. Identical inputs give identical intervals on every platform. Different data, labels or manifests give different streams. This stream shares nothing with the simulator's SplitMix64 streams.

Interpretation. An interval describes the uncertainty of the mean over this evaluation sample of scenarios and seeds, resampled at the episode level. It does not prove that one model is better in general, on other scenarios or on other seeds. Episodes of one scenario are not independent of each other, and the public suite is small, so treat intervals as descriptive.

9. Exact arithmetic and serialization

  • Per-episode values are exact Fractions rebuilt from integer counts (for mean_exposure, from the fire-distance histogram: Σ count_d / d).
  • Means, medians, variances, quantiles, min/max, deltas and bootstrap means are exact Fractions in the dataclasses.
  • The output boundary is the dictionary: each exact value becomes its correctly rounded float. The standard deviation is the one irrational value: it is math.sqrt(float(variance)), which IEEE 754 rounds correctly.
  • Every list is in manifest, suite or scenario family order. No result depends on dict or set iteration order or on PYTHONHASHSEED.
  • The JSON text is canonical JSON (sorted keys, no whitespace, ASCII, no NaN). The digest is its SHA-256. The model ID label, the input result digests and resamples are part of the artifact, so a change to any of them changes the digest.

10. Artifact layout

The aggregate statistics dictionary form:

statistics_version, artifact = "aggregate", model_id,
evaluation_manifest_hash, suite_id, suite_version, suite_hash,
simulator_version, observation_version, metrics_version,
result_digest, scheduled_episodes, observed_episodes, missing_episodes[],
metric_definitions[], methods{quantiles, variance, std, bootstrap},
overall{kind, name, episode_indices, missing_indices, robustness, metrics[]},
scenarios[], families[]

Each metrics[] entry: metric, scheduled, observed, missing, undefined, statistics{n, mean, median, variance, std, min, max, quantiles{p25…p95}}, mean_ci{method, statistic, level, resamples, seed, lower, upper} | null.

The paired statistics dictionary form has the same header with model_a, model_b, result_digest_a, result_digest_b, delta = "A - B", paired_count, missing_from_a[], missing_from_b[], and groups with paired_indices, missing_from_a, missing_from_b and the §8 fields per metric.

11. Summary of what each section is

Concept Where Is it a judgement?
Descriptive statistics §5, statistics no: a summary of observed values
Robustness counts §6, robustness no: raw episode counts
Missing / undefined §7 no: completeness accounting
Paired differences §8, deltas no: signed A - B values
Per-metric win/loss/tie §8 per metric only, by its direction
Metrics with no direction §3, exit metrics never a winner
Confidence intervals §9 uncertainty over this sample only

There is no EMBER score or global winner in this module.

Reviewed public benchmark contract. Apache License 2.0. Download Markdown