Skip to main content

sciforge_hub/engine/
campaign.rs

1//! Multi-step experiment campaigns.
2//!
3//! A [`Campaign`] groups ordered [`Step`]s, each wrapping an
4//! [`Experiment`], and collects their results in a [`CampaignResult`].
5
6use super::experience::experiment::Experiment;
7use super::experience::runner::{ExperimentRunner, RunOutput};
8use crate::domain::common::errors::HubResult;
9
10/// Single step in a campaign.
11pub struct Step {
12    /// Step name.
13    pub name: String,
14    /// Experiment to run.
15    pub experiment: Experiment,
16}
17
18/// Collected results from a campaign run.
19pub struct CampaignResult {
20    /// Ordered pairs of (step name, output).
21    pub step_results: Vec<(String, RunOutput)>,
22}
23
24impl CampaignResult {
25    /// Returns the output for a step by name.
26    pub fn get(&self, name: &str) -> Option<&RunOutput> {
27        self.step_results
28            .iter()
29            .find(|(n, _)| n == name)
30            .map(|(_, r)| r)
31    }
32
33    /// Number of completed steps.
34    pub fn len(&self) -> usize {
35        self.step_results.len()
36    }
37
38    /// Returns `true` if no steps were executed.
39    pub fn is_empty(&self) -> bool {
40        self.step_results.is_empty()
41    }
42
43    /// Extracts all scalar results as (name, value) pairs.
44    pub fn scalars(&self) -> Vec<(&str, f64)> {
45        self.step_results
46            .iter()
47            .filter_map(|(n, r)| {
48                if let RunOutput::Scalar(v) = r {
49                    Some((n.as_str(), *v))
50                } else {
51                    None
52                }
53            })
54            .collect()
55    }
56}
57
58/// Ordered sequence of experiments executed in series.
59pub struct Campaign {
60    /// Campaign name.
61    pub name: String,
62    steps: Vec<Step>,
63}
64
65impl Campaign {
66    /// Creates a new empty campaign.
67    pub fn new(name: &str) -> Self {
68        Self {
69            name: name.to_string(),
70            steps: Vec::new(),
71        }
72    }
73
74    /// Appends a named experiment step.
75    pub fn add_step(mut self, name: &str, experiment: Experiment) -> Self {
76        self.steps.push(Step {
77            name: name.to_string(),
78            experiment,
79        });
80        self
81    }
82
83    /// Number of registered steps.
84    pub fn step_count(&self) -> usize {
85        self.steps.len()
86    }
87
88    /// Runs all steps sequentially and collects results.
89    pub fn run(&self, runner: &ExperimentRunner) -> HubResult<CampaignResult> {
90        let mut results = Vec::with_capacity(self.steps.len());
91        for step in &self.steps {
92            let output = runner.run(&step.experiment)?;
93            results.push((step.name.clone(), output));
94        }
95        Ok(CampaignResult {
96            step_results: results,
97        })
98    }
99}