use derive_more::Display;
use failure::{format_err, Error, ResultExt};
use serde::{de, Deserialize, Deserializer};
use std::str::FromStr;
pub use serde_xml_rs::from_reader;
fn t_from_str<'de, T, D>(deser: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
let s = <String>::deserialize(deser)?;
T::from_str(&s).map_err(de::Error::custom)
}
fn vec_from_str<'de, T, D>(deser: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
let s = <String>::deserialize(deser)?;
if s.len() == 0 {
return Ok(Vec::<T>::new());
}
s.split(" ")
.map(|i| T::from_str(&i).map_err(de::Error::custom))
.collect()
}
fn dtparse_from_str<'de, D>(deser: D) -> Result<chrono::DateTime<chrono::Utc>, D::Error>
where
D: Deserializer<'de>,
{
use chrono::{DateTime, Utc};
let s = <String>::deserialize(deser)?;
dtparse::parse(&s)
.map_err(|e| de::Error::custom(format!("{:?}", e)))
.and_then(|dt| Ok(DateTime::<Utc>::from_utc(dt.0, Utc)))
}
#[derive(Debug, Deserialize)]
#[serde(rename = "fmiModelDescription", rename_all = "camelCase")]
pub struct ModelDescription {
pub fmi_version: String,
pub model_name: String,
pub guid: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub version: String,
#[serde(deserialize_with = "dtparse_from_str")]
pub generation_date_and_time: chrono::DateTime<chrono::Utc>,
#[serde(default)]
pub generation_tool: String,
pub variable_naming_convention: String,
#[serde(deserialize_with = "t_from_str")]
pub number_of_event_indicators: u32,
#[serde(rename = "ModelExchange")]
pub model_exchange: Option<ModelExchange>,
#[serde(rename = "CoSimulation")]
pub co_simulation: Option<CoSimulation>,
#[serde(rename = "LogCategories")]
pub log_categories: Option<LogCategories>,
#[serde(rename = "DefaultExperiment")]
pub default_experiment: Option<DefaultExperiment>,
#[serde(rename = "UnitDefinitions")]
pub unit_definitions: Option<UnitDefinitions>,
#[serde(rename = "TypeDefinitions")]
pub type_definitions: Option<TypeDefinitions>,
#[serde(rename = "ModelVariables")]
model_variables: ModelVariables,
#[serde(rename = "ModelStructure")]
model_structure: ModelStructure,
}
pub type ScalarVariableMap<'a> = std::collections::HashMap<String, &'a ScalarVariable>;
pub type UnknownsTuple<'a> = (&'a ScalarVariable, Vec<&'a ScalarVariable>);
#[derive(Debug, Default)]
pub struct Counts {
pub num_constants: usize,
pub num_parameters: usize,
pub num_discrete: usize,
pub num_continuous: usize,
pub num_inputs: usize,
pub num_outputs: usize,
pub num_local: usize,
pub num_independent: usize,
pub num_calculated_parameters: usize,
pub num_real_vars: usize,
pub num_integer_vars: usize,
pub num_enum_vars: usize,
pub num_bool_vars: usize,
pub num_string_vars: usize,
}
impl ModelDescription {
pub fn model_name(&self) -> &str {
&self.model_name
}
pub fn model_counts(&self) -> Counts {
self.model_variables
.variables
.iter()
.fold(Counts::default(), |mut cts, ref sv| {
match sv.variability {
Variability::Constant => {
cts.num_constants += 1;
}
Variability::Continuous => {
cts.num_continuous += 1;
}
Variability::Discrete => {
cts.num_discrete += 1;
}
_ => {}
}
match sv.causality {
Causality::CalculatedParameter => {
cts.num_calculated_parameters += 1;
}
Causality::Parameter => {
cts.num_parameters += 1;
}
Causality::Input => {
cts.num_inputs += 1;
}
Causality::Output => {
cts.num_outputs += 1;
}
Causality::Local => {
cts.num_local += 1;
}
Causality::Independent => {
cts.num_independent += 1;
}
_ => {}
}
match sv.elem {
ScalarVariableElement::Real { .. } => {
cts.num_real_vars += 1;
}
ScalarVariableElement::Integer { .. } => {
cts.num_integer_vars += 1;
}
ScalarVariableElement::Enumeration { .. } => {
cts.num_enum_vars += 1;
}
ScalarVariableElement::Boolean { .. } => {
cts.num_bool_vars += 1;
}
ScalarVariableElement::String { .. } => {
cts.num_string_vars += 1;
}
}
cts
})
}
pub fn num_variables(&self) -> usize {
self.model_variables.variables.len()
}
pub fn num_states(&self) -> usize {
self.model_structure.derivatives.unknowns.len()
}
pub fn num_event_indicators(&self) -> usize {
self.number_of_event_indicators as usize
}
pub fn model_variables(&self) -> impl Iterator<Item = (&str, &ScalarVariable)> {
self.model_variables
.variables
.iter()
.map(|row| (row.name.as_ref(), row))
}
fn map_unknowns(&self, list: &UnknownList) -> Result<Vec<UnknownsTuple>, Error> {
list.unknowns
.iter()
.map(|unknown| {
self.model_variables
.variables
.get(unknown.index as usize - 1)
.ok_or(format_err!("Variable not found"))
.context(format!("{} var index {}", self.model_name, unknown.index))
.map_err(|e| -> Error { e.into() })
.and_then(|var| {
let deps = unknown
.dependencies
.iter()
.map(|dep| {
self.model_variables
.variables
.get(*dep as usize - 1)
.ok_or(format_err!("Dependency not found"))
.context(format!(
"{} var index {}",
self.model_name, *dep as usize
))
.map_err(|e| -> Error { e.into() })
})
.collect::<Result<Vec<_>, Error>>()?;
Ok((var, deps))
})
})
.collect()
}
pub fn outputs(&self) -> Result<Vec<UnknownsTuple>, Error> {
self.map_unknowns(&self.model_structure.outputs)
}
pub fn derivatives(&self) -> Result<Vec<UnknownsTuple>, Error> {
self.map_unknowns(&self.model_structure.derivatives)
}
pub fn initial_unknowns(&self) -> Result<Vec<UnknownsTuple>, Error> {
self.map_unknowns(&self.model_structure.initial_unknowns)
}
fn model_variable_by_index(&self, idx: usize) -> Result<&ScalarVariable, Error> {
self.model_variables
.variables
.get(idx - 1)
.ok_or(format_err!("Variable not found"))
}
pub fn continuous_states(&self) -> Result<Vec<(&ScalarVariable, &ScalarVariable)>, Error> {
self.model_structure
.derivatives
.unknowns
.iter()
.map(|unknown| {
self.model_variable_by_index(unknown.index as usize)
.and_then(|der| {
if let ScalarVariableElement::Real { derivative, .. } = der.elem {
derivative
.ok_or(format_err!("Derivative index missing"))
.and_then(|der_idx| {
self.model_variable_by_index(der_idx as usize)
.map(|state| (state, der))
})
} else {
Err(format_err!("Referenced state variable not a Real"))
}
})
})
.collect()
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelExchange {
pub model_identifier: String,
#[serde(default)]
pub needs_execution_tool: bool,
#[serde(default)]
pub completed_integrator_step_not_needed: bool,
#[serde(default)]
pub can_be_instantiated_only_once_per_process: bool,
#[serde(default)]
pub can_not_use_memory_management_functions: bool,
#[serde(default, rename = "canGetAndSetFMUState")]
pub can_get_and_set_fmu_state: bool,
#[serde(default, rename = "canSerializeFMUState")]
pub can_serialize_fmu_state: bool,
#[serde(default)]
pub provides_directional_derivative: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CoSimulation {
pub model_identifier: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LogCategories {
#[serde(default, rename = "$value")]
pub categories: Vec<Category>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Category {
pub name: String,
pub description: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Unit {
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UnitDefinitions {
#[serde(default, rename = "$value")]
pub units: Vec<Unit>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SimpleType {}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TypeDefinitions {
#[serde(default, rename = "$value")]
pub types: Vec<SimpleType>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DefaultExperiment {
#[serde(default, deserialize_with = "t_from_str")]
pub start_time: f64,
#[serde(default = "default_stop_time", deserialize_with = "t_from_str")]
pub stop_time: f64,
#[serde(default = "default_tolerance", deserialize_with = "t_from_str")]
pub tolerance: f64,
}
fn default_stop_time() -> f64 {
10.0
}
fn default_tolerance() -> f64 {
1e-3
}
#[derive(Debug, Display, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub enum Causality {
Parameter,
CalculatedParameter,
Input,
Output,
Local,
Independent,
Unknown,
}
impl Default for Causality {
fn default() -> Causality {
Causality::Unknown
}
}
#[derive(Debug, Display, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub enum Variability {
Constant,
Fixed,
Tunable,
Discrete,
Continuous,
Unknown,
}
impl Default for Variability {
fn default() -> Variability {
Variability::Unknown
}
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub enum Initial {
Exact,
Approx,
Calculated,
}
impl Default for Initial {
fn default() -> Initial {
Initial::Exact
}
}
#[derive(Debug, Deserialize, Display, Clone)]
#[serde(rename_all = "camelCase")]
#[display(
fmt = "ScalarVariable {} {{{}, {}, {}}}",
elem,
name,
causality,
variability
)]
pub struct ScalarVariable {
pub name: String,
#[serde(deserialize_with = "t_from_str")]
pub value_reference: u64,
#[serde(default)]
pub description: String,
#[serde(default)]
pub causality: Causality,
#[serde(default)]
pub variability: Variability,
#[serde(default)]
pub initial: Initial,
#[serde(rename = "$value")]
pub elem: ScalarVariableElement,
}
impl PartialEq for ScalarVariable {
fn eq(&self, other: &ScalarVariable) -> bool {
self.value_reference == other.value_reference
}
}
impl Eq for ScalarVariable {}
impl std::hash::Hash for ScalarVariable {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.value_reference.hash(state);
}
}
impl ScalarVariable {
pub fn is_continuous_input(&self) -> bool {
match (&self.elem, &self.causality) {
(ScalarVariableElement::Real { .. }, Causality::Input) => true,
_ => false
}
}
}
#[derive(Debug, Deserialize, Display, PartialEq, Clone)]
pub enum ScalarVariableElement {
#[serde(rename_all = "camelCase")]
#[display(fmt = "Real({:?},{})", declared_type, start)]
Real {
declared_type: Option<String>,
#[serde(default, deserialize_with = "t_from_str")]
start: f64,
#[serde(default, deserialize_with = "t_from_str")]
relative_quantity: bool,
#[serde(default)]
derivative: Option<u32>,
},
#[serde(rename_all = "camelCase")]
#[display(fmt = "Int({},{})", declared_type, start)]
Integer {
#[serde(default)]
declared_type: String,
#[serde(default, deserialize_with = "t_from_str")]
start: i64,
},
#[serde(rename_all = "camelCase")]
#[display(fmt = "Bool({},{})", declared_type, start)]
Boolean {
#[serde(default)]
declared_type: String,
#[serde(default, deserialize_with = "t_from_str")]
start: bool,
},
#[serde(rename_all = "camelCase")]
#[display(fmt = "String({},{})", declared_type, start)]
String {
#[serde(default)]
declared_type: String,
start: String,
},
#[serde(rename_all = "camelCase")]
#[display(fmt = "Enum({},{})", declared_type, start)]
Enumeration {
#[serde(default)]
declared_type: String,
#[serde(default, deserialize_with = "t_from_str")]
start: u32,
},
}
#[derive(Debug, Deserialize)]
pub struct ModelVariables {
#[serde(default, rename = "$value")]
variables: Vec<ScalarVariable>,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "Unknown")]
pub struct Unknown {
#[serde(deserialize_with = "t_from_str")]
pub index: u32,
#[serde(default, deserialize_with = "vec_from_str")]
pub dependencies: Vec<u32>,
}
#[derive(Debug, Deserialize)]
pub struct UnknownList {
#[serde(default, rename = "$value")]
pub unknowns: Vec<Unknown>,
}
impl Default for UnknownList {
fn default() -> UnknownList {
UnknownList {
unknowns: Vec::<Unknown>::new(),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename = "ModelStructure", rename_all = "PascalCase")]
struct ModelStructure {
#[serde(default)]
pub outputs: UnknownList,
#[serde(default)]
pub derivatives: UnknownList,
#[serde(default)]
pub initial_unknowns: UnknownList,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_exchange() {
let s = r##"<ModelExchange modelIdentifier="MyLibrary_SpringMassDamper"/>"##;
let x: ModelExchange = serde_xml_rs::from_reader(s.as_bytes()).unwrap();
assert!(x.model_identifier == "MyLibrary_SpringMassDamper");
}
#[test]
fn test_default_experiment() {
let s = r##"<DefaultExperiment stopTime="3.0" tolerance="0.0001"/>"##;
let x: DefaultExperiment = serde_xml_rs::from_reader(s.as_bytes()).unwrap();
assert_eq!(x.start_time, 0.0);
assert_eq!(x.stop_time, 3.0);
assert_eq!(x.tolerance, 0.0001);
let s = r#"<DefaultExperiment startTime = "0.10000000000000000e+00" stopTime = "1.50000000000000000e+00" tolerance = "0.0001"/>"#;
let x: DefaultExperiment = serde_xml_rs::from_reader(s.as_bytes()).unwrap();
assert_eq!(x.start_time, 0.1);
assert_eq!(x.stop_time, 1.5);
assert_eq!(x.tolerance, 0.0001);
}
#[test]
fn test_scalar_variable() {
let s =r##"
<ScalarVariable name="inertia1.J" valueReference="1073741824" description="Moment of load inertia" causality="parameter" variability="fixed">
<Real declaredType="Modelica.SIunits.Inertia" start="1"/>
</ScalarVariable>
"##;
let x: ScalarVariable = serde_xml_rs::from_reader(s.as_bytes()).unwrap();
assert_eq!(x.name, "inertia1.J");
assert_eq!(x.value_reference, 1073741824);
assert_eq!(x.description, "Moment of load inertia");
assert_eq!(x.causality, Causality::Parameter);
assert_eq!(x.variability, Variability::Fixed);
assert_eq!(
x.elem,
ScalarVariableElement::Real {
declared_type: Some("Modelica.SIunits.Inertia".to_string()),
start: 1.0,
relative_quantity: false,
derivative: None
}
);
}
#[test]
fn test_model_variables() {
let s = r##"
<ModelVariables>
<ScalarVariable name="x[1]" valueReference="0" initial="exact"> <Real/> </ScalarVariable> <!-- idex="5" -->
<ScalarVariable name="x[2]" valueReference="1" initial="exact"> <Real/> </ScalarVariable> <!-- index="6" -->
<ScalarVariable name="der(x[1])" valueReference="2"> <Real derivative="5"/> </ScalarVariable> <!-- index="7" -->
<ScalarVariable name="der(x[2])" valueReference="3"> <Real derivative="6"/> </ScalarVariable> <!-- index="8" -->
</ModelVariables>
"##;
let x: ModelVariables = serde_xml_rs::from_reader(s.as_bytes()).unwrap();
assert_eq!(x.variables.len(), 4);
assert_eq!(
x.variables
.iter()
.map(|v| &v.name)
.collect::<Vec<_>>()
.sort(),
vec!["x[1]", "x[2]", "der(x[1])", "der(x[2])"].sort()
);
}
#[test]
fn test_model_structure() {
let s = r##"
<ModelStructure>
<Outputs> <Unknown index="3" /> <Unknown index="4" /> </Outputs>
<Derivatives> <Unknown index="7" /> <Unknown index="8" /> </Derivatives>
<InitialUnknowns> <Unknown index="3" /> <Unknown index="4" /> <Unknown index="7" dependencies="5 2" /> <Unknown index="8" dependencies="5 6" /> </InitialUnknowns>
</ModelStructure>
"##;
let x: ModelStructure = serde_xml_rs::from_reader(s.as_bytes()).unwrap();
assert_eq!(x.outputs.unknowns[0].index, 3);
assert_eq!(x.outputs.unknowns[1].index, 4);
assert_eq!(x.derivatives.unknowns[0].index, 7);
assert_eq!(x.derivatives.unknowns[1].index, 8);
assert_eq!(x.initial_unknowns.unknowns[0].index, 3);
assert_eq!(x.initial_unknowns.unknowns[1].index, 4);
assert_eq!(x.initial_unknowns.unknowns[2].index, 7);
assert_eq!(x.initial_unknowns.unknowns[2].dependencies, vec! {5, 2});
assert_eq!(x.initial_unknowns.unknowns[3].dependencies, vec! {5, 6});
}
#[test]
fn test_model_description() {
let s = r##"
<?xml version="1.0" encoding="UTF8"?>
<fmiModelDescription
fmiVersion="2.0"
modelName="MyLibrary.SpringMassDamper"
guid="{8c4e810f-3df3-4a00-8276-176fa3c9f9e0}"
description="Rotational Spring Mass Damper System"
version="1.0"
generationDateAndTime="2011-09-23T16:57:33Z"
variableNamingConvention="structured"
numberOfEventIndicators="2">
<ModelVariables>
<ScalarVariable name="x[1]" valueReference="0" initial="exact"> <Real/> </ScalarVariable> <!-- idex="5" -->
<ScalarVariable name="x[2]" valueReference="1" initial="exact"> <Real/> </ScalarVariable> <!-- index="6" -->
<ScalarVariable name="PI.x" valueReference="46" description="State of block" causality="local" variability="continuous" initial="calculated">
<Real relativeQuantity="false" />
</ScalarVariable>
<ScalarVariable name="der(PI.x)" valueReference="45" causality="local" variability="continuous" initial="calculated">
<Real relativeQuantity="false" derivative="3" />
</ScalarVariable>
</ModelVariables>
<ModelStructure>
<Outputs><Unknown index="1" dependencies="1 2" /><Unknown index="2" /></Outputs>
<Derivatives><Unknown index="4" dependencies="1 2" /></Derivatives>
<InitialUnknowns />
</ModelStructure>
</fmiModelDescription>
"##;
let x: ModelDescription = serde_xml_rs::from_str(s).expect("hello");
assert_eq!(x.fmi_version, "2.0");
assert_eq!(x.model_name, "MyLibrary.SpringMassDamper");
assert_eq!(x.guid, "{8c4e810f-3df3-4a00-8276-176fa3c9f9e0}");
assert_eq!(x.description, "Rotational Spring Mass Damper System");
assert_eq!(x.version, "1.0");
assert_eq!(x.variable_naming_convention, "structured");
assert_eq!(x.number_of_event_indicators, 2);
assert_eq!(x.model_variables.variables.len(), 4);
let outputs = x.outputs().unwrap();
assert_eq!(outputs[0].0.name, "x[1]");
assert_eq!(outputs[0].1.len(), 2);
assert_eq!(outputs[0].1[0].name, "x[1]");
assert_eq!(outputs[1].0.name, "x[2]");
assert_eq!(outputs[1].1.len(), 0);
let derivatives = x.derivatives().unwrap();
assert_eq!(derivatives[0].0.name, "der(PI.x)");
assert_eq!(
derivatives[0].0.elem,
ScalarVariableElement::Real {
declared_type: None,
start: 0.0,
relative_quantity: false,
derivative: Some(3)
}
);
let states = x.continuous_states().unwrap();
assert_eq!(
states
.iter()
.map(|(der, state)| (der.name.as_str(), state.name.as_str()))
.collect::<Vec<(_, _)>>(),
vec![("PI.x", "der(PI.x)")]
);
}
}