Skip to main content

fmi_schema/fmi3/
model_description.rs

1use crate::{Error, fmi3::Fmi3Unknown, traits::FmiModelDescription};
2
3use super::{
4    Annotations, Fmi3CoSimulation, Fmi3ModelExchange, Fmi3ScheduledExecution, Fmi3Unit,
5    ModelVariables, TypeDefinitions, VariableDependency,
6};
7
8#[derive(Default, Debug, PartialEq, hard_xml::XmlRead, hard_xml::XmlWrite)]
9#[xml(
10    tag = "fmiModelDescription",
11    strict(unknown_attribute, unknown_element)
12)]
13pub struct Fmi3ModelDescription {
14    /// Version of FMI that was used to generate the XML file.
15    #[xml(attr = "fmiVersion")]
16    pub fmi_version: String,
17
18    /// The name of the model as used in the modeling environment that generated the XML file, such
19    /// as "Modelica.Mechanics.Rotational.Examples.CoupledClutches".
20    #[xml(attr = "modelName")]
21    pub model_name: String,
22
23    /// The instantiationToken is a string that can be used by the FMU to check that the XML file
24    /// is compatible with the implementation of the FMU.
25    #[xml(attr = "instantiationToken")]
26    pub instantiation_token: String,
27
28    /// Optional string with a brief description of the model.
29    #[xml(attr = "description")]
30    pub description: Option<String>,
31
32    /// String with the name and organization of the model author.
33    #[xml(attr = "author")]
34    pub author: Option<String>,
35
36    /// Version of the model [for example 1.0].
37    #[xml(attr = "version")]
38    pub version: Option<String>,
39
40    /// Information on the intellectual property copyright for this FMU [for example © My Company
41    /// 2011].
42    #[xml(attr = "copyright")]
43    pub copyright: Option<String>,
44
45    /// Information on the intellectual property licensing for this FMU (for example BSD license
46    /// `<license text or link to license>`).
47    #[xml(attr = "license")]
48    pub license: Option<String>,
49
50    /// Name of the tool that generated the XML file.
51    #[xml(attr = "generationTool")]
52    pub generation_tool: Option<String>,
53
54    ///  Date and time when the XML file was generated. The format is a subset of dateTime and
55    /// should be: YYYY-MM-DDThh:mm:ssZ (with one T between date and time; Z characterizes the Zulu
56    /// time zone, in other words, Greenwich meantime) [for example 2009-12-08T14:33:22Z].
57    #[xml(attr = "generationDateAndTime")]
58    pub generation_date_and_time: Option<String>,
59
60    /// Defines whether the variable names in `ModelVariables` and in `TypeDefinitions` follow a
61    /// particular convention.
62    #[xml(attr = "variableNamingConvention")]
63    pub variable_naming_convention: Option<String>,
64
65    /// If present, the FMU is based on FMI for Model Exchange
66    #[xml(child = "ModelExchange")]
67    pub model_exchange: Option<Fmi3ModelExchange>,
68
69    /// If present, the FMU is based on FMI for Co-Simulation
70    #[xml(child = "CoSimulation")]
71    pub co_simulation: Option<Fmi3CoSimulation>,
72
73    /// If present, the FMU is based on FMI for Scheduled Execution
74    #[xml(child = "ScheduledExecution")]
75    pub scheduled_execution: Option<Fmi3ScheduledExecution>,
76
77    /// A global list of unit and display unit definitions
78    #[xml(child = "UnitDefinitions")]
79    pub unit_definitions: Option<UnitDefinitions>,
80
81    /// A global list of type definitions that are utilized in `ModelVariables`
82    #[xml(child = "TypeDefinitions")]
83    pub type_definitions: Option<TypeDefinitions>,
84
85    /// Categories for logging purposes
86    #[xml(child = "LogCategories")]
87    pub log_categories: Option<LogCategories>,
88
89    /// Providing default settings for the integrator, such as stop time and relative tolerance.
90    #[xml(child = "DefaultExperiment")]
91    pub default_experiment: Option<DefaultExperiment>,
92
93    /// The model variables defined in the model.
94    #[xml(child = "ModelVariables", default)]
95    pub model_variables: ModelVariables,
96
97    /// The model structure defines the dependency structure of the model variables.
98    #[xml(child = "ModelStructure", default)]
99    pub model_structure: ModelStructure,
100
101    /// Optional annotations for the top-level element.
102    #[xml(child = "Annotations")]
103    pub annotations: Option<Annotations>,
104}
105
106impl FmiModelDescription for Fmi3ModelDescription {
107    fn model_name(&self) -> &str {
108        &self.model_name
109    }
110
111    fn version_string(&self) -> &str {
112        &self.fmi_version
113    }
114
115    fn serialize(&self) -> Result<String, Error> {
116        hard_xml::XmlWrite::to_string(self).map_err(Error::XmlParse)
117    }
118
119    fn deserialize(xml: &str) -> Result<Self, crate::Error> {
120        hard_xml::XmlRead::from_str(xml).map_err(crate::Error::XmlParse)
121    }
122}
123
124#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
125#[xml(tag = "UnitDefinitions", strict(unknown_attribute, unknown_element))]
126pub struct UnitDefinitions {
127    #[xml(child = "Unit")]
128    pub units: Vec<Fmi3Unit>,
129}
130
131#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
132#[xml(tag = "LogCategories", strict(unknown_attribute, unknown_element))]
133pub struct LogCategories {
134    #[xml(child = "Category")]
135    pub categories: Vec<Category>,
136}
137
138#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
139#[xml(tag = "Category", strict(unknown_attribute, unknown_element))]
140pub struct Category {
141    #[xml(child = "Annotations")]
142    pub annotations: Option<Annotations>,
143    #[xml(attr = "name")]
144    pub name: String,
145    #[xml(attr = "description")]
146    pub description: Option<String>,
147}
148
149#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
150#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
151#[cfg_attr(feature = "serde", serde(default))]
152#[xml(tag = "DefaultExperiment", strict(unknown_attribute, unknown_element))]
153pub struct DefaultExperiment {
154    #[xml(child = "Annotations")]
155    pub annotations: Option<Annotations>,
156    #[cfg_attr(
157        feature = "serde",
158        serde(deserialize_with = "crate::utils::deserialize_optional_f64_from_string")
159    )]
160    #[xml(attr = "startTime")]
161    pub start_time: Option<f64>,
162    #[cfg_attr(
163        feature = "serde",
164        serde(deserialize_with = "crate::utils::deserialize_optional_f64_from_string")
165    )]
166    #[xml(attr = "stopTime")]
167    pub stop_time: Option<f64>,
168    #[cfg_attr(
169        feature = "serde",
170        serde(deserialize_with = "crate::utils::deserialize_optional_f64_from_string")
171    )]
172    #[xml(attr = "tolerance")]
173    pub tolerance: Option<f64>,
174    #[cfg_attr(
175        feature = "serde",
176        serde(deserialize_with = "crate::utils::deserialize_optional_f64_from_string")
177    )]
178    #[xml(attr = "stepSize")]
179    pub step_size: Option<f64>,
180}
181
182#[derive(Default, PartialEq, Debug, hard_xml::XmlRead)]
183#[xml(tag = "ModelStructure", strict(unknown_attribute, unknown_element))]
184pub struct ModelStructure {
185    #[xml(
186        child = "Output",
187        child = "ContinuousStateDerivative",
188        child = "ClockedState",
189        child = "InitialUnknown",
190        child = "EventIndicator"
191    )]
192    pub unknowns: Vec<VariableDependency>,
193}
194
195impl hard_xml::XmlWrite for ModelStructure {
196    fn to_writer<W: std::io::Write>(
197        &self,
198        writer: &mut hard_xml::XmlWriter<W>,
199    ) -> hard_xml::XmlResult<()> {
200        ::hard_xml::log_start_writing!(ModelStructure);
201        writer.write_element_start("ModelStructure")?;
202
203        if self.unknowns.is_empty() {
204            writer.write_element_end_empty()?;
205            ::hard_xml::log_finish_writing!(ModelStructure);
206            return Ok(());
207        }
208
209        writer.write_element_end_open()?;
210
211        for unknown in self
212            .unknowns
213            .iter()
214            .filter(|dep| matches!(dep, VariableDependency::Output(_)))
215        {
216            hard_xml::XmlWrite::to_writer(unknown, writer)?;
217        }
218        for unknown in self
219            .unknowns
220            .iter()
221            .filter(|dep| matches!(dep, VariableDependency::ContinuousStateDerivative(_)))
222        {
223            hard_xml::XmlWrite::to_writer(unknown, writer)?;
224        }
225        for unknown in self
226            .unknowns
227            .iter()
228            .filter(|dep| matches!(dep, VariableDependency::ClockedState(_)))
229        {
230            hard_xml::XmlWrite::to_writer(unknown, writer)?;
231        }
232        for unknown in self
233            .unknowns
234            .iter()
235            .filter(|dep| matches!(dep, VariableDependency::InitialUnknown(_)))
236        {
237            hard_xml::XmlWrite::to_writer(unknown, writer)?;
238        }
239        for unknown in self
240            .unknowns
241            .iter()
242            .filter(|dep| matches!(dep, VariableDependency::EventIndicator(_)))
243        {
244            hard_xml::XmlWrite::to_writer(unknown, writer)?;
245        }
246
247        writer.write_element_end_close("ModelStructure")?;
248        ::hard_xml::log_finish_writing!(ModelStructure);
249        Ok(())
250    }
251}
252
253impl ModelStructure {
254    pub fn outputs(&self) -> impl Iterator<Item = &Fmi3Unknown> {
255        self.unknowns.iter().filter_map(|dep| match dep {
256            VariableDependency::Output(unknown) => Some(unknown),
257            _ => None,
258        })
259    }
260    pub fn continuous_state_derivatives(&self) -> impl Iterator<Item = &Fmi3Unknown> {
261        self.unknowns.iter().filter_map(|dep| match dep {
262            VariableDependency::ContinuousStateDerivative(unknown) => Some(unknown),
263            _ => None,
264        })
265    }
266    pub fn clocked_states(&self) -> impl Iterator<Item = &Fmi3Unknown> {
267        self.unknowns.iter().filter_map(|dep| match dep {
268            VariableDependency::ClockedState(unknown) => Some(unknown),
269            _ => None,
270        })
271    }
272    pub fn initial_unknowns(&self) -> impl Iterator<Item = &Fmi3Unknown> {
273        self.unknowns.iter().filter_map(|dep| match dep {
274            VariableDependency::InitialUnknown(unknown) => Some(unknown),
275            _ => None,
276        })
277    }
278    pub fn event_indicators(&self) -> impl Iterator<Item = &Fmi3Unknown> {
279        self.unknowns.iter().filter_map(|dep| match dep {
280            VariableDependency::EventIndicator(unknown) => Some(unknown),
281            _ => None,
282        })
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use hard_xml::{XmlRead, XmlWrite};
289
290    use crate::fmi3::Fmi3Unknown;
291
292    use super::*;
293    #[test]
294    fn test_model_descr() {
295        let _ = env_logger::builder()
296            .is_test(true)
297            .format_timestamp(None)
298            .try_init();
299
300        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
301    <fmiModelDescription
302        fmiVersion="3.0-beta.2"
303        modelName="FMI3"
304        instantiationToken="{fmi3}"
305        description="FMI3 Test FMU"
306        generationTool="FMI3"
307        generationDateAndTime="2021-03-01T00:00:00Z"
308        variableNamingConvention="flat">
309        <DefaultExperiment startTime="0" stopTime="3" stepSize="1e-3"/>
310        <ModelStructure>
311            <Output valueReference="1" />
312        </ModelStructure>
313    </fmiModelDescription>"#;
314
315        let md = Fmi3ModelDescription::from_str(xml).unwrap();
316
317        assert_eq!(md.fmi_version, "3.0-beta.2");
318        assert_eq!(md.model_name, "FMI3");
319        assert_eq!(md.instantiation_token, "{fmi3}");
320        assert_eq!(md.description.as_deref(), Some("FMI3 Test FMU"));
321        assert_eq!(md.variable_naming_convention.as_deref(), Some("flat"));
322        assert_eq!(md.generation_tool.as_deref(), Some("FMI3"));
323        assert_eq!(
324            md.default_experiment,
325            Some(DefaultExperiment {
326                start_time: Some(0.0),
327                stop_time: Some(3.0),
328                step_size: Some(1e-3),
329                ..Default::default()
330            })
331        );
332        assert_eq!(
333            md.model_structure,
334            ModelStructure {
335                unknowns: vec![VariableDependency::Output(Fmi3Unknown {
336                    value_reference: 1,
337                    ..Default::default()
338                })],
339            }
340        );
341    }
342
343    #[test]
344    fn test_model_structure_ordering() {
345        let model_structure = ModelStructure {
346            unknowns: vec![
347                VariableDependency::EventIndicator(Fmi3Unknown {
348                    value_reference: 5,
349                    ..Default::default()
350                }),
351                VariableDependency::Output(Fmi3Unknown {
352                    value_reference: 1,
353                    ..Default::default()
354                }),
355                VariableDependency::ContinuousStateDerivative(Fmi3Unknown {
356                    value_reference: 2,
357                    ..Default::default()
358                }),
359                VariableDependency::InitialUnknown(Fmi3Unknown {
360                    value_reference: 4,
361                    ..Default::default()
362                }),
363                VariableDependency::ClockedState(Fmi3Unknown {
364                    value_reference: 3,
365                    ..Default::default()
366                }),
367            ],
368        };
369
370        let xml = model_structure.to_string().unwrap();
371        assert_eq!(
372            xml,
373            r#"<ModelStructure><Output valueReference="1"/><ContinuousStateDerivative valueReference="2"/><ClockedState valueReference="3"/><InitialUnknown valueReference="4"/><EventIndicator valueReference="5"/></ModelStructure>"#
374        );
375    }
376}