use fmi::fmi3::{binding, schema};
use crate::fmi3::types::{Binary, Clock};
pub enum StartValue<T> {
Scalar(T),
Vector(Vec<T>),
}
impl<T> From<T> for StartValue<T> {
fn from(value: T) -> Self {
StartValue::Scalar(value)
}
}
impl<T> From<Vec<T>> for StartValue<T> {
fn from(value: Vec<T>) -> Self {
StartValue::Vector(value)
}
}
impl<T, const N: usize> From<[T; N]> for StartValue<T> {
fn from(value: [T; N]) -> Self {
StartValue::Vector(value.into())
}
}
impl<T: Clone> From<StartValue<T>> for Vec<T> {
fn from(value: StartValue<T>) -> Self {
match value {
StartValue::Scalar(v) => vec![v],
StartValue::Vector(v) => v,
}
}
}
impl<const N: usize> From<&[u8; N]> for StartValue<Vec<u8>> {
fn from(value: &[u8; N]) -> Self {
StartValue::Scalar(value.to_vec())
}
}
impl From<&[u8]> for StartValue<Vec<u8>> {
fn from(value: &[u8]) -> Self {
StartValue::Scalar(value.to_vec())
}
}
pub struct VariableBuilder<T>
where
T: FmiVariableBuilder,
{
name: String,
value_reference: binding::fmi3ValueReference,
description: Option<String>,
causality: Option<schema::Causality>,
variability: Option<schema::Variability>,
can_handle_multiple_set_per_time_instant: Option<bool>,
intermediate_update: Option<bool>,
previous: Option<u32>,
declared_type: Option<String>,
initial: Option<schema::Initial>,
start: Option<T::Start>,
derivative: Option<u32>, reinit: Option<bool>,
min: Option<f64>,
max: Option<f64>,
nominal: Option<f64>,
quantity: Option<String>,
max_size: Option<usize>,
mime_type: Option<String>,
clocks: Option<Vec<u32>>,
interval_variability: Option<schema::IntervalVariability>,
dimensions: Vec<schema::Dimension>,
_phantom: std::marker::PhantomData<T>,
}
impl<T> VariableBuilder<T>
where
T: FmiVariableBuilder,
{
pub fn new(name: impl Into<String>, value_reference: binding::fmi3ValueReference) -> Self {
Self {
name: name.into(),
value_reference,
description: None,
causality: None,
variability: None,
can_handle_multiple_set_per_time_instant: None,
intermediate_update: None,
previous: None,
declared_type: None,
initial: None,
start: None,
derivative: None,
reinit: None,
min: None,
max: None,
nominal: None,
quantity: None,
max_size: None,
mime_type: None,
clocks: None,
interval_variability: None,
dimensions: Vec::new(),
_phantom: std::marker::PhantomData,
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_causality(mut self, causality: schema::Causality) -> Self {
self.causality = Some(causality);
self
}
pub fn with_variability(mut self, variability: schema::Variability) -> Self {
self.variability = Some(variability);
self
}
pub fn with_can_handle_multiple_set_per_time_instant(mut self, value: bool) -> Self {
self.can_handle_multiple_set_per_time_instant = Some(value);
self
}
pub fn with_intermediate_update(mut self, value: bool) -> Self {
self.intermediate_update = Some(value);
self
}
pub fn with_previous(mut self, value_reference: u32) -> Self {
self.previous = Some(value_reference);
self
}
pub fn with_declared_type(mut self, type_name: impl Into<String>) -> Self {
self.declared_type = Some(type_name.into());
self
}
pub fn with_initial(mut self, initial: schema::Initial) -> Self {
self.initial = Some(initial);
self
}
pub fn with_start(mut self, start: impl Into<T::Start>) -> Self {
self.start = Some(start.into());
self
}
pub fn with_derivative(mut self, derivative_vr: u32) -> Self {
self.derivative = Some(derivative_vr);
self
}
pub fn with_reinit(mut self, reinit: bool) -> Self {
self.reinit = Some(reinit);
self
}
pub fn with_min(mut self, min: f64) -> Self {
self.min = Some(min);
self
}
pub fn with_max(mut self, max: f64) -> Self {
self.max = Some(max);
self
}
pub fn with_nominal(mut self, nominal: f64) -> Self {
self.nominal = Some(nominal);
self
}
pub fn with_quantity(mut self, quantity: impl Into<String>) -> Self {
self.quantity = Some(quantity.into());
self
}
pub fn with_dimensions(mut self, dimensions: Vec<schema::Dimension>) -> Self {
self.dimensions = dimensions;
self
}
pub fn with_max_size(mut self, max_size: usize) -> Self {
self.max_size = Some(max_size);
self
}
pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
self.mime_type = Some(mime_type.into());
self
}
pub fn with_clocks(mut self, clocks: Vec<u32>) -> Self {
self.clocks = Some(clocks);
self
}
pub fn with_interval_variability(
mut self,
interval_variability: schema::IntervalVariability,
) -> Self {
self.interval_variability = Some(interval_variability);
self
}
pub fn finish(self) -> T::Var {
T::finish(self)
}
}
pub trait FmiVariableBuilder: Sized {
type Var: schema::AbstractVariableTrait;
type Start;
fn variable(name: impl Into<String>, value_reference: u32) -> VariableBuilder<Self> {
VariableBuilder::new(name, value_reference)
}
fn finish(builder: VariableBuilder<Self>) -> Self::Var;
}
macro_rules! impl_fmi_variable_builder_float {
($primitive_type:ty, $fmi_type:ty, $default_variability:expr) => {
impl FmiVariableBuilder for $primitive_type {
type Var = $fmi_type;
type Start = StartValue<Self>;
fn finish(builder: VariableBuilder<Self>) -> Self::Var {
let mut var = <$fmi_type>::new(
builder.name,
builder.value_reference,
if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
None
} else {
builder.description
},
builder.causality.unwrap_or(schema::Causality::Local),
builder.variability.unwrap_or($default_variability),
builder.start.map(Into::into),
builder.initial,
);
if let Some(derivative) = builder.derivative {
var.derivative = Some(derivative);
}
if let Some(reinit) = builder.reinit {
var.reinit = Some(reinit);
}
if !builder.dimensions.is_empty() {
var.dimensions = builder.dimensions;
}
var
}
}
};
}
macro_rules! impl_fmi_variable_builder_int {
($primitive_type:ty, $fmi_type:ty) => {
impl FmiVariableBuilder for $primitive_type {
type Var = $fmi_type;
type Start = StartValue<Self>;
fn finish(builder: VariableBuilder<Self>) -> Self::Var {
let mut var = <$fmi_type>::new(
builder.name,
builder.value_reference,
if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
None
} else {
builder.description
},
builder.causality.unwrap_or(schema::Causality::Local),
builder.variability.unwrap_or(schema::Variability::Discrete),
builder.start.map(Into::into),
builder.initial,
);
if !builder.dimensions.is_empty() {
var.dimensions = builder.dimensions;
}
var
}
}
};
}
impl_fmi_variable_builder_float!(f32, schema::FmiFloat32, schema::Variability::Continuous);
impl_fmi_variable_builder_float!(f64, schema::FmiFloat64, schema::Variability::Continuous);
impl_fmi_variable_builder_int!(i8, schema::FmiInt8);
impl_fmi_variable_builder_int!(u8, schema::FmiUInt8);
impl_fmi_variable_builder_int!(i16, schema::FmiInt16);
impl_fmi_variable_builder_int!(u16, schema::FmiUInt16);
impl_fmi_variable_builder_int!(i32, schema::FmiInt32);
impl_fmi_variable_builder_int!(u32, schema::FmiUInt32);
impl FmiVariableBuilder for bool {
type Var = schema::FmiBoolean;
type Start = StartValue<Self>;
fn finish(builder: VariableBuilder<Self>) -> Self::Var {
let mut var = schema::FmiBoolean::new(
builder.name,
builder.value_reference,
if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
None
} else {
builder.description
},
builder.causality.unwrap_or(schema::Causality::Local),
builder.variability.unwrap_or(schema::Variability::Discrete),
builder.start.map(Into::into),
builder.initial,
);
if !builder.dimensions.is_empty() {
var.dimensions = builder.dimensions;
}
var
}
}
impl FmiVariableBuilder for String {
type Var = schema::FmiString;
type Start = StartValue<Self>;
fn finish(builder: VariableBuilder<Self>) -> Self::Var {
let mut var = schema::FmiString::new(
builder.name,
builder.value_reference,
if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
None
} else {
builder.description
},
builder.causality.unwrap_or(schema::Causality::Local),
builder.variability.unwrap_or(schema::Variability::Discrete),
builder.start.map(Into::into),
builder.initial,
);
if !builder.dimensions.is_empty() {
var.dimensions = builder.dimensions;
}
var
}
}
impl<const N: usize, T> FmiVariableBuilder for [T; N]
where
T: FmiVariableBuilder,
T::Var: schema::ArrayableVariableTrait,
T::Start: Into<Vec<T>>,
{
type Var = T::Var;
type Start = T::Start;
fn finish(mut builder: VariableBuilder<Self>) -> Self::Var {
builder.dimensions.push(schema::Dimension::Fixed(N as _));
let element_builder = VariableBuilder::<T> {
name: builder.name,
value_reference: builder.value_reference,
description: builder.description,
causality: builder.causality,
variability: builder.variability,
can_handle_multiple_set_per_time_instant: builder
.can_handle_multiple_set_per_time_instant,
intermediate_update: builder.intermediate_update,
previous: builder.previous,
declared_type: builder.declared_type,
initial: builder.initial,
start: builder.start,
derivative: builder.derivative,
reinit: builder.reinit,
min: builder.min,
max: builder.max,
nominal: builder.nominal,
quantity: builder.quantity,
max_size: builder.max_size,
mime_type: builder.mime_type,
clocks: builder.clocks,
interval_variability: builder.interval_variability,
dimensions: builder.dimensions,
_phantom: std::marker::PhantomData,
};
T::finish(element_builder)
}
}
impl<T> FmiVariableBuilder for Vec<T>
where
T: FmiVariableBuilder,
T::Var: schema::ArrayableVariableTrait,
T::Start: Into<Vec<T>>,
{
type Var = T::Var;
type Start = T::Start;
fn finish(mut builder: VariableBuilder<Self>) -> Self::Var {
builder.dimensions.push(schema::Dimension::Variable(0));
let element_builder = VariableBuilder::<T> {
name: builder.name,
value_reference: builder.value_reference,
description: builder.description,
causality: builder.causality,
variability: builder.variability,
can_handle_multiple_set_per_time_instant: builder
.can_handle_multiple_set_per_time_instant,
intermediate_update: builder.intermediate_update,
previous: builder.previous,
declared_type: builder.declared_type,
initial: builder.initial,
start: builder.start,
derivative: builder.derivative,
reinit: builder.reinit,
min: builder.min,
max: builder.max,
nominal: builder.nominal,
quantity: builder.quantity,
max_size: builder.max_size,
mime_type: builder.mime_type,
clocks: builder.clocks,
interval_variability: builder.interval_variability,
dimensions: builder.dimensions,
_phantom: std::marker::PhantomData,
};
T::finish(element_builder)
}
}
impl FmiVariableBuilder for Clock {
type Var = schema::FmiClock;
type Start = ();
fn finish(builder: VariableBuilder<Self>) -> Self::Var {
let var = schema::FmiClock::new(
builder.name,
builder.value_reference,
builder.description,
builder.causality.unwrap_or(schema::Causality::Local),
builder.variability.unwrap_or_default(),
);
let mut var = var;
var.interval_variability = Some(
builder
.interval_variability
.unwrap_or(schema::IntervalVariability::Triggered),
);
var
}
}
impl FmiVariableBuilder for Binary {
type Var = schema::FmiBinary;
type Start = StartValue<Vec<u8>>;
fn finish(builder: VariableBuilder<Self>) -> Self::Var {
let start_values = builder.start.map(|start| {
let vec_values: Vec<Vec<u8>> = start.into();
vec_values
.into_iter()
.map(|bytes| {
bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>()
})
.collect()
});
let mut var = schema::FmiBinary::new(
builder.name,
builder.value_reference,
builder.description,
builder.causality.unwrap_or(schema::Causality::Local),
builder.variability.unwrap_or(schema::Variability::Discrete),
start_values,
builder.initial,
);
if let Some(max_size) = builder.max_size {
var.max_size = Some(max_size as u32);
}
if let Some(mime_type) = builder.mime_type {
var.mime_type = Some(mime_type);
}
if let Some(clocks) = builder.clocks {
var.clocks = Some(fmi::schema::utils::AttrList(clocks));
}
if !builder.dimensions.is_empty() {
var.dimensions = builder.dimensions;
}
var
}
}
#[cfg(test)]
mod tests {
use fmi::schema::fmi3::{AbstractVariableTrait, ArrayableVariableTrait};
use super::*;
#[test]
fn test_scalar() {
let var_f1 = <f64 as FmiVariableBuilder>::variable("f1", 0)
.with_description("Description for f1")
.with_causality(schema::Causality::Parameter)
.with_variability(schema::Variability::Tunable)
.with_start(0.0)
.finish();
assert_eq!(var_f1.dimensions(), &[]);
}
#[test]
fn test_array1() {
let var_f2 = <[u16; 2] as FmiVariableBuilder>::variable("f2", 0)
.with_description("Description for f2")
.with_causality(schema::Causality::Parameter)
.with_variability(schema::Variability::Tunable)
.with_start(vec![0u16, 1])
.finish();
assert_eq!(var_f2.dimensions(), &[schema::Dimension::Fixed(2)]);
}
#[test]
fn test_builder_pattern() {
let var = <f64 as FmiVariableBuilder>::variable("test_var", 42)
.with_description("A test variable")
.with_causality(schema::Causality::Output)
.with_variability(schema::Variability::Continuous)
.with_start(1.5)
.with_initial(schema::Initial::Exact)
.finish();
assert_eq!(var.name(), "test_var");
assert_eq!(var.value_reference(), 42);
assert_eq!(var.description(), Some("A test variable"));
assert_eq!(var.causality(), schema::Causality::Output);
assert_eq!(var.variability(), schema::Variability::Continuous);
}
#[test]
fn test_builder_with_defaults() {
let var = <f64 as FmiVariableBuilder>::variable("minimal", 0)
.with_start(0.0)
.finish();
assert_eq!(var.name(), "minimal");
assert_eq!(var.value_reference(), 0);
assert_eq!(var.causality(), schema::Causality::Local);
assert_eq!(var.variability(), schema::Variability::Continuous);
}
#[test]
fn test_float_specific_attributes() {
let var = <f64 as FmiVariableBuilder>::variable("state_derivative", 10)
.with_causality(schema::Causality::Local)
.with_variability(schema::Variability::Continuous)
.with_derivative(5) .with_reinit(true)
.with_min(-100.0)
.with_max(100.0)
.with_nominal(10.0)
.with_start(0.0)
.with_initial(schema::Initial::Calculated)
.finish();
assert_eq!(var.name(), "state_derivative");
assert_eq!(var.value_reference(), 10);
assert_eq!(var.derivative(), Some(5));
assert_eq!(var.reinit(), Some(true));
}
#[test]
fn test_bool_type_creates_fmi_boolean() {
let var = <bool as FmiVariableBuilder>::variable("flag", 20)
.with_causality(schema::Causality::Output)
.with_variability(schema::Variability::Discrete)
.with_start(false)
.finish();
assert_eq!(var.name(), "flag");
assert_eq!(var.value_reference(), 20);
assert_eq!(var.causality(), schema::Causality::Output);
assert_eq!(var.variability(), schema::Variability::Discrete);
}
#[test]
fn test_comprehensive_builder() {
let var = <f64 as FmiVariableBuilder>::variable("comprehensive", 100)
.with_description("A comprehensive test variable")
.with_causality(schema::Causality::Parameter)
.with_variability(schema::Variability::Tunable)
.with_can_handle_multiple_set_per_time_instant(true)
.with_intermediate_update(false)
.with_declared_type("CustomFloat64Type")
.with_start(42.0)
.with_initial(schema::Initial::Exact)
.with_min(0.0)
.with_max(1000.0)
.with_nominal(100.0)
.finish();
assert_eq!(var.name(), "comprehensive");
assert_eq!(var.value_reference(), 100);
assert_eq!(var.description(), Some("A comprehensive test variable"));
assert_eq!(var.causality(), schema::Causality::Parameter);
assert_eq!(var.variability(), schema::Variability::Tunable);
}
}