fmi_schema/fmi2/
model_description.rs

1use yaserde_derive::{YaDeserialize, YaSerialize};
2
3use crate::{Error, default_wrapper, traits::FmiModelDescription};
4
5use super::{
6    CoSimulation, Fmi2Unit, Fmi2VariableDependency, ModelExchange, ScalarVariable, SimpleType,
7};
8
9#[derive(Default, Debug, YaSerialize, YaDeserialize)]
10pub struct Fmi2ModelDescription {
11    /// Version of FMI (Clarification for FMI 2.0.2: for FMI 2.0.x revisions fmiVersion is defined
12    /// as "2.0").
13    #[yaserde(attribute = true, rename = "fmiVersion")]
14    pub fmi_version: String,
15
16    /// The name of the model as used in the modeling environment that generated the XML file, such
17    /// as Modelica.Mechanics.Rotational.Examples.CoupledClutches.
18    #[yaserde(attribute = true, rename = "modelName")]
19    pub model_name: String,
20
21    /// Fingerprint of xml-file content to verify that xml-file and C-functions are compatible to
22    /// each other
23    #[yaserde(attribute = true)]
24    pub guid: String,
25
26    #[yaserde(attribute = true)]
27    pub description: Option<String>,
28
29    /// Version of FMU, e.g., "1.4.1"
30    #[yaserde(attribute = true)]
31    pub version: Option<String>,
32
33    /// Information on intellectual property copyright for this FMU, such as “© MyCompany 2011“
34    #[yaserde(attribute = true)]
35    pub copyright: Option<String>,
36
37    /// Information on intellectual property licensing for this FMU, such as “BSD license”,
38    /// "Proprietary", or "Public Domain"
39    #[yaserde(attribute = true)]
40    pub license: Option<String>,
41
42    /// Name of the tool that generated the XML file.
43    #[yaserde(attribute = true, rename = "generationTool")]
44    pub generation_tool: Option<String>,
45
46    /// time/date of database creation according to ISO 8601 (preference: YYYY-MM-DDThh:mm:ss)
47    /// Date and time when the XML file was generated. The format is a subset of dateTime and
48    /// should be: YYYY-MM-DDThh:mm:ssZ (with one T between date and time; Z characterizes the
49    /// Zulu time zone, in other words, Greenwich meantime) [for example 2009-12-08T14:33:22Z].
50    #[yaserde(attribute = true, rename = "generationDateAndTime")]
51    pub generation_date_and_time: Option<String>,
52
53    /// Defines whether the variable names in <ModelVariables> and in <TypeDefinitions> follow a
54    /// particular convention.
55    #[yaserde(attribute = true, rename = "variableNamingConvention")]
56    pub variable_naming_convention: Option<String>,
57
58    #[yaserde(attribute = true, rename = "numberOfEventIndicators")]
59    pub number_of_event_indicators: u32,
60
61    /// If present, the FMU is based on FMI for Model Exchange
62    #[yaserde(rename = "ModelExchange")]
63    pub model_exchange: Option<ModelExchange>,
64
65    /// If present, the FMU is based on FMI for Co-Simulation
66    #[yaserde(rename = "CoSimulation")]
67    pub co_simulation: Option<CoSimulation>,
68
69    #[yaserde(rename = "LogCategories")]
70    pub log_categories: Option<LogCategories>,
71
72    #[yaserde(rename = "DefaultExperiment")]
73    pub default_experiment: Option<DefaultExperiment>,
74
75    #[yaserde(rename = "UnitDefinitions")]
76    pub unit_definitions: Option<UnitDefinitions>,
77
78    #[yaserde(rename = "TypeDefinitions")]
79    pub type_definitions: Option<TypeDefinitions>,
80
81    #[yaserde(rename = "ModelVariables")]
82    pub model_variables: ModelVariables,
83
84    #[yaserde(rename = "ModelStructure")]
85    pub model_structure: ModelStructure,
86}
87
88impl Fmi2ModelDescription {
89    /// Total number of variables
90    pub fn num_variables(&self) -> usize {
91        self.model_variables.variables.len()
92    }
93
94    /// Get the number of continuous states (and derivatives)
95    pub fn num_states(&self) -> usize {
96        self.model_structure.derivatives.unknowns.len()
97    }
98
99    pub fn num_event_indicators(&self) -> usize {
100        self.number_of_event_indicators as usize
101    }
102
103    /// Get a iterator of the SalarVariables
104    pub fn get_model_variables(&self) -> impl Iterator<Item = &ScalarVariable> {
105        self.model_variables.variables.iter()
106    }
107
108    #[cfg(false)]
109    pub fn get_model_variable_by_vr(&self, vr: u32) -> Option<&ScalarVariable> {
110        self.model_variables.map.get(&vr)
111    }
112
113    /// Turns an UnknownList into a nested Vector of ScalarVariables and their Dependencies
114    #[cfg(false)]
115    fn map_unknowns(
116        &self,
117        list: &UnknownList,
118    ) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
119        list.unknowns
120            .iter()
121            .map(|unknown| {
122                self.model_variables
123                    .by_index
124                    // Variable indices start at 1 in the modelDescription
125                    .get(unknown.index as usize - 1)
126                    .map(|vr| &self.model_variables.map[vr])
127                    .ok_or_else(|| {
128                        ModelDescriptionError::VariableAtIndexNotFound(
129                            self.model_name.clone(),
130                            unknown.index as usize,
131                        )
132                    })
133                    .and_then(|var| {
134                        let deps = unknown
135                            .dependencies
136                            .iter()
137                            .map(|dep| {
138                                self.model_variables
139                                    .by_index
140                                    .get(*dep as usize - 1)
141                                    .map(|vr| &self.model_variables.map[vr])
142                                    .ok_or_else(|| {
143                                        ModelDescriptionError::VariableAtIndexNotFound(
144                                            self.model_name.clone(),
145                                            *dep as usize,
146                                        )
147                                    })
148                            })
149                            .collect::<Result<Vec<_>, ModelDescriptionError>>()?;
150
151                        Ok((var, deps))
152                    })
153            })
154            .collect()
155    }
156
157    /// Get a reference to the vector of Unknowns marked as outputs
158    #[cfg(false)]
159    pub fn outputs(&self) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
160        self.map_unknowns(&self.model_structure.outputs)
161    }
162
163    /// Get a reference to the vector of Unknowns marked as derivatives
164    #[cfg(false)]
165    pub fn derivatives(&self) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
166        self.map_unknowns(&self.model_structure.derivatives)
167    }
168
169    /// Get a reference to the vector of Unknowns marked as initial_unknowns
170    #[cfg(false)]
171    pub fn initial_unknowns(&self) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
172        self.map_unknowns(&self.model_structure.initial_unknowns)
173    }
174
175    /// Get a reference to the model variable with the given name
176    pub fn model_variable_by_name(&self, name: &str) -> Result<&ScalarVariable, Error> {
177        self.model_variables
178            .variables
179            .iter()
180            .find(|var| var.name == name)
181            .ok_or_else(|| Error::VariableNotFound(name.to_owned()))
182    }
183
184    /// This private function is used to de-reference variable indices from the UnknownList and
185    /// Real{derivative}
186    #[cfg(false)]
187    fn model_variable_by_index(
188        &self,
189        idx: usize,
190    ) -> Result<&ScalarVariable, ModelDescriptionError> {
191        self.model_variables
192            .by_index
193            .get(idx - 1)
194            .map(|vr| &self.model_variables.map[vr])
195            .ok_or_else(|| {
196                ModelDescriptionError::VariableAtIndexNotFound(
197                    self.model_name.clone(),
198                    idx as usize,
199                )
200            })
201    }
202
203    /// Return a vector of tuples `(&ScalarVariable, &ScalarVariabel)`, where the 1st is a
204    /// continuous-time state, and the 2nd is its derivative.
205    #[cfg(false)]
206    pub fn continuous_states(
207        &self,
208    ) -> Result<Vec<(&ScalarVariable, &ScalarVariable)>, ModelDescriptionError> {
209        self.model_structure
210            .derivatives
211            .unknowns
212            .iter()
213            .map(|unknown| {
214                self.model_variable_by_index(unknown.index as usize)
215                    .and_then(|der| {
216                        if let ScalarVariableElement::Real { derivative, .. } = der.elem {
217                            derivative
218                                .ok_or_else(|| {
219                                    ModelDescriptionError::VariableDerivativeMissing(
220                                        der.name.clone(),
221                                    )
222                                })
223                                .and_then(|der_idx| {
224                                    self.model_variable_by_index(der_idx as usize)
225                                        .map(|state| (state, der))
226                                })
227                        } else {
228                            Err(ModelDescriptionError::VariableTypeMismatch(
229                                ScalarVariableElementBase::Real,
230                                ScalarVariableElementBase::from(&der.elem),
231                            ))
232                        }
233                    })
234            })
235            .collect()
236    }
237}
238
239impl FmiModelDescription for Fmi2ModelDescription {
240    fn model_name(&self) -> &str {
241        &self.model_name
242    }
243
244    fn version_string(&self) -> &str {
245        &self.fmi_version
246    }
247}
248
249#[derive(Clone, Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
250pub struct LogCategories {
251    #[yaserde(rename = "Category")]
252    pub categories: Vec<Category>,
253}
254
255#[derive(Clone, Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
256pub struct Category {
257    #[yaserde(attribute = true)]
258    pub name: String,
259    #[yaserde(attribute = true)]
260    pub description: String,
261}
262
263#[derive(Clone, Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
264pub struct DefaultExperiment {
265    #[yaserde(attribute = true, default = "default_start_time", rename = "startTime")]
266    pub start_time: f64,
267    #[yaserde(attribute = true, default = "default_stop_time", rename = "stopTime")]
268    pub stop_time: f64,
269    #[yaserde(attribute = true, default = "default_tolerance", rename = "tolerance")]
270    pub tolerance: f64,
271}
272
273const fn default_start_time() -> f64 {
274    0.0
275}
276
277const fn default_stop_time() -> f64 {
278    10.0
279}
280const fn default_tolerance() -> f64 {
281    1e-3
282}
283
284#[derive(Default, Debug, YaSerialize, YaDeserialize)]
285pub struct UnitDefinitions {
286    #[yaserde(rename = "Unit")]
287    pub units: Vec<Fmi2Unit>,
288}
289
290#[derive(Default, Debug, YaSerialize, YaDeserialize)]
291pub struct TypeDefinitions {
292    #[yaserde(rename = "SimpleType")]
293    pub types: Vec<SimpleType>,
294}
295
296#[derive(Default, Debug, YaSerialize, YaDeserialize)]
297pub struct ModelVariables {
298    #[yaserde(rename = "ScalarVariable")]
299    pub variables: Vec<ScalarVariable>,
300}
301
302#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
303#[yaserde(rename = "ModelStructure")]
304pub struct ModelStructure {
305    #[yaserde(rename = "Outputs")]
306    pub outputs: UnknownList,
307
308    #[yaserde(rename = "Derivatives", default = "default_wrapper")]
309    pub derivatives: UnknownList,
310
311    #[yaserde(rename = "InitialUnknowns", default = "default_wrapper")]
312    pub initial_unknowns: UnknownList,
313}
314
315#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
316pub struct UnknownList {
317    #[yaserde(rename = "Unknown")]
318    pub unknowns: Vec<Fmi2VariableDependency>,
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    #[test]
326    fn test_model_description() {
327        let s = r##"<?xml version="1.0" encoding="UTF8"?>
328<fmiModelDescription
329 fmiVersion="2.0"
330 modelName="MyLibrary.SpringMassDamper"
331 guid="{8c4e810f-3df3-4a00-8276-176fa3c9f9e0}"
332 description="Rotational Spring Mass Damper System"
333 version="1.0"
334 generationDateAndTime="2011-09-23T16:57:33Z"
335 variableNamingConvention="structured"
336 numberOfEventIndicators="2">
337 <ModelVariables>
338    <ScalarVariable name="x[1]" valueReference="0" initial="exact"> <Real/> </ScalarVariable> <!-- idex="5" -->
339    <ScalarVariable name="x[2]" valueReference="1" initial="exact"> <Real/> </ScalarVariable> <!-- index="6" -->
340    <ScalarVariable name="PI.x" valueReference="46" description="State of block" causality="local" variability="continuous" initial="calculated">
341        <Real relativeQuantity="false" />
342    </ScalarVariable>
343    <ScalarVariable name="der(PI.x)" valueReference="45" causality="local" variability="continuous" initial="calculated">
344        <Real relativeQuantity="false" derivative="3" />
345    </ScalarVariable>
346 </ModelVariables>
347 <ModelStructure>
348    <Outputs><Unknown index="1" dependencies="1 2" /><Unknown index="2" /></Outputs>
349    <Derivatives><Unknown index="4" dependencies="1 2" /></Derivatives>
350    <InitialUnknowns />
351</ModelStructure>
352</fmiModelDescription>"##;
353        let md: Fmi2ModelDescription = yaserde::de::from_str(s).unwrap();
354        assert_eq!(md.fmi_version, "2.0");
355        assert_eq!(md.model_name, "MyLibrary.SpringMassDamper");
356        assert_eq!(md.guid, "{8c4e810f-3df3-4a00-8276-176fa3c9f9e0}");
357        assert_eq!(
358            md.description.as_deref(),
359            Some("Rotational Spring Mass Damper System")
360        );
361        assert_eq!(md.version.as_deref(), Some("1.0"));
362        // assert_eq!(x.generation_date_and_time, chrono::DateTime<chrono::Utc>::from)
363        assert_eq!(md.variable_naming_convention, Some("structured".to_owned()));
364        assert_eq!(md.number_of_event_indicators, 2);
365        assert_eq!(md.model_variables.variables.len(), 4);
366
367        let outputs = &md.model_structure.outputs.unknowns;
368        assert_eq!(outputs.len(), 2);
369        assert_eq!(outputs[0].index, 1);
370        assert_eq!(outputs[0].dependencies, vec![1, 2]);
371        assert_eq!(outputs[1].index, 2);
372        assert!(outputs[1].dependencies.is_empty());
373
374        let derivatives = &md.model_structure.derivatives.unknowns;
375        assert_eq!(derivatives.len(), 1);
376        assert_eq!(derivatives[0].index, 4);
377        assert_eq!(derivatives[0].dependencies, vec![1, 2]);
378    }
379}