///|
fn combine_error_summaries(summaries : Array[ErrorSummary]) -> ErrorSummary {
let mut count = 0
let mut mae_total = 0.0
let mut squared_total = 0.0
let mut mape_total = 0.0
let mut maximum = 0.0
for summary in summaries {
count += summary.count
mae_total += summary.mae * summary.count.to_double()
squared_total += square(summary.rmse) * summary.count.to_double()
mape_total += summary.mape * summary.count.to_double()
if summary.max_error > maximum {
maximum = summary.max_error
}
}
if count == 0 {
{ count: 0, mae: 0.0, rmse: 0.0, mape: 0.0, max_error: 0.0 }
} else {
let total = count.to_double()
{
count,
mae: mae_total / total,
rmse: (squared_total / total).sqrt(),
mape: mape_total / total,
max_error: maximum,
}
}
}
///|
fn mean_result_confidence(results : Array[EtaResult]) -> Double {
let values : Array[Double] = []
for result in results {
values.push(result.confidence)
}
robust_mean(values)
}
///|
fn benchmark_case_from_config(
name : String,
notes : String,
config : ScenarioConfig,
route : RouteProfile,
) -> BenchmarkCase {
let trace = generate_scenario(route, config)
let results = forecast_route(trace.observations, route)
let points = eta_results_to_points(results, trace.actual_remaining)
{
name,
points: points.length(),
summary: summarize_errors(points),
mean_confidence: mean_result_confidence(results),
notes,
}
}
///|
/// Build the deterministic benchmark matrix used in CI and README output.
pub fn benchmark_cases() -> Array[BenchmarkCase] {
let route = demo_route()
[
benchmark_case_from_config(
"stable",
"low congestion with no injected measurement spike",
{ ..default_scenario_config(), congestion: 0.12, noise_magnitude: 0.0 },
route,
),
benchmark_case_from_config(
"noisy",
"moderate congestion with periodic positive and negative spikes",
{ ..default_scenario_config(), congestion: 0.35, noise_magnitude: 72.0 },
route,
),
benchmark_case_from_config(
"delayed",
"longer observation interval and lower base speed",
{
..default_scenario_config(),
steps: 10,
interval_seconds: 75.0,
base_speed: 8.0,
noise_magnitude: 34.0,
},
route,
),
benchmark_case_from_config(
"congested",
"high congestion route trace with short local speed envelope",
{
..default_scenario_config(),
congestion: 0.82,
base_speed: 7.0,
noise_magnitude: 48.0,
},
route,
),
]
}
///|
/// Render one benchmark case as a stable text row.
pub fn format_benchmark_case(case : BenchmarkCase) -> String {
"\{case.name} points=\{case.points} mae=\{case.summary.mae} rmse=\{case.summary.rmse} mape=\{case.summary.mape} confidence=\{case.mean_confidence}"
}
///|
/// Render the full benchmark report as Markdown-compatible text.
pub fn render_benchmark_report(
cases : Array[BenchmarkCase],
aggregate : ErrorSummary,
) -> String {
let builder = StringBuilder()
builder.write_string("ETA benchmark\n")
builder.write_string("case | points | MAE | RMSE | MAPE | confidence\n")
builder.write_string("--- | ---: | ---: | ---: | ---: | ---:\n")
for case in cases {
builder.write_string(format_benchmark_case(case))
builder.write_char('\n')
}
builder.write_string("aggregate ")
builder.write_string(format_error_summary(aggregate))
builder.to_string()
}
///|
/// Run all benchmark cases and return aggregate metrics.
pub fn run_benchmark_suite() -> BenchmarkReport {
let cases = benchmark_cases()
let summaries : Array[ErrorSummary] = []
let mut total_points = 0
for case in cases {
summaries.push(case.summary)
total_points += case.points
}
let aggregate = combine_error_summaries(summaries)
{
cases,
aggregate,
total_points,
rendered: render_benchmark_report(cases, aggregate),
}
}
///|
/// Return the benchmark case with the lowest MAE.
pub fn best_benchmark_case(report : BenchmarkReport) -> BenchmarkCase? {
if report.cases.length() == 0 {
None
} else {
let mut best = report.cases[0]
for case in report.cases[1:] {
if case.summary.mae < best.summary.mae {
best = case
}
}
Some(best)
}
}
///|
/// Return the benchmark case with the highest MAE.
pub fn worst_benchmark_case(report : BenchmarkReport) -> BenchmarkCase? {
if report.cases.length() == 0 {
None
} else {
let mut worst = report.cases[0]
for case in report.cases[1:] {
if case.summary.mae > worst.summary.mae {
worst = case
}
}
Some(worst)
}
}
///|
/// Check whether all cases meet a deterministic quality threshold.
pub fn benchmark_passes(report : BenchmarkReport, max_mae~ : Double) -> Bool {
report.cases.all(case => case.summary.mae <= max_mae)
}
///|
/// Return all case names in report order.
pub fn benchmark_case_names(report : BenchmarkReport) -> Array[String] {
let names : Array[String] = []
for case in report.cases {
names.push(case.name)
}
names
}
///|
/// Calculate the relative MAE spread between best and worst cases.
pub fn benchmark_mae_spread(report : BenchmarkReport) -> Double {
match (best_benchmark_case(report), worst_benchmark_case(report)) {
(Some(best), Some(worst)) =>
safe_div(worst.summary.mae - best.summary.mae, best.summary.mae, 0.0)
_ => 0.0
}
}
///|
/// Create a benchmark report from an arbitrary route and scenario matrix.
pub fn benchmark_route(
route : RouteProfile,
scenarios : Array[(String, ScenarioConfig)],
) -> BenchmarkReport {
let cases : Array[BenchmarkCase] = []
for pair in scenarios {
let name = pair.0
let config = pair.1
cases.push(
benchmark_case_from_config(
name, "caller supplied scenario", config, route,
),
)
}
let summaries : Array[ErrorSummary] = []
let mut total_points = 0
for case in cases {
summaries.push(case.summary)
total_points += case.points
}
let aggregate = combine_error_summaries(summaries)
{
cases,
aggregate,
total_points,
rendered: render_benchmark_report(cases, aggregate),
}
}
///|
/// Return a benchmark summary suitable for a single-line CI annotation.
pub fn benchmark_annotation(report : BenchmarkReport) -> String {
"cases=\{report.cases.length()} points=\{report.total_points} mae=\{report.aggregate.mae} rmse=\{report.aggregate.rmse}"
}