1use std::{
2 collections::HashMap,
3 fs,
4 hash::{Hash, Hasher},
5 path::Path,
6};
7
8use quick_xml::{de::from_str, DeError};
9use serde::{Deserialize, Deserializer};
10
11#[derive(Debug, PartialEq, Default, Deserialize)]
12#[serde(default, rename_all = "PascalCase")]
13#[allow(non_snake_case)]
14pub struct BaseUnit {
15 #[serde(rename = "@kg")]
16 pub kg: Option<i32>,
17 #[serde(rename = "@m")]
18 pub m: Option<i32>,
19 #[serde(rename = "@s")]
20 pub s: Option<i32>,
21 #[serde(rename = "@A")]
22 pub A: Option<i32>,
23 #[serde(rename = "@K")]
24 pub K: Option<i32>,
25 #[serde(rename = "@mol")]
26 pub mol: Option<i32>,
27 #[serde(rename = "@cd")]
28 pub cd: Option<i32>,
29 #[serde(rename = "@rad")]
30 pub rad: Option<i32>,
31 #[serde(rename = "@factor")]
32 pub factor: Option<f64>,
33 #[serde(rename = "@offset")]
34 pub offset: Option<f64>,
35}
36
37#[derive(Debug, PartialEq, Default, Deserialize)]
38#[serde(default, rename_all = "PascalCase")]
39pub struct DisplayUnit {
40 #[serde(rename = "@name")]
41 pub name: String,
42 #[serde(rename = "@factor")]
43 pub factor: Option<f64>,
44 #[serde(rename = "@offset")]
45 pub offset: Option<f64>,
46}
47
48#[derive(Debug, PartialEq, Default, Deserialize)]
49#[serde(default, rename_all = "PascalCase")]
50pub struct Unit {
51 #[serde(rename = "@name")]
52 pub name: String,
53 pub base_unit: Option<BaseUnit>,
55 pub display_unit: Vec<DisplayUnit>,
56 #[serde(rename = "@offset")]
57 pub offset: f64,
58}
59
60#[derive(Debug, PartialEq, Default, Deserialize)]
61#[serde(default, rename_all = "PascalCase")]
62pub struct UnitDefinitions {
63 pub unit: Vec<Unit>,
64}
65
66#[derive(Debug, PartialEq, Default, Deserialize)]
67#[serde(rename_all = "PascalCase")]
68pub struct Real {
69 #[serde(rename = "@declaredType")]
70 declared_type: Option<String>,
71 #[serde(rename = "@start")]
72 start: Option<f64>,
73 #[serde(rename = "@derivative")]
74 derivative: Option<usize>,
75 #[serde(rename = "@reinit")]
76 reinit: Option<bool>,
77}
78
79#[derive(Debug, PartialEq, Default, Deserialize)]
80#[serde(rename_all = "PascalCase")]
81pub struct Boolean {
82 #[serde(rename = "@declaredType")]
83 declared_type: Option<String>,
84 #[serde(rename = "@start")]
85 start: Option<bool>,
86}
87
88#[derive(Debug, PartialEq, Default, Deserialize)]
89#[serde(rename_all = "PascalCase")]
90pub struct Integer {
91 #[serde(rename = "@declaredType")]
92 declared_type: Option<String>,
93 #[serde(rename = "@start")]
94 start: Option<i64>,
95}
96
97#[derive(Debug, Deserialize)]
98#[serde(rename_all = "PascalCase")]
99pub enum SignalType {
100 Real(Real),
101 Integer(Integer),
102 Boolean(Boolean),
103 String,
104 Enumeration,
105}
106
107#[derive(Debug, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub enum Causality {
110 Parameter,
111 CalculatedParameter,
112 Input,
113 Output,
114 Local,
115 Independent,
116}
117
118impl Default for Causality {
119 fn default() -> Self {
120 Causality::Local
121 }
122}
123
124#[derive(Debug, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub enum Variability {
127 Constant,
128 Fixed,
129 Tunable,
130 Discrete,
131 Continuous,
132}
133
134impl Default for Variability {
135 fn default() -> Self {
136 Variability::Continuous
137 }
138}
139
140#[derive(Debug, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub enum Initial {
143 Exact,
144 Approx,
145 Calculated,
146}
147
148#[derive(Debug, Deserialize)]
149#[serde(rename_all = "PascalCase")]
150pub struct ScalarVariable {
151 #[serde(rename = "@name")]
152 pub name: String,
153 #[serde(rename = "@valueReference")]
154 pub value_reference: ::std::os::raw::c_uint,
155 #[serde(default, rename = "@description")]
156 pub description: String,
157 #[serde(default, rename = "@causality")]
158 pub causality: Causality,
159 #[serde(default, rename = "@variability")]
160 pub variability: Variability,
161 #[serde(rename = "@initial")]
162 pub initial: Option<Initial>,
163 #[serde(rename = "@canHandleMultipleSetPerTimeInstant")]
164 pub can_handle_multiple_set_per_time_instant: Option<bool>,
165 pub annotations: Option<()>,
166 #[serde(rename = "$value")]
167 pub signal_type: SignalType,
168}
169
170impl PartialEq for ScalarVariable {
171 fn eq(&self, other: &Self) -> bool {
172 self.name == other.name
173 }
174}
175impl Eq for ScalarVariable {}
176
177impl Hash for ScalarVariable {
178 fn hash<H: Hasher>(&self, state: &mut H) {
179 self.name.hash(state);
180 }
181}
182
183fn deserialize_to_map<'de, D>(deserializer: D) -> Result<HashMap<String, ScalarVariable>, D::Error>
184where
185 D: Deserializer<'de>,
186{
187 let v = Vec::<ScalarVariable>::deserialize(deserializer)?;
188 let mut map = HashMap::new();
189 for item in v {
190 map.insert(item.name.clone(), item);
191 }
192 Ok(map)
193}
194
195#[derive(Debug, PartialEq, Default, Deserialize)]
196#[serde(default, rename_all = "PascalCase")]
197pub struct ModelVariables {
198 #[serde(deserialize_with = "deserialize_to_map")]
199 pub scalar_variable: HashMap<String, ScalarVariable>,
200}
201
202#[derive(Debug, PartialEq, Default, Deserialize)]
203#[serde(default, rename_all = "PascalCase")]
204pub struct FMIFile {
205 #[serde(rename = "@name")]
206 pub name: String,
207}
208
209#[derive(Debug, PartialEq, Default, Deserialize)]
210#[serde(default, rename_all = "PascalCase")]
211pub struct FMISourceFiles {
212 #[serde(rename = "@name")]
213 pub file: Vec<FMIFile>,
214}
215
216#[derive(Debug, PartialEq, Default, Deserialize)]
217#[serde(default, rename_all = "PascalCase")]
218pub struct Category {
219 #[serde(rename = "@name")]
220 pub name: String,
221 #[serde(rename = "@description")]
222 pub description: String,
223}
224
225#[derive(Debug, PartialEq, Default, Deserialize)]
226#[serde(default, rename_all = "PascalCase")]
227pub struct LogCategories {
228 pub category: Vec<Category>,
229}
230
231#[derive(Debug, PartialEq, Default, Deserialize)]
232#[serde(default, rename_all = "PascalCase")]
233pub struct ModelExchange {
234 pub source_files: FMISourceFiles,
235 #[serde(rename = "@modelIdentifier")]
236 pub model_identifier: String,
237 #[serde(rename = "@needsExecutionTool")]
238 pub needs_execution_tool: bool,
239 #[serde(rename = "@completedIntegratorStepNotNeeded")]
240 pub completed_integrator_step_not_needed: bool,
241 #[serde(rename = "@canBeInstantiatedOnlyOncePerProcess")]
242 pub can_be_instantiated_only_once_per_process: bool,
243 #[serde(rename = "@canNotUseMemoryManagementFunctions")]
244 pub can_not_use_memory_management_functions: bool,
245 #[serde(rename = "@canGetAndSetFMUstate")]
246 pub can_get_and_set_fmustate: bool,
247 #[serde(rename = "@canSerializeFMUstate")]
248 pub can_serialize_fmustate: bool,
249 #[serde(rename = "@providesDirectionalDerivative")]
250 pub provides_directional_derivative: bool,
251}
252
253#[derive(Debug, PartialEq, Default, Deserialize)]
254#[serde(default, rename_all = "PascalCase")]
255pub struct CoSimulation {
256 pub source_files: FMISourceFiles,
257 #[serde(rename = "@modelIdentifier")]
258 pub model_identifier: String,
259 #[serde(rename = "@needsExecutionTool")]
260 pub needs_execution_tool: bool,
261 #[serde(rename = "@canHandleVariableCommunicationStepSize")]
262 pub can_handle_variable_communication_step_size: bool,
263 #[serde(rename = "@canInterpolateInputs")]
264 pub can_interpolate_inputs: bool,
265 #[serde(rename = "@maxOutputDerivativeOrder")]
266 pub max_output_derivative_order: bool,
267 #[serde(rename = "@canRunAsynchronuously")]
268 pub can_run_asynchronuously: bool,
269 #[serde(rename = "@canBeInstantiatedOnlyOncePerProcess")]
270 pub can_be_instantiated_only_once_per_process: bool,
271 #[serde(rename = "@canNotUseMemoryManagementFunctions")]
272 pub can_not_use_memory_management_functions: bool,
273 #[serde(rename = "@canGetAndSetFMUstate")]
274 pub can_get_and_set_fmustate: bool,
275 #[serde(rename = "@canSerializeFMUstate")]
276 pub can_serialize_fmustate: bool,
277 #[serde(rename = "@providesDirectionalDerivative")]
278 pub provides_directional_derivative: bool,
279}
280
281#[derive(Debug, PartialEq, Default, Deserialize)]
282#[serde(default, rename_all = "PascalCase")]
283pub struct DefaultExperiment {
284 #[serde(rename = "@startTime")]
285 pub start_time: f64,
286 #[serde(rename = "@stopTime")]
287 pub stop_time: f64,
288 #[serde(rename = "@tolerance")]
289 pub tolerance: f64,
290 #[serde(rename = "@stepSize")]
291 pub step_size: Option<f64>,
292}
293
294#[derive(Debug, PartialEq, Default, Deserialize)]
295#[serde(default, rename_all = "PascalCase")]
296pub struct FmiModelDescription {
297 pub co_simulation: Option<CoSimulation>,
298 pub model_exchange: Option<ModelExchange>,
299 pub model_variables: ModelVariables,
300 pub unit_definitions: Option<UnitDefinitions>,
301 pub log_categories: Option<LogCategories>,
302 pub default_experiment: Option<DefaultExperiment>,
303 #[serde(rename = "@fmiVersion")]
307 pub fmi_version: String,
308 #[serde(rename = "@modelName")]
309 pub model_name: String,
310 #[serde(rename = "@guid")]
311 pub guid: String,
312 #[serde(rename = "@description")]
313 pub description: String,
314 #[serde(rename = "@author")]
315 pub author: String,
316 #[serde(rename = "@version")]
317 pub version: String,
318 #[serde(rename = "@copyright")]
319 pub copyright: String,
320 #[serde(rename = "@license")]
321 pub license: String,
322 #[serde(rename = "@generationTool")]
323 pub generation_tool: String,
324 #[serde(rename = "@generationDateAndTime")]
325 pub generation_date_and_time: String,
326 #[serde(rename = "@variableNamingConvention")]
327 pub variable_naming_convention: String,
328 #[serde(rename = "@numberOfEventIndicators")]
329 pub number_of_event_indicators: String,
330}
331
332impl FmiModelDescription {
333 pub fn new(path: &Path) -> Result<Self, DeError> {
334 let text = fs::read_to_string(path).unwrap();
335 from_str(&text)
336 }
337}
338
339#[cfg(test)]
341mod tests {
342 use super::*;
343 use rstest::rstest;
344
345 #[rstest]
346 #[case("./tests/parsing/unit-test.xml")]
347 #[case("./tests/parsing/complex-fmi.xml")]
348 #[case("./tests/parsing/bouncing-ball.xml")]
349 fn test_parsing_model_description(#[case] xml: &str) {
350 let text = fs::read_to_string(xml).unwrap();
351 let md: FmiModelDescription = from_str(&text).unwrap();
352
353 println!("{:?}", md.description);
354 println!("{:?}", md.default_experiment);
355 println!("{:?}", md.model_variables);
356 println!("{:?}", md.model_variables.scalar_variable);
357 }
358}