sim_lib_view_math/
sweep.rs1use sim_kernel::{Error, Expr, Result};
9use sim_lib_scene::{node, sym};
10
11use crate::num::{as_f64, number};
12use crate::plot::{multi_plot_view, response_plot_view};
13
14pub struct Sweep {
16 param: f64,
17 samples: usize,
18 snapshots: Vec<(String, Vec<(f64, f64)>)>,
19}
20
21impl Sweep {
22 pub fn new(param: f64, samples: usize) -> Self {
24 Self {
25 param,
26 samples: samples.max(2),
27 snapshots: Vec::new(),
28 }
29 }
30
31 pub fn param(&self) -> f64 {
33 self.param
34 }
35
36 fn series(&self) -> Vec<(f64, f64)> {
37 (0..self.samples)
38 .map(|i| {
39 let x = i as f64;
40 (x, self.param * x)
41 })
42 .collect()
43 }
44
45 pub fn plot(&self) -> Expr {
48 let plot = multi_plot_view(&[(format!("param={}", self.param), self.series())]);
49 node(
50 "stack",
51 vec![
52 ("role", sym("sweep")),
53 ("dir", sym("column")),
54 (
55 "children",
56 Expr::List(vec![
57 node(
58 "slider",
59 vec![
60 ("param", sym("slope")),
61 ("min", number(-5.0)),
62 ("max", number(5.0)),
63 ("value", number(self.param)),
64 ],
65 ),
66 plot,
67 ]),
68 ),
69 ],
70 )
71 }
72
73 pub fn set_param(&mut self, intent: &Expr) -> Result<Expr> {
76 match intent_field(intent, "kind") {
77 Some(Expr::Symbol(kind)) if &*kind.name == "set-param" => {}
78 _ => return Err(Error::HostError("expected an intent/set-param".to_owned())),
79 }
80 let value = intent_field(intent, "value")
81 .and_then(as_f64)
82 .ok_or_else(|| Error::HostError("set-param 'value' must be a number".to_owned()))?;
83 self.param = value;
84 Ok(self.plot())
85 }
86
87 pub fn snapshot(&mut self) {
89 self.snapshots
90 .push((format!("param={}", self.param), self.series()));
91 }
92
93 pub fn snapshot_count(&self) -> usize {
95 self.snapshots.len()
96 }
97
98 pub fn compare(&self) -> Expr {
100 let mut series = self.snapshots.clone();
101 series.push((format!("param={} (current)", self.param), self.series()));
102 multi_plot_view(&series)
103 }
104}
105
106pub fn response_sweep_view(
108 lens: &str,
109 role: &str,
110 param: SweepParam<'_>,
111 series_list: &[(String, Vec<(f64, f64)>)],
112) -> Expr {
113 node(
114 "stack",
115 vec![
116 ("lens", sym(lens)),
117 ("role", sym(role)),
118 ("dir", sym("column")),
119 (
120 "children",
121 Expr::List(vec![
122 node(
123 "slider",
124 vec![
125 ("param", sym(param.name)),
126 ("min", number(param.min)),
127 ("max", number(param.max)),
128 ("value", number(param.value)),
129 ],
130 ),
131 response_plot_view(lens, "response-plot", series_list),
132 ]),
133 ),
134 ],
135 )
136}
137
138pub struct SweepParam<'a> {
140 pub name: &'a str,
142 pub min: f64,
144 pub max: f64,
146 pub value: f64,
148}
149
150fn intent_field<'a>(intent: &'a Expr, name: &str) -> Option<&'a Expr> {
151 let Expr::Map(entries) = intent else {
152 return None;
153 };
154 entries.iter().find_map(|(key, value)| {
155 matches!(key, Expr::Symbol(symbol) if &*symbol.name == name && symbol.namespace.is_none())
156 .then_some(value)
157 })
158}