1use yaserde_derive::{YaDeserialize, YaSerialize};
2
3use crate::{Error, default_wrapper, traits::FmiModelDescription};
4
5use super::{
6 CoSimulation, Fmi2Unit, Fmi2VariableDependency, ModelExchange, ScalarVariable, SimpleType,
7};
8
9#[derive(Default, Debug, YaSerialize, YaDeserialize)]
10pub struct Fmi2ModelDescription {
11 #[yaserde(attribute = true, rename = "fmiVersion")]
14 pub fmi_version: String,
15
16 #[yaserde(attribute = true, rename = "modelName")]
19 pub model_name: String,
20
21 #[yaserde(attribute = true)]
24 pub guid: String,
25
26 #[yaserde(attribute = true)]
27 pub description: Option<String>,
28
29 #[yaserde(attribute = true)]
31 pub version: Option<String>,
32
33 #[yaserde(attribute = true)]
35 pub copyright: Option<String>,
36
37 #[yaserde(attribute = true)]
40 pub license: Option<String>,
41
42 #[yaserde(attribute = true, rename = "generationTool")]
44 pub generation_tool: Option<String>,
45
46 #[yaserde(attribute = true, rename = "generationDateAndTime")]
51 pub generation_date_and_time: Option<String>,
52
53 #[yaserde(attribute = true, rename = "variableNamingConvention")]
56 pub variable_naming_convention: Option<String>,
57
58 #[yaserde(attribute = true, rename = "numberOfEventIndicators")]
59 pub number_of_event_indicators: u32,
60
61 #[yaserde(rename = "ModelExchange")]
63 pub model_exchange: Option<ModelExchange>,
64
65 #[yaserde(rename = "CoSimulation")]
67 pub co_simulation: Option<CoSimulation>,
68
69 #[yaserde(rename = "LogCategories")]
70 pub log_categories: Option<LogCategories>,
71
72 #[yaserde(rename = "DefaultExperiment")]
73 pub default_experiment: Option<DefaultExperiment>,
74
75 #[yaserde(rename = "UnitDefinitions")]
76 pub unit_definitions: Option<UnitDefinitions>,
77
78 #[yaserde(rename = "TypeDefinitions")]
79 pub type_definitions: Option<TypeDefinitions>,
80
81 #[yaserde(rename = "ModelVariables")]
82 pub model_variables: ModelVariables,
83
84 #[yaserde(rename = "ModelStructure")]
85 pub model_structure: ModelStructure,
86}
87
88impl Fmi2ModelDescription {
89 pub fn num_variables(&self) -> usize {
91 self.model_variables.variables.len()
92 }
93
94 pub fn num_states(&self) -> usize {
96 self.model_structure.derivatives.unknowns.len()
97 }
98
99 pub fn num_event_indicators(&self) -> usize {
100 self.number_of_event_indicators as usize
101 }
102
103 pub fn get_model_variables(&self) -> impl Iterator<Item = &ScalarVariable> {
105 self.model_variables.variables.iter()
106 }
107
108 #[cfg(false)]
109 pub fn get_model_variable_by_vr(&self, vr: u32) -> Option<&ScalarVariable> {
110 self.model_variables.map.get(&vr)
111 }
112
113 #[cfg(false)]
115 fn map_unknowns(
116 &self,
117 list: &UnknownList,
118 ) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
119 list.unknowns
120 .iter()
121 .map(|unknown| {
122 self.model_variables
123 .by_index
124 .get(unknown.index as usize - 1)
126 .map(|vr| &self.model_variables.map[vr])
127 .ok_or_else(|| {
128 ModelDescriptionError::VariableAtIndexNotFound(
129 self.model_name.clone(),
130 unknown.index as usize,
131 )
132 })
133 .and_then(|var| {
134 let deps = unknown
135 .dependencies
136 .iter()
137 .map(|dep| {
138 self.model_variables
139 .by_index
140 .get(*dep as usize - 1)
141 .map(|vr| &self.model_variables.map[vr])
142 .ok_or_else(|| {
143 ModelDescriptionError::VariableAtIndexNotFound(
144 self.model_name.clone(),
145 *dep as usize,
146 )
147 })
148 })
149 .collect::<Result<Vec<_>, ModelDescriptionError>>()?;
150
151 Ok((var, deps))
152 })
153 })
154 .collect()
155 }
156
157 #[cfg(false)]
159 pub fn outputs(&self) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
160 self.map_unknowns(&self.model_structure.outputs)
161 }
162
163 #[cfg(false)]
165 pub fn derivatives(&self) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
166 self.map_unknowns(&self.model_structure.derivatives)
167 }
168
169 #[cfg(false)]
171 pub fn initial_unknowns(&self) -> Result<Vec<UnknownsTuple>, ModelDescriptionError> {
172 self.map_unknowns(&self.model_structure.initial_unknowns)
173 }
174
175 pub fn model_variable_by_name(&self, name: &str) -> Result<&ScalarVariable, Error> {
177 self.model_variables
178 .variables
179 .iter()
180 .find(|var| var.name == name)
181 .ok_or_else(|| Error::VariableNotFound(name.to_owned()))
182 }
183
184 #[cfg(false)]
187 fn model_variable_by_index(
188 &self,
189 idx: usize,
190 ) -> Result<&ScalarVariable, ModelDescriptionError> {
191 self.model_variables
192 .by_index
193 .get(idx - 1)
194 .map(|vr| &self.model_variables.map[vr])
195 .ok_or_else(|| {
196 ModelDescriptionError::VariableAtIndexNotFound(
197 self.model_name.clone(),
198 idx as usize,
199 )
200 })
201 }
202
203 #[cfg(false)]
206 pub fn continuous_states(
207 &self,
208 ) -> Result<Vec<(&ScalarVariable, &ScalarVariable)>, ModelDescriptionError> {
209 self.model_structure
210 .derivatives
211 .unknowns
212 .iter()
213 .map(|unknown| {
214 self.model_variable_by_index(unknown.index as usize)
215 .and_then(|der| {
216 if let ScalarVariableElement::Real { derivative, .. } = der.elem {
217 derivative
218 .ok_or_else(|| {
219 ModelDescriptionError::VariableDerivativeMissing(
220 der.name.clone(),
221 )
222 })
223 .and_then(|der_idx| {
224 self.model_variable_by_index(der_idx as usize)
225 .map(|state| (state, der))
226 })
227 } else {
228 Err(ModelDescriptionError::VariableTypeMismatch(
229 ScalarVariableElementBase::Real,
230 ScalarVariableElementBase::from(&der.elem),
231 ))
232 }
233 })
234 })
235 .collect()
236 }
237}
238
239impl FmiModelDescription for Fmi2ModelDescription {
240 fn model_name(&self) -> &str {
241 &self.model_name
242 }
243
244 fn version_string(&self) -> &str {
245 &self.fmi_version
246 }
247}
248
249#[derive(Clone, Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
250pub struct LogCategories {
251 #[yaserde(rename = "Category")]
252 pub categories: Vec<Category>,
253}
254
255#[derive(Clone, Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
256pub struct Category {
257 #[yaserde(attribute = true)]
258 pub name: String,
259 #[yaserde(attribute = true)]
260 pub description: String,
261}
262
263#[derive(Clone, Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
264pub struct DefaultExperiment {
265 #[yaserde(attribute = true, default = "default_start_time", rename = "startTime")]
266 pub start_time: f64,
267 #[yaserde(attribute = true, default = "default_stop_time", rename = "stopTime")]
268 pub stop_time: f64,
269 #[yaserde(attribute = true, default = "default_tolerance", rename = "tolerance")]
270 pub tolerance: f64,
271}
272
273const fn default_start_time() -> f64 {
274 0.0
275}
276
277const fn default_stop_time() -> f64 {
278 10.0
279}
280const fn default_tolerance() -> f64 {
281 1e-3
282}
283
284#[derive(Default, Debug, YaSerialize, YaDeserialize)]
285pub struct UnitDefinitions {
286 #[yaserde(rename = "Unit")]
287 pub units: Vec<Fmi2Unit>,
288}
289
290#[derive(Default, Debug, YaSerialize, YaDeserialize)]
291pub struct TypeDefinitions {
292 #[yaserde(rename = "SimpleType")]
293 pub types: Vec<SimpleType>,
294}
295
296#[derive(Default, Debug, YaSerialize, YaDeserialize)]
297pub struct ModelVariables {
298 #[yaserde(rename = "ScalarVariable")]
299 pub variables: Vec<ScalarVariable>,
300}
301
302#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
303#[yaserde(rename = "ModelStructure")]
304pub struct ModelStructure {
305 #[yaserde(rename = "Outputs")]
306 pub outputs: UnknownList,
307
308 #[yaserde(rename = "Derivatives", default = "default_wrapper")]
309 pub derivatives: UnknownList,
310
311 #[yaserde(rename = "InitialUnknowns", default = "default_wrapper")]
312 pub initial_unknowns: UnknownList,
313}
314
315#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
316pub struct UnknownList {
317 #[yaserde(rename = "Unknown")]
318 pub unknowns: Vec<Fmi2VariableDependency>,
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn test_model_description() {
327 let s = r##"<?xml version="1.0" encoding="UTF8"?>
328<fmiModelDescription
329 fmiVersion="2.0"
330 modelName="MyLibrary.SpringMassDamper"
331 guid="{8c4e810f-3df3-4a00-8276-176fa3c9f9e0}"
332 description="Rotational Spring Mass Damper System"
333 version="1.0"
334 generationDateAndTime="2011-09-23T16:57:33Z"
335 variableNamingConvention="structured"
336 numberOfEventIndicators="2">
337 <ModelVariables>
338 <ScalarVariable name="x[1]" valueReference="0" initial="exact"> <Real/> </ScalarVariable> <!-- idex="5" -->
339 <ScalarVariable name="x[2]" valueReference="1" initial="exact"> <Real/> </ScalarVariable> <!-- index="6" -->
340 <ScalarVariable name="PI.x" valueReference="46" description="State of block" causality="local" variability="continuous" initial="calculated">
341 <Real relativeQuantity="false" />
342 </ScalarVariable>
343 <ScalarVariable name="der(PI.x)" valueReference="45" causality="local" variability="continuous" initial="calculated">
344 <Real relativeQuantity="false" derivative="3" />
345 </ScalarVariable>
346 </ModelVariables>
347 <ModelStructure>
348 <Outputs><Unknown index="1" dependencies="1 2" /><Unknown index="2" /></Outputs>
349 <Derivatives><Unknown index="4" dependencies="1 2" /></Derivatives>
350 <InitialUnknowns />
351</ModelStructure>
352</fmiModelDescription>"##;
353 let md: Fmi2ModelDescription = yaserde::de::from_str(s).unwrap();
354 assert_eq!(md.fmi_version, "2.0");
355 assert_eq!(md.model_name, "MyLibrary.SpringMassDamper");
356 assert_eq!(md.guid, "{8c4e810f-3df3-4a00-8276-176fa3c9f9e0}");
357 assert_eq!(
358 md.description.as_deref(),
359 Some("Rotational Spring Mass Damper System")
360 );
361 assert_eq!(md.version.as_deref(), Some("1.0"));
362 assert_eq!(md.variable_naming_convention, Some("structured".to_owned()));
364 assert_eq!(md.number_of_event_indicators, 2);
365 assert_eq!(md.model_variables.variables.len(), 4);
366
367 let outputs = &md.model_structure.outputs.unknowns;
368 assert_eq!(outputs.len(), 2);
369 assert_eq!(outputs[0].index, 1);
370 assert_eq!(outputs[0].dependencies, vec![1, 2]);
371 assert_eq!(outputs[1].index, 2);
372 assert!(outputs[1].dependencies.is_empty());
373
374 let derivatives = &md.model_structure.derivatives.unknowns;
375 assert_eq!(derivatives.len(), 1);
376 assert_eq!(derivatives[0].index, 4);
377 assert_eq!(derivatives[0].dependencies, vec![1, 2]);
378 }
379}