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