use crate::{Error, fmi3::Fmi3Unknown, traits::FmiModelDescription};
use super::{
Annotations, Fmi3CoSimulation, Fmi3ModelExchange, Fmi3ScheduledExecution, Fmi3Unit,
ModelVariables, TypeDefinitions, VariableDependency,
};
#[derive(Default, Debug, PartialEq, hard_xml::XmlRead, hard_xml::XmlWrite)]
#[xml(
tag = "fmiModelDescription",
strict(unknown_attribute, unknown_element)
)]
pub struct Fmi3ModelDescription {
#[xml(attr = "fmiVersion")]
pub fmi_version: String,
#[xml(attr = "modelName")]
pub model_name: String,
#[xml(attr = "instantiationToken")]
pub instantiation_token: String,
#[xml(attr = "description")]
pub description: Option<String>,
#[xml(attr = "author")]
pub author: Option<String>,
#[xml(attr = "version")]
pub version: Option<String>,
#[xml(attr = "copyright")]
pub copyright: Option<String>,
#[xml(attr = "license")]
pub license: Option<String>,
#[xml(attr = "generationTool")]
pub generation_tool: Option<String>,
#[xml(attr = "generationDateAndTime")]
pub generation_date_and_time: Option<String>,
#[xml(attr = "variableNamingConvention")]
pub variable_naming_convention: Option<String>,
#[xml(child = "ModelExchange")]
pub model_exchange: Option<Fmi3ModelExchange>,
#[xml(child = "CoSimulation")]
pub co_simulation: Option<Fmi3CoSimulation>,
#[xml(child = "ScheduledExecution")]
pub scheduled_execution: Option<Fmi3ScheduledExecution>,
#[xml(child = "UnitDefinitions")]
pub unit_definitions: Option<UnitDefinitions>,
#[xml(child = "TypeDefinitions")]
pub type_definitions: Option<TypeDefinitions>,
#[xml(child = "LogCategories")]
pub log_categories: Option<LogCategories>,
#[xml(child = "DefaultExperiment")]
pub default_experiment: Option<DefaultExperiment>,
#[xml(child = "ModelVariables", default)]
pub model_variables: ModelVariables,
#[xml(child = "ModelStructure", default)]
pub model_structure: ModelStructure,
#[xml(child = "Annotations")]
pub annotations: Option<Annotations>,
}
impl FmiModelDescription for Fmi3ModelDescription {
fn model_name(&self) -> &str {
&self.model_name
}
fn version_string(&self) -> &str {
&self.fmi_version
}
fn serialize(&self) -> Result<String, Error> {
hard_xml::XmlWrite::to_string(self).map_err(Error::XmlParse)
}
fn deserialize(xml: &str) -> Result<Self, crate::Error> {
hard_xml::XmlRead::from_str(xml).map_err(crate::Error::XmlParse)
}
}
#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
#[xml(tag = "UnitDefinitions", strict(unknown_attribute, unknown_element))]
pub struct UnitDefinitions {
#[xml(child = "Unit")]
pub units: Vec<Fmi3Unit>,
}
#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
#[xml(tag = "LogCategories", strict(unknown_attribute, unknown_element))]
pub struct LogCategories {
#[xml(child = "Category")]
pub categories: Vec<Category>,
}
#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
#[xml(tag = "Category", strict(unknown_attribute, unknown_element))]
pub struct Category {
#[xml(child = "Annotations")]
pub annotations: Option<Annotations>,
#[xml(attr = "name")]
pub name: String,
#[xml(attr = "description")]
pub description: Option<String>,
}
#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[xml(tag = "DefaultExperiment", strict(unknown_attribute, unknown_element))]
pub struct DefaultExperiment {
#[xml(child = "Annotations")]
pub annotations: Option<Annotations>,
#[cfg_attr(
feature = "serde",
serde(deserialize_with = "crate::utils::deserialize_optional_f64_from_string")
)]
#[xml(attr = "startTime")]
pub start_time: Option<f64>,
#[cfg_attr(
feature = "serde",
serde(deserialize_with = "crate::utils::deserialize_optional_f64_from_string")
)]
#[xml(attr = "stopTime")]
pub stop_time: Option<f64>,
#[cfg_attr(
feature = "serde",
serde(deserialize_with = "crate::utils::deserialize_optional_f64_from_string")
)]
#[xml(attr = "tolerance")]
pub tolerance: Option<f64>,
#[cfg_attr(
feature = "serde",
serde(deserialize_with = "crate::utils::deserialize_optional_f64_from_string")
)]
#[xml(attr = "stepSize")]
pub step_size: Option<f64>,
}
#[derive(Default, PartialEq, Debug, hard_xml::XmlRead)]
#[xml(tag = "ModelStructure", strict(unknown_attribute, unknown_element))]
pub struct ModelStructure {
#[xml(
child = "Output",
child = "ContinuousStateDerivative",
child = "ClockedState",
child = "InitialUnknown",
child = "EventIndicator"
)]
pub unknowns: Vec<VariableDependency>,
}
impl hard_xml::XmlWrite for ModelStructure {
fn to_writer<W: std::io::Write>(
&self,
writer: &mut hard_xml::XmlWriter<W>,
) -> hard_xml::XmlResult<()> {
::hard_xml::log_start_writing!(ModelStructure);
writer.write_element_start("ModelStructure")?;
if self.unknowns.is_empty() {
writer.write_element_end_empty()?;
::hard_xml::log_finish_writing!(ModelStructure);
return Ok(());
}
writer.write_element_end_open()?;
for unknown in self
.unknowns
.iter()
.filter(|dep| matches!(dep, VariableDependency::Output(_)))
{
hard_xml::XmlWrite::to_writer(unknown, writer)?;
}
for unknown in self
.unknowns
.iter()
.filter(|dep| matches!(dep, VariableDependency::ContinuousStateDerivative(_)))
{
hard_xml::XmlWrite::to_writer(unknown, writer)?;
}
for unknown in self
.unknowns
.iter()
.filter(|dep| matches!(dep, VariableDependency::ClockedState(_)))
{
hard_xml::XmlWrite::to_writer(unknown, writer)?;
}
for unknown in self
.unknowns
.iter()
.filter(|dep| matches!(dep, VariableDependency::InitialUnknown(_)))
{
hard_xml::XmlWrite::to_writer(unknown, writer)?;
}
for unknown in self
.unknowns
.iter()
.filter(|dep| matches!(dep, VariableDependency::EventIndicator(_)))
{
hard_xml::XmlWrite::to_writer(unknown, writer)?;
}
writer.write_element_end_close("ModelStructure")?;
::hard_xml::log_finish_writing!(ModelStructure);
Ok(())
}
}
impl ModelStructure {
pub fn outputs(&self) -> impl Iterator<Item = &Fmi3Unknown> {
self.unknowns.iter().filter_map(|dep| match dep {
VariableDependency::Output(unknown) => Some(unknown),
_ => None,
})
}
pub fn continuous_state_derivatives(&self) -> impl Iterator<Item = &Fmi3Unknown> {
self.unknowns.iter().filter_map(|dep| match dep {
VariableDependency::ContinuousStateDerivative(unknown) => Some(unknown),
_ => None,
})
}
pub fn clocked_states(&self) -> impl Iterator<Item = &Fmi3Unknown> {
self.unknowns.iter().filter_map(|dep| match dep {
VariableDependency::ClockedState(unknown) => Some(unknown),
_ => None,
})
}
pub fn initial_unknowns(&self) -> impl Iterator<Item = &Fmi3Unknown> {
self.unknowns.iter().filter_map(|dep| match dep {
VariableDependency::InitialUnknown(unknown) => Some(unknown),
_ => None,
})
}
pub fn event_indicators(&self) -> impl Iterator<Item = &Fmi3Unknown> {
self.unknowns.iter().filter_map(|dep| match dep {
VariableDependency::EventIndicator(unknown) => Some(unknown),
_ => None,
})
}
}
#[cfg(test)]
mod tests {
use hard_xml::{XmlRead, XmlWrite};
use crate::fmi3::Fmi3Unknown;
use super::*;
#[test]
fn test_model_descr() {
let _ = env_logger::builder()
.is_test(true)
.format_timestamp(None)
.try_init();
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<fmiModelDescription
fmiVersion="3.0-beta.2"
modelName="FMI3"
instantiationToken="{fmi3}"
description="FMI3 Test FMU"
generationTool="FMI3"
generationDateAndTime="2021-03-01T00:00:00Z"
variableNamingConvention="flat">
<DefaultExperiment startTime="0" stopTime="3" stepSize="1e-3"/>
<ModelStructure>
<Output valueReference="1" />
</ModelStructure>
</fmiModelDescription>"#;
let md = Fmi3ModelDescription::from_str(xml).unwrap();
assert_eq!(md.fmi_version, "3.0-beta.2");
assert_eq!(md.model_name, "FMI3");
assert_eq!(md.instantiation_token, "{fmi3}");
assert_eq!(md.description.as_deref(), Some("FMI3 Test FMU"));
assert_eq!(md.variable_naming_convention.as_deref(), Some("flat"));
assert_eq!(md.generation_tool.as_deref(), Some("FMI3"));
assert_eq!(
md.default_experiment,
Some(DefaultExperiment {
start_time: Some(0.0),
stop_time: Some(3.0),
step_size: Some(1e-3),
..Default::default()
})
);
assert_eq!(
md.model_structure,
ModelStructure {
unknowns: vec![VariableDependency::Output(Fmi3Unknown {
value_reference: 1,
..Default::default()
})],
}
);
}
#[test]
fn test_model_structure_ordering() {
let model_structure = ModelStructure {
unknowns: vec![
VariableDependency::EventIndicator(Fmi3Unknown {
value_reference: 5,
..Default::default()
}),
VariableDependency::Output(Fmi3Unknown {
value_reference: 1,
..Default::default()
}),
VariableDependency::ContinuousStateDerivative(Fmi3Unknown {
value_reference: 2,
..Default::default()
}),
VariableDependency::InitialUnknown(Fmi3Unknown {
value_reference: 4,
..Default::default()
}),
VariableDependency::ClockedState(Fmi3Unknown {
value_reference: 3,
..Default::default()
}),
],
};
let xml = model_structure.to_string().unwrap();
assert_eq!(
xml,
r#"<ModelStructure><Output valueReference="1"/><ContinuousStateDerivative valueReference="2"/><ClockedState valueReference="3"/><InitialUnknown valueReference="4"/><EventIndicator valueReference="5"/></ModelStructure>"#
);
}
}