Skip to main content

mech_sim/
sweep.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6
7use crate::config::{SweepCase, SweepPreset};
8use crate::integrator::simulate;
9use crate::outputs::write_run_outputs;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct SweepCaseSummary {
13    pub case_id: String,
14    pub group: String,
15    pub note: String,
16    pub scenario: String,
17    pub output_dir: String,
18    pub success: bool,
19    pub continuous_power_mw: f64,
20    pub burst_power_mw: f64,
21    pub burst_duration_s: f64,
22    pub pulse_energy_gj: f64,
23    pub initial_ep_gj: f64,
24    pub thermal_rejection_mw_per_k: f64,
25    pub actuator_demand_scale: f64,
26    pub damping_scale: f64,
27    pub stiffness_scale: f64,
28    pub burst_cadence_s: Option<f64>,
29    pub allocation_strategy: Option<String>,
30    pub min_ep_gj: f64,
31    pub energy_depleted_gj: f64,
32    pub peak_temperature_k: f64,
33    pub peak_temperature_c: f64,
34    pub time_above_thermal_threshold_s: f64,
35    pub recharge_time_s: Option<f64>,
36    pub time_to_any_threshold_s: Option<f64>,
37    pub first_local_buffer_breach_s: Option<f64>,
38    pub first_admissible_breach_s: Option<f64>,
39    pub effective_duty_cycle: f64,
40    pub recharge_readiness_fraction: f64,
41    pub successful_burst_fraction: f64,
42    pub mean_authority_utilization: f64,
43    pub mean_delivered_ratio: f64,
44    pub degraded_state_fraction: f64,
45    pub min_local_buffer_mj: f64,
46    pub local_imbalance_max_mj: f64,
47    pub saturation_count: usize,
48    pub delivered_mechanical_work_j: f64,
49    pub energy_breach: bool,
50    pub thermal_breach: bool,
51    pub local_buffer_breach: bool,
52    pub saturation_breach: bool,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct SweepAggregate {
57    pub preset: String,
58    pub root_dir: PathBuf,
59    pub cases_dir: PathBuf,
60    pub case_summaries: Vec<SweepCaseSummary>,
61}
62
63pub fn run_sweep(
64    preset: SweepPreset,
65    cases: Vec<SweepCase>,
66    run_root: &Path,
67) -> Result<SweepAggregate> {
68    let cases_dir = run_root.join("sweeps").join(preset.as_str());
69    fs::create_dir_all(&cases_dir)?;
70
71    let mut case_summaries = Vec::new();
72    for case in cases {
73        let case_dir = cases_dir.join(&case.metadata.case_id);
74        fs::create_dir_all(&case_dir)?;
75        let result = simulate(case.config.clone())?;
76        write_run_outputs(&case_dir, &result)?;
77
78        case_summaries.push(SweepCaseSummary {
79            case_id: case.metadata.case_id.clone(),
80            group: case.metadata.group.clone(),
81            note: case.metadata.note.clone(),
82            scenario: result.config.scenario.name.clone(),
83            output_dir: case_dir.to_string_lossy().to_string(),
84            success: result.summary.success,
85            continuous_power_mw: case.metadata.continuous_power_mw,
86            burst_power_mw: case.metadata.burst_power_mw,
87            burst_duration_s: case.metadata.burst_duration_s,
88            pulse_energy_gj: case.metadata.pulse_energy_gj,
89            initial_ep_gj: case.metadata.initial_ep_gj,
90            thermal_rejection_mw_per_k: case.metadata.thermal_rejection_mw_per_k,
91            actuator_demand_scale: case.metadata.actuator_demand_scale,
92            damping_scale: case.metadata.damping_scale,
93            stiffness_scale: case.metadata.stiffness_scale,
94            burst_cadence_s: case.metadata.burst_cadence_s,
95            allocation_strategy: case
96                .metadata
97                .allocation_strategy
98                .map(|strategy| format!("{strategy:?}")),
99            min_ep_gj: result.summary.min_ep_j / 1.0e9,
100            energy_depleted_gj: result.summary.energy_depleted_j / 1.0e9,
101            peak_temperature_k: result.summary.peak_temperature_k,
102            peak_temperature_c: result.summary.peak_temperature_k - 273.15,
103            time_above_thermal_threshold_s: result.summary.time_above_thermal_threshold_s,
104            recharge_time_s: result.summary.recharge_time_s,
105            time_to_any_threshold_s: result.summary.time_to_any_threshold_s,
106            first_local_buffer_breach_s: result.summary.first_local_buffer_breach_s,
107            first_admissible_breach_s: result.summary.first_admissible_breach_s,
108            effective_duty_cycle: result.summary.effective_duty_cycle,
109            recharge_readiness_fraction: result.summary.recharge_readiness_fraction,
110            successful_burst_fraction: result.summary.successful_burst_fraction,
111            mean_authority_utilization: result.summary.mean_authority_utilization,
112            mean_delivered_ratio: result.summary.mean_delivered_ratio,
113            degraded_state_fraction: result.summary.degraded_state_fraction,
114            min_local_buffer_mj: result.summary.min_local_buffer_j / 1.0e6,
115            local_imbalance_max_mj: result.summary.local_imbalance_max_j / 1.0e6,
116            saturation_count: result.summary.saturation_count,
117            delivered_mechanical_work_j: result.summary.delivered_mechanical_work_j,
118            energy_breach: result.summary.energy_breach,
119            thermal_breach: result.summary.thermal_breach,
120            local_buffer_breach: result.summary.local_buffer_breach,
121            saturation_breach: result.summary.saturation_breach,
122        });
123    }
124
125    Ok(SweepAggregate {
126        preset: preset.as_str().to_string(),
127        root_dir: run_root.to_path_buf(),
128        cases_dir,
129        case_summaries,
130    })
131}