1use crate::{Error, traits::FmiModelDescription};
2
3use super::{
4 CoSimulation, Fmi2Unit, Fmi2VariableDependency, ModelExchange, ScalarVariable, SimpleType,
5};
6
7#[derive(Default, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
8#[xml(
9 tag = "fmiModelDescription",
10 strict(unknown_attribute, unknown_element)
11)]
12pub struct Fmi2ModelDescription {
13 #[xml(attr = "fmiVersion")]
16 pub fmi_version: String,
17
18 #[xml(attr = "modelName")]
21 pub model_name: String,
22
23 #[xml(attr = "guid")]
26 pub guid: String,
27
28 #[xml(attr = "description")]
29 pub description: Option<String>,
30
31 #[xml(attr = "version")]
33 pub version: Option<String>,
34
35 #[xml(attr = "copyright")]
37 pub copyright: Option<String>,
38
39 #[xml(attr = "license")]
42 pub license: Option<String>,
43
44 #[xml(attr = "generationTool")]
46 pub generation_tool: Option<String>,
47
48 #[xml(attr = "generationDateAndTime")]
53 pub generation_date_and_time: Option<String>,
54
55 #[xml(attr = "variableNamingConvention")]
58 pub variable_naming_convention: Option<String>,
59
60 #[xml(attr = "numberOfEventIndicators")]
62 pub number_of_event_indicators: Option<u32>,
63
64 #[xml(child = "ModelExchange")]
66 pub model_exchange: Option<ModelExchange>,
67
68 #[xml(child = "CoSimulation")]
70 pub co_simulation: Option<CoSimulation>,
71
72 #[xml(child = "LogCategories")]
73 pub log_categories: Option<LogCategories>,
74
75 #[xml(child = "DefaultExperiment")]
76 pub default_experiment: Option<DefaultExperiment>,
77
78 #[xml(child = "UnitDefinitions")]
79 pub unit_definitions: Option<UnitDefinitions>,
80
81 #[xml(child = "TypeDefinitions")]
82 pub type_definitions: Option<TypeDefinitions>,
83
84 #[xml(child = "ModelVariables", default)]
85 pub model_variables: ModelVariables,
86
87 #[xml(child = "ModelStructure", default)]
88 pub model_structure: ModelStructure,
89}
90
91impl Fmi2ModelDescription {
92 pub fn num_variables(&self) -> usize {
94 self.model_variables.variables.len()
95 }
96
97 pub fn num_states(&self) -> usize {
99 self.model_structure.derivatives.unknowns.len()
100 }
101
102 pub fn num_event_indicators(&self) -> usize {
103 self.number_of_event_indicators.unwrap_or(0) as usize
104 }
105
106 pub fn get_model_variables(&self) -> impl Iterator<Item = &ScalarVariable> {
108 self.model_variables.variables.iter()
109 }
110
111 #[cfg(false)]
112 pub fn get_model_variable_by_vr(&self, vr: u32) -> Option<&ScalarVariable> {
113 self.model_variables.map.get(&vr)
114 }
115
116 #[cfg(false)]
118 fn map_unknowns(
119 &self,
120 list: &UnknownList,
121 ) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
122 list.unknowns
123 .iter()
124 .map(|unknown| {
125 self.model_variables
126 .by_index
127 .get(unknown.index as usize - 1)
129 .map(|vr| &self.model_variables.map[vr])
130 .ok_or_else(|| {
131 ModelDescriptionError::VariableAtIndexNotFound(
132 self.model_name.clone(),
133 unknown.index as usize,
134 )
135 })
136 .and_then(|var| {
137 let deps = unknown
138 .dependencies
139 .iter()
140 .map(|dep| {
141 self.model_variables
142 .by_index
143 .get(*dep as usize - 1)
144 .map(|vr| &self.model_variables.map[vr])
145 .ok_or_else(|| {
146 ModelDescriptionError::VariableAtIndexNotFound(
147 self.model_name.clone(),
148 *dep as usize,
149 )
150 })
151 })
152 .collect::<Result<Vec<_>, ModelDescriptionError>>()?;
153
154 Ok((var, deps))
155 })
156 })
157 .collect()
158 }
159
160 #[cfg(false)]
162 pub fn outputs(&self) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
163 self.map_unknowns(&self.model_structure.outputs)
164 }
165
166 #[cfg(false)]
168 pub fn derivatives(&self) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
169 self.map_unknowns(&self.model_structure.derivatives)
170 }
171
172 #[cfg(false)]
174 pub fn initial_unknowns(&self) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
175 self.map_unknowns(&self.model_structure.initial_unknowns)
176 }
177
178 pub fn model_variable_by_name(&self, name: &str) -> Result<&ScalarVariable, Error> {
180 self.model_variables
181 .variables
182 .iter()
183 .find(|var| var.name == name)
184 .ok_or_else(|| Error::VariableNotFound(name.to_owned()))
185 }
186
187 #[cfg(false)]
190 fn model_variable_by_index(
191 &self,
192 idx: usize,
193 ) -> Result<&ScalarVariable, ModelDescriptionError> {
194 self.model_variables
195 .by_index
196 .get(idx - 1)
197 .map(|vr| &self.model_variables.map[vr])
198 .ok_or_else(|| {
199 ModelDescriptionError::VariableAtIndexNotFound(
200 self.model_name.clone(),
201 idx as usize,
202 )
203 })
204 }
205
206 #[cfg(false)]
209 pub fn continuous_states(
210 &self,
211 ) -> Result<Vec<(&ScalarVariable, &ScalarVariable)>, ModelDescriptionError> {
212 self.model_structure
213 .derivatives
214 .unknowns
215 .iter()
216 .map(|unknown| {
217 self.model_variable_by_index(unknown.index as usize)
218 .and_then(|der| {
219 if let ScalarVariableElement::Real { derivative, .. } = der.elem {
220 derivative
221 .ok_or_else(|| {
222 ModelDescriptionError::VariableDerivativeMissing(
223 der.name.clone(),
224 )
225 })
226 .and_then(|der_idx| {
227 self.model_variable_by_index(der_idx as usize)
228 .map(|state| (state, der))
229 })
230 } else {
231 Err(ModelDescriptionError::VariableTypeMismatch(
232 ScalarVariableElementBase::Real,
233 ScalarVariableElementBase::from(&der.elem),
234 ))
235 }
236 })
237 })
238 .collect()
239 }
240}
241
242impl FmiModelDescription for Fmi2ModelDescription {
243 fn model_name(&self) -> &str {
244 &self.model_name
245 }
246
247 fn version_string(&self) -> &str {
248 &self.fmi_version
249 }
250
251 fn deserialize(xml: &str) -> Result<Self, crate::Error> {
252 hard_xml::XmlRead::from_str(xml).map_err(crate::Error::XmlParse)
253 }
254
255 fn serialize(&self) -> Result<String, crate::Error> {
256 hard_xml::XmlWrite::to_string(self).map_err(crate::Error::XmlParse)
257 }
258}
259
260#[derive(Clone, Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
261#[xml(tag = "LogCategories", strict(unknown_attribute, unknown_element))]
262pub struct LogCategories {
263 #[xml(child = "Category")]
264 pub categories: Vec<Category>,
265}
266
267#[derive(Clone, Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
268#[xml(tag = "Category", strict(unknown_attribute, unknown_element))]
269pub struct Category {
270 #[xml(attr = "name")]
271 pub name: String,
272 #[xml(attr = "description")]
273 pub description: String,
274}
275
276#[derive(Clone, Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
277#[xml(tag = "DefaultExperiment")]
278pub struct DefaultExperiment {
279 #[xml(attr = "startTime")]
281 pub start_time: Option<f64>,
282 #[xml(attr = "stopTime")]
284 pub stop_time: Option<f64>,
285 #[xml(attr = "tolerance")]
287 pub tolerance: Option<f64>,
288 #[xml(attr = "stepSize")]
291 pub step_size: Option<f64>,
292}
293
294impl DefaultExperiment {
295 pub fn start_time(&self) -> f64 {
296 self.start_time.unwrap_or(0.0)
297 }
298
299 pub fn stop_time(&self) -> f64 {
300 self.stop_time.unwrap_or(10.0)
301 }
302
303 pub fn tolerance(&self) -> f64 {
304 self.tolerance.unwrap_or(1e-3)
305 }
306
307 pub fn step_size(&self) -> Option<f64> {
308 self.step_size
309 }
310}
311
312#[derive(Default, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
313#[xml(tag = "UnitDefinitions", strict(unknown_attribute, unknown_element))]
314pub struct UnitDefinitions {
315 #[xml(child = "Unit")]
316 pub units: Vec<Fmi2Unit>,
317}
318
319#[derive(Default, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
320#[xml(tag = "TypeDefinitions", strict(unknown_attribute, unknown_element))]
321pub struct TypeDefinitions {
322 #[xml(child = "SimpleType")]
323 pub types: Vec<SimpleType>,
324}
325
326#[derive(Default, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
327#[xml(tag = "ModelVariables", strict(unknown_attribute, unknown_element))]
328pub struct ModelVariables {
329 #[xml(child = "ScalarVariable")]
330 pub variables: Vec<ScalarVariable>,
331}
332
333#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
334#[xml(tag = "ModelStructure", strict(unknown_attribute, unknown_element))]
335pub struct ModelStructure {
336 #[xml(child = "Outputs", default)]
337 pub outputs: Outputs,
338
339 #[xml(child = "Derivatives", default)]
340 pub derivatives: Derivatives,
341
342 #[xml(child = "InitialUnknowns", default)]
343 pub initial_unknowns: InitialUnknowns,
344}
345
346#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
347#[xml(tag = "Outputs")]
348pub struct Outputs {
349 #[xml(child = "Unknown")]
350 pub unknowns: Vec<Fmi2VariableDependency>,
351}
352
353#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
354#[xml(tag = "Derivatives")]
355pub struct Derivatives {
356 #[xml(child = "Unknown")]
357 pub unknowns: Vec<Fmi2VariableDependency>,
358}
359
360#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
361#[xml(tag = "InitialUnknowns")]
362pub struct InitialUnknowns {
363 #[xml(child = "Unknown")]
364 pub unknowns: Vec<Fmi2VariableDependency>,
365}
366
367#[cfg(test)]
368mod tests {
369 use hard_xml::XmlRead;
370
371 use super::*;
372
373 #[test]
374 fn test_model_description() {
375 let s = r##"<?xml version="1.0" encoding="UTF8"?>
376<fmiModelDescription
377 fmiVersion="2.0"
378 modelName="MyLibrary.SpringMassDamper"
379 guid="{8c4e810f-3df3-4a00-8276-176fa3c9f9e0}"
380 description="Rotational Spring Mass Damper System"
381 version="1.0"
382 generationDateAndTime="2011-09-23T16:57:33Z"
383 variableNamingConvention="structured"
384 numberOfEventIndicators="2">
385 <ModelVariables>
386 <ScalarVariable name="x[1]" valueReference="0" initial="exact"> <Real/> </ScalarVariable> <!-- idex="5" -->
387 <ScalarVariable name="x[2]" valueReference="1" initial="exact"> <Real/> </ScalarVariable> <!-- index="6" -->
388 <ScalarVariable name="PI.x" valueReference="46" description="State of block" causality="local" variability="continuous" initial="calculated">
389 <Real relativeQuantity="false" />
390 </ScalarVariable>
391 <ScalarVariable name="der(PI.x)" valueReference="45" causality="local" variability="continuous" initial="calculated">
392 <Real relativeQuantity="false" derivative="3" />
393 </ScalarVariable>
394 </ModelVariables>
395 <ModelStructure>
396 <Outputs><Unknown index="1" dependencies="1 2" /><Unknown index="2" /></Outputs>
397 <Derivatives><Unknown index="4" dependencies="1 2" /></Derivatives>
398 <InitialUnknowns />
399</ModelStructure>
400</fmiModelDescription>"##;
401 let md = Fmi2ModelDescription::from_str(&s).unwrap();
402 assert_eq!(md.fmi_version, "2.0");
403 assert_eq!(md.model_name, "MyLibrary.SpringMassDamper");
404 assert_eq!(md.guid, "{8c4e810f-3df3-4a00-8276-176fa3c9f9e0}");
405 assert_eq!(
406 md.description.as_deref(),
407 Some("Rotational Spring Mass Damper System")
408 );
409 assert_eq!(md.version.as_deref(), Some("1.0"));
410 assert_eq!(md.variable_naming_convention, Some("structured".to_owned()));
412 assert_eq!(md.number_of_event_indicators, Some(2));
413 assert_eq!(md.model_variables.variables.len(), 4);
414
415 let outputs = &md.model_structure.outputs.unknowns;
416 assert_eq!(outputs.len(), 2);
417 assert_eq!(outputs[0].index, 1);
418 assert_eq!(outputs[0].dependencies, vec![1, 2]);
419 assert_eq!(outputs[1].index, 2);
420 assert!(outputs[1].dependencies.is_empty());
421
422 let derivatives = &md.model_structure.derivatives.unknowns;
423 assert_eq!(derivatives.len(), 1);
424 assert_eq!(derivatives[0].index, 4);
425 assert_eq!(derivatives[0].dependencies, vec![1, 2]);
426 }
427}