1use super::*;
2
3#[serde_api]
4#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
5#[non_exhaustive]
6#[serde(deny_unknown_fields)]
7#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
8pub struct SimParams {
10 #[serde(default = "SimParams::def_ach_speed_max_iter")]
11 pub ach_speed_max_iter: u32,
14 #[serde(default = "SimParams::def_ach_speed_tol")]
15 pub ach_speed_tol: si::Ratio,
18 #[serde(default = "SimParams::def_ach_speed_solver_gain")]
19 pub ach_speed_solver_gain: f64,
21 #[serde(default = "SimParams::def_trace_miss_tol")]
25 pub trace_miss_tol: TraceMissTolerance,
26 #[serde(default = "SimParams::def_trace_miss_opts")]
27 pub trace_miss_opts: TraceMissOptions,
28 #[serde(default = "SimParams::def_trace_miss_correct_max_steps")]
29 pub trace_miss_correct_max_steps: u32,
34 #[serde(default = "SimParams::def_f2_const_air_density")]
36 pub f2_const_air_density: bool,
37 pub ambient_thermal_soak: bool,
39}
40
41#[pyo3_api]
42impl SimParams {
43 #[staticmethod]
44 #[pyo3(name = "default")]
45 fn default_py() -> Self {
46 Self::default()
47 }
48}
49
50impl SimParams {
51 fn def_ach_speed_max_iter() -> u32 {
52 Self::default().ach_speed_max_iter
53 }
54 fn def_ach_speed_tol() -> si::Ratio {
55 Self::default().ach_speed_tol
56 }
57 fn def_ach_speed_solver_gain() -> f64 {
58 Self::default().ach_speed_solver_gain
59 }
60 fn def_trace_miss_tol() -> TraceMissTolerance {
61 Self::default().trace_miss_tol
62 }
63 fn def_trace_miss_opts() -> TraceMissOptions {
64 Self::default().trace_miss_opts
65 }
66 fn def_trace_miss_correct_max_steps() -> u32 {
67 Self::default().trace_miss_correct_max_steps
68 }
69 fn def_f2_const_air_density() -> bool {
70 Self::default().f2_const_air_density
71 }
72}
73
74impl SerdeAPI for SimParams {}
75impl Init for SimParams {}
76
77impl Default for SimParams {
78 fn default() -> Self {
79 Self {
80 ach_speed_max_iter: 3,
81 ach_speed_tol: 1.0e-3 * uc::R,
82 ach_speed_solver_gain: 0.9,
83 trace_miss_tol: Default::default(),
84 trace_miss_opts: Default::default(),
85 trace_miss_correct_max_steps: 6,
86 f2_const_air_density: true,
87 ambient_thermal_soak: false,
88 }
89 }
90}
91
92#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
93#[serde(deny_unknown_fields)]
94#[non_exhaustive]
95pub struct TraceMissTolerance {
96 pub tol_dist: si::Length,
99 pub tol_dist_frac: si::Ratio,
102 pub tol_speed: si::Velocity,
105 pub tol_speed_frac: si::Ratio,
108}
109
110impl TraceMissTolerance {
111 pub fn check_trace_miss(
112 &self,
113 cyc_speed: si::Velocity,
114 ach_speed: si::Velocity,
115 cyc_dist: si::Length,
116 ach_dist: si::Length,
117 ) -> anyhow::Result<()> {
118 ensure!(
119 (cyc_speed - ach_speed).abs() < self.tol_speed,
120 concat!(
121 "trace miss: achieved speed misses prescribed speed\n",
122 " achieved speed: {:?}\n",
123 " prescribed speed: {:?}\n",
124 " exceeds allowed tolerance: {:?}",
125 ),
126 ach_speed,
127 cyc_speed,
128 self.tol_speed,
129 );
130 if cyc_speed > self.tol_speed {
132 ensure!(
133 (cyc_speed - ach_speed).abs() / cyc_speed < self.tol_speed_frac,
134 concat!(
135 "trace miss: achieved speed misses prescribed speed (fractional)\n",
136 " achieved speed: {:?}\n",
137 " prescribed speed: {:?}\n",
138 " exceeds allowed fractional tolerance: {:?}",
139 ),
140 ach_speed,
141 cyc_speed,
142 self.tol_speed_frac
143 )
144 }
145 ensure!(
146 (cyc_dist - ach_dist).abs() < self.tol_dist,
147 concat!(
148 "trace miss: achieved distance misses prescribed distance\n",
149 " achieved distance: {:?}\n",
150 " prescribed distance: {:?}\n",
151 " exceeds allowed tolerance: {:?}",
152 ),
153 ach_dist,
154 cyc_dist,
155 self.tol_dist
156 );
157 if cyc_dist > self.tol_dist * 5.0 {
159 ensure!(
160 (cyc_dist - ach_dist).abs() / cyc_dist < self.tol_dist_frac,
161 concat!(
162 "trace miss: achieved distance misses prescribed distance (fractional)\n",
163 " achieved distance: {:?}\n",
164 " prescribed distance: {:?}\n",
165 " exceeds allowed fractional tolerance: {:?}",
166 ),
167 ach_dist,
168 cyc_dist,
169 self.tol_dist_frac
170 )
171 }
172
173 Ok(())
174 }
175}
176impl SerdeAPI for TraceMissTolerance {}
177impl Init for TraceMissTolerance {}
178impl Default for TraceMissTolerance {
179 fn default() -> Self {
180 Self {
181 tol_dist: 100. * uc::M,
182 tol_dist_frac: 0.05 * uc::R,
183 tol_speed: 10. * uc::MPS,
184 tol_speed_frac: 0.5 * uc::R,
185 }
186 }
187}
188
189#[derive(
190 Clone, Default, Debug, Deserialize, Serialize, PartialEq, IsVariant, derive_more::From, TryInto,
191)]
192pub enum TraceMissOptions {
193 Allow,
195 AllowChecked,
197 #[default]
202 Error,
205 Correct,
207}
208
209impl SerdeAPI for TraceMissOptions {}
210impl Init for TraceMissOptions {}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 #[cfg(feature = "resources")]
218 #[cfg(feature = "yaml")]
219 fn test_trace_miss_allow() {
220 let mut veh =
221 crate::vehicle::Vehicle::from_resource("2012_Ford_Fusion.yaml", false).unwrap();
222 veh.mass = Some(10000.0 * uc::KG);
223 let params = SimParams {
224 trace_miss_opts: TraceMissOptions::Allow,
225 ..Default::default()
226 };
227 let cyc = crate::drive_cycle::CYC_ACCEL.clone();
228 let mut sim = SimDrive::new(veh, cyc, Some(params));
229 assert!(sim.run().is_ok());
230 }
231
232 #[test]
233 #[cfg(feature = "resources")]
234 #[cfg(feature = "yaml")]
235 fn test_trace_miss_allowchecked() {
236 let mut veh =
237 crate::vehicle::Vehicle::from_resource("2012_Ford_Fusion.yaml", false).unwrap();
238 veh.mass = Some(10000.0 * uc::KG);
239 let cyc = crate::drive_cycle::CYC_ACCEL.clone();
240 let params = SimParams {
242 trace_miss_opts: TraceMissOptions::AllowChecked,
243 ..Default::default()
244 };
245 let mut sim = SimDrive::new(veh.clone(), cyc.clone(), Some(params));
246 assert!(sim.run().is_err());
247 let params = SimParams {
249 trace_miss_opts: TraceMissOptions::AllowChecked,
250 trace_miss_tol: TraceMissTolerance {
251 tol_dist: 1e6 * uc::M,
252 tol_dist_frac: 10.0 * uc::R,
253 tol_speed: *cyc.speed.max().unwrap(),
254 tol_speed_frac: 1.0 * uc::R,
255 },
256 ..Default::default()
257 };
258 let mut sim = SimDrive::new(veh.clone(), cyc.clone(), Some(params));
259 sim.run().unwrap();
260 let params = SimParams {
262 trace_miss_opts: TraceMissOptions::AllowChecked,
263 trace_miss_tol: TraceMissTolerance {
264 tol_dist_frac: 10.0 * uc::R,
265 tol_speed: *cyc.speed.max().unwrap(),
266 tol_speed_frac: 1.0 * uc::R,
267 ..Default::default()
268 },
269 ..Default::default()
270 };
271 let mut sim = SimDrive::new(veh.clone(), cyc.clone(), Some(params));
272 assert!(sim.run().is_err());
273 let params = SimParams {
275 trace_miss_opts: TraceMissOptions::AllowChecked,
276 trace_miss_tol: TraceMissTolerance {
277 tol_dist: 100. * uc::M,
278 tol_dist_frac: 0.05 * uc::R,
279 tol_speed_frac: 0.5 * uc::R,
280 ..Default::default()
281 },
282 ..Default::default()
283 };
284 let mut sim = SimDrive::new(veh.clone(), cyc.clone(), Some(params));
285 assert!(sim.run().is_err());
286 let params = SimParams {
288 trace_miss_opts: TraceMissOptions::AllowChecked,
289 trace_miss_tol: TraceMissTolerance {
290 tol_dist: 100. * uc::M,
291 tol_dist_frac: 0.05 * uc::R,
292 tol_speed: 10. * uc::MPS,
293 ..Default::default()
294 },
295 ..Default::default()
296 };
297 let mut sim = SimDrive::new(veh.clone(), cyc.clone(), Some(params));
298 assert!(sim.run().is_err());
299 }
300
301 #[test]
318 #[cfg(feature = "resources")]
319 #[cfg(feature = "yaml")]
320 fn test_trace_miss_error() {
321 let mut veh =
322 crate::vehicle::Vehicle::from_resource("2012_Ford_Fusion.yaml", false).unwrap();
323 veh.mass = Some(10000.0 * uc::KG);
324 let params = SimParams {
325 trace_miss_opts: TraceMissOptions::Error,
326 ..Default::default()
327 };
328 let cyc = crate::drive_cycle::CYC_ACCEL.clone();
329 let mut sim = SimDrive::new(veh, cyc, Some(params));
330 assert!(sim.run().is_err());
331 }
332
333 #[test]
335 #[cfg(feature = "resources")]
336 #[cfg(feature = "yaml")]
337 fn test_trace_miss_correct() {
338 let mut veh =
339 crate::vehicle::Vehicle::from_resource("2012_Ford_Fusion.yaml", false).unwrap();
340 veh.mass = Some(10000.0 * uc::KG);
341 let params = SimParams {
342 trace_miss_opts: TraceMissOptions::Correct,
343 ..Default::default()
344 };
345 let cyc = crate::drive_cycle::CYC_ACCEL.clone();
346 let mut sim = SimDrive::new(veh, cyc, Some(params));
347 sim.run().unwrap();
348 }
349}