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, 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}