Skip to main content

fpm_rs/tabular/tables/
benchmark.rs

1use polars::prelude::*;
2
3use crate::{
4    Result,
5    benchmark::{BenchmarkFrameRecord, BenchmarkRecord},
6};
7
8/// Builds one stable summary row per benchmark run.
9pub fn benchmark_runs_dataframe(records: &[BenchmarkRecord]) -> Result<DataFrame> {
10    let length = records.len();
11    let mut run_ids = Vec::with_capacity(length);
12    let mut case_ids = Vec::with_capacity(length);
13    let mut algorithms = Vec::with_capacity(length);
14    let mut algorithm_configurations = Vec::with_capacity(length);
15    let mut dataset_names = Vec::with_capacity(length);
16    let mut dataset_versions = Vec::with_capacity(length);
17    let mut random_seeds = Vec::with_capacity(length);
18    let mut frame_counts = Vec::with_capacity(length);
19    let mut completed_iterations = Vec::with_capacity(length);
20    let mut elapsed_seconds = Vec::with_capacity(length);
21    let mut final_objectives = Vec::with_capacity(length);
22    let mut successes = Vec::with_capacity(length);
23    let mut errors = Vec::with_capacity(length);
24    let mut repetitions = Vec::with_capacity(length);
25    let mut warmups = Vec::with_capacity(length);
26    let mut groups = Vec::with_capacity(length);
27    let mut reference_run_ids = Vec::with_capacity(length);
28    for record in records {
29        run_ids.push(record.run_id.as_str());
30        case_ids.push(record.case_id.as_str());
31        algorithms.push(record.algorithm.as_str());
32        algorithm_configurations.push(record.algorithm_configuration.as_str());
33        dataset_names.push(record.dataset_name.as_str());
34        dataset_versions.push(record.dataset_version.as_deref());
35        random_seeds.push(record.random_seed);
36        frame_counts.push(record.frame_count as u64);
37        completed_iterations.push(record.success.then_some(record.completed_iterations as u64));
38        elapsed_seconds.push(record.elapsed_seconds);
39        final_objectives.push(record.success.then_some(record.final_objective).flatten());
40        successes.push(record.success);
41        errors.push(
42            (!record.success)
43                .then_some(record.error.as_deref())
44                .flatten(),
45        );
46        repetitions.push(
47            record
48                .metadata
49                .get("repetition")
50                .and_then(|value| value.parse::<u64>().ok()),
51        );
52        warmups.push(
53            record
54                .metadata
55                .get("warmup")
56                .and_then(|value| value.parse::<bool>().ok()),
57        );
58        groups.push(record.metadata.get("benchmark_group").map(String::as_str));
59        reference_run_ids.push(record.metadata.get("reference_run_id").map(String::as_str));
60    }
61    Ok(df!(
62        "run_id" => run_ids,
63        "case_id" => case_ids,
64        "algorithm" => algorithms,
65        "algorithm_configuration" => algorithm_configurations,
66        "dataset_name" => dataset_names,
67        "dataset_version" => dataset_versions,
68        "random_seed" => random_seeds,
69        "frame_count" => frame_counts,
70        "completed_iterations" => completed_iterations,
71        "elapsed_seconds" => elapsed_seconds,
72        "final_objective" => final_objectives,
73        "success" => successes,
74        "error" => errors,
75        "repetition" => repetitions,
76        "warmup" => warmups,
77        "benchmark_group" => groups,
78        "reference_run_id" => reference_run_ids,
79    )?)
80}
81
82/// Builds one row per recorded acquisition-frame benchmark metric.
83pub fn benchmark_frames_dataframe(records: &[BenchmarkRecord]) -> Result<DataFrame> {
84    let length = records.iter().map(|record| record.frames.len()).sum();
85    let mut run_ids = Vec::with_capacity(length);
86    let mut frame_indices = Vec::with_capacity(length);
87    let mut original_frame_indices = Vec::with_capacity(length);
88    let mut original_illumination_indices = Vec::with_capacity(length);
89    let mut normalized_l2 = Vec::with_capacity(length);
90    for record in records {
91        for BenchmarkFrameRecord {
92            frame_index,
93            original_frame_index,
94            original_illumination_index,
95            normalized_l2: residual,
96        } in &record.frames
97        {
98            run_ids.push(record.run_id.as_str());
99            frame_indices.push(*frame_index as u64);
100            original_frame_indices.push(*original_frame_index as u64);
101            original_illumination_indices
102                .push(original_illumination_index.map(|value| value as u64));
103            normalized_l2.push(*residual);
104        }
105    }
106    Ok(df!(
107        "run_id" => run_ids,
108        "frame_index" => frame_indices,
109        "original_frame_index" => original_frame_indices,
110        "original_illumination_index" => original_illumination_indices,
111        "normalized_l2" => normalized_l2,
112    )?)
113}
114
115/// Builds one result-bundle artifact row per run with a supplied relative path.
116pub fn benchmark_artifacts_dataframe(
117    records: &[BenchmarkRecord],
118    relative_result_paths: &std::collections::BTreeMap<String, String>,
119) -> Result<DataFrame> {
120    let length = records.len();
121    let mut run_ids = Vec::with_capacity(length);
122    let mut roles = Vec::with_capacity(length);
123    let mut relative_paths = Vec::with_capacity(length);
124    for record in records {
125        if let Some(path) = relative_result_paths.get(&record.run_id) {
126            run_ids.push(record.run_id.as_str());
127            roles.push("result_bundle");
128            relative_paths.push(path.as_str());
129        }
130    }
131    Ok(df!(
132        "run_id" => run_ids,
133        "role" => roles,
134        "relative_path" => relative_paths,
135    )?)
136}
137
138/// Builds long-form non-promoted key/value metadata rows for benchmark runs.
139pub fn benchmark_metadata_dataframe(records: &[BenchmarkRecord]) -> Result<DataFrame> {
140    const PROMOTED: &[&str] = &[
141        "repetition",
142        "warmup",
143        "benchmark_group",
144        "reference_run_id",
145    ];
146    let length = records
147        .iter()
148        .map(|record| record.metadata.len())
149        .sum::<usize>();
150    let mut run_ids = Vec::with_capacity(length);
151    let mut keys = Vec::with_capacity(length);
152    let mut values = Vec::with_capacity(length);
153    for record in records {
154        for (key, value) in &record.metadata {
155            if PROMOTED.contains(&key.as_str()) {
156                continue;
157            }
158            run_ids.push(record.run_id.as_str());
159            keys.push(key.as_str());
160            values.push(value.as_str());
161        }
162    }
163    Ok(df!(
164        "run_id" => run_ids,
165        "key" => keys,
166        "value" => values,
167    )?)
168}