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 #[xml(attr = "fmiVersion")]
16 pub fmi_version: String,
17
18 #[xml(attr = "modelName")]
21 pub model_name: String,
22
23 #[xml(attr = "instantiationToken")]
26 pub instantiation_token: String,
27
28 #[xml(attr = "description")]
30 pub description: Option<String>,
31
32 #[xml(attr = "author")]
34 pub author: Option<String>,
35
36 #[xml(attr = "version")]
38 pub version: Option<String>,
39
40 #[xml(attr = "copyright")]
43 pub copyright: Option<String>,
44
45 #[xml(attr = "license")]
48 pub license: Option<String>,
49
50 #[xml(attr = "generationTool")]
52 pub generation_tool: Option<String>,
53
54 #[xml(attr = "generationDateAndTime")]
58 pub generation_date_and_time: Option<String>,
59
60 #[xml(attr = "variableNamingConvention")]
63 pub variable_naming_convention: Option<String>,
64
65 #[xml(child = "ModelExchange")]
67 pub model_exchange: Option<Fmi3ModelExchange>,
68
69 #[xml(child = "CoSimulation")]
71 pub co_simulation: Option<Fmi3CoSimulation>,
72
73 #[xml(child = "ScheduledExecution")]
75 pub scheduled_execution: Option<Fmi3ScheduledExecution>,
76
77 #[xml(child = "UnitDefinitions")]
79 pub unit_definitions: Option<UnitDefinitions>,
80
81 #[xml(child = "TypeDefinitions")]
83 pub type_definitions: Option<TypeDefinitions>,
84
85 #[xml(child = "LogCategories")]
87 pub log_categories: Option<LogCategories>,
88
89 #[xml(child = "DefaultExperiment")]
91 pub default_experiment: Option<DefaultExperiment>,
92
93 #[xml(child = "ModelVariables", default)]
95 pub model_variables: ModelVariables,
96
97 #[xml(child = "ModelStructure", default)]
99 pub model_structure: ModelStructure,
100
101 #[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}