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, hard_xml::XmlWrite)]
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 ModelStructure {
196    pub fn outputs(&self) -> impl Iterator<Item = &Fmi3Unknown> {
197        self.unknowns.iter().filter_map(|dep| match dep {
198            VariableDependency::Output(unknown) => Some(unknown),
199            _ => None,
200        })
201    }
202    pub fn continuous_state_derivatives(&self) -> impl Iterator<Item = &Fmi3Unknown> {
203        self.unknowns.iter().filter_map(|dep| match dep {
204            VariableDependency::ContinuousStateDerivative(unknown) => Some(unknown),
205            _ => None,
206        })
207    }
208    pub fn clocked_states(&self) -> impl Iterator<Item = &Fmi3Unknown> {
209        self.unknowns.iter().filter_map(|dep| match dep {
210            VariableDependency::ClockedState(unknown) => Some(unknown),
211            _ => None,
212        })
213    }
214    pub fn initial_unknowns(&self) -> impl Iterator<Item = &Fmi3Unknown> {
215        self.unknowns.iter().filter_map(|dep| match dep {
216            VariableDependency::InitialUnknown(unknown) => Some(unknown),
217            _ => None,
218        })
219    }
220    pub fn event_indicators(&self) -> impl Iterator<Item = &Fmi3Unknown> {
221        self.unknowns.iter().filter_map(|dep| match dep {
222            VariableDependency::EventIndicator(unknown) => Some(unknown),
223            _ => None,
224        })
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use hard_xml::XmlRead;
231
232    use crate::fmi3::Fmi3Unknown;
233
234    use super::*;
235    #[test]
236    fn test_model_descr() {
237        let _ = env_logger::builder()
238            .is_test(true)
239            .format_timestamp(None)
240            .try_init();
241
242        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
243    <fmiModelDescription
244        fmiVersion="3.0-beta.2"
245        modelName="FMI3"
246        instantiationToken="{fmi3}"
247        description="FMI3 Test FMU"
248        generationTool="FMI3"
249        generationDateAndTime="2021-03-01T00:00:00Z"
250        variableNamingConvention="flat">
251        <DefaultExperiment startTime="0" stopTime="3" stepSize="1e-3"/>
252        <ModelStructure>
253            <Output valueReference="1" />
254        </ModelStructure>
255    </fmiModelDescription>"#;
256
257        let md = Fmi3ModelDescription::from_str(xml).unwrap();
258
259        assert_eq!(md.fmi_version, "3.0-beta.2");
260        assert_eq!(md.model_name, "FMI3");
261        assert_eq!(md.instantiation_token, "{fmi3}");
262        assert_eq!(md.description.as_deref(), Some("FMI3 Test FMU"));
263        assert_eq!(md.variable_naming_convention.as_deref(), Some("flat"));
264        assert_eq!(md.generation_tool.as_deref(), Some("FMI3"));
265        assert_eq!(
266            md.default_experiment,
267            Some(DefaultExperiment {
268                start_time: Some(0.0),
269                stop_time: Some(3.0),
270                step_size: Some(1e-3),
271                ..Default::default()
272            })
273        );
274        assert_eq!(
275            md.model_structure,
276            ModelStructure {
277                unknowns: vec![VariableDependency::Output(Fmi3Unknown {
278                    value_reference: 1,
279                    ..Default::default()
280                })],
281            }
282        );
283    }
284}