fmi_schema/fmi3/
variable.rs

1use yaserde_derive::{YaDeserialize, YaSerialize};
2
3use super::{
4    Float32Attributes, Float64Attributes, Int8Attributes, Int16Attributes, Int32Attributes,
5    Int64Attributes, IntegerBaseAttributes, RealBaseAttributes, RealVariableAttributes,
6    UInt8Attributes, UInt16Attributes, UInt32Attributes, UInt64Attributes,
7};
8
9use crate::{Error, default_wrapper};
10
11/// An enumeration that defines the type of a variable.
12#[derive(Debug, PartialEq)]
13pub enum VariableType {
14    FmiFloat32,
15    FmiFloat64,
16    FmiInt8,
17    FmiUInt8,
18    FmiInt16,
19    FmiUInt16,
20    FmiInt32,
21    FmiUInt32,
22    FmiInt64,
23    FmiUInt64,
24    FmiBoolean,
25    FmiString,
26    FmiBinary,
27}
28
29#[cfg(feature = "arrow")]
30impl From<VariableType> for arrow::datatypes::DataType {
31    fn from(v: VariableType) -> Self {
32        match v {
33            VariableType::FmiFloat32 => arrow::datatypes::DataType::Float32,
34            VariableType::FmiFloat64 => arrow::datatypes::DataType::Float64,
35            VariableType::FmiInt8 => arrow::datatypes::DataType::Int8,
36            VariableType::FmiUInt8 => arrow::datatypes::DataType::UInt8,
37            VariableType::FmiInt16 => arrow::datatypes::DataType::Int16,
38            VariableType::FmiUInt16 => arrow::datatypes::DataType::UInt16,
39            VariableType::FmiInt32 => arrow::datatypes::DataType::Int32,
40            VariableType::FmiUInt32 => arrow::datatypes::DataType::UInt32,
41            VariableType::FmiInt64 => arrow::datatypes::DataType::Int64,
42            VariableType::FmiUInt64 => arrow::datatypes::DataType::UInt64,
43            VariableType::FmiBoolean => arrow::datatypes::DataType::Boolean,
44            VariableType::FmiString => arrow::datatypes::DataType::Utf8,
45            VariableType::FmiBinary => arrow::datatypes::DataType::Binary,
46        }
47    }
48}
49
50pub trait AbstractVariableTrait {
51    /// The full, unique name of the variable.
52    fn name(&self) -> &str;
53    /// A handle of the variable to efficiently identify the variable value in the model interface
54    /// and for references within the modelDescription.xml
55    fn value_reference(&self) -> u32;
56    /// An optional description string describing the meaning of the variable.
57    fn description(&self) -> Option<&str>;
58    /// Enumeration that defines the causality of the variable.
59    fn causality(&self) -> Causality;
60    fn variability(&self) -> Variability;
61    fn can_handle_multiple_set_per_time_instant(&self) -> Option<bool>;
62    fn data_type(&self) -> VariableType;
63}
64
65pub trait ArrayableVariableTrait: AbstractVariableTrait {
66    fn dimensions(&self) -> &[Dimension];
67    fn intermediate_update(&self) -> Option<bool>;
68    fn previous(&self) -> Option<u32>;
69}
70
71pub trait TypedArrayableVariableTrait: ArrayableVariableTrait {
72    fn declared_type(&self) -> Option<&str>;
73}
74
75pub trait InitializableVariableTrait: TypedArrayableVariableTrait {
76    fn initial(&self) -> Option<Initial>;
77}
78
79macro_rules! impl_abstract_variable {
80    ($name:ident, $default_variability:expr) => {
81        impl AbstractVariableTrait for $name {
82            fn name(&self) -> &str {
83                &self
84                    .init_var
85                    .typed_arrayable_var
86                    .arrayable_var
87                    .abstract_var
88                    .name
89            }
90            fn value_reference(&self) -> u32 {
91                self.init_var
92                    .typed_arrayable_var
93                    .arrayable_var
94                    .abstract_var
95                    .value_reference
96            }
97            fn description(&self) -> Option<&str> {
98                self.init_var
99                    .typed_arrayable_var
100                    .arrayable_var
101                    .abstract_var
102                    .description
103                    .as_deref()
104            }
105            fn causality(&self) -> Causality {
106                self.init_var
107                    .typed_arrayable_var
108                    .arrayable_var
109                    .abstract_var
110                    .causality
111            }
112            fn variability(&self) -> Variability {
113                self.init_var
114                    .typed_arrayable_var
115                    .arrayable_var
116                    .abstract_var
117                    .variability
118                    .unwrap_or($default_variability)
119            }
120            fn can_handle_multiple_set_per_time_instant(&self) -> Option<bool> {
121                self.init_var
122                    .typed_arrayable_var
123                    .arrayable_var
124                    .abstract_var
125                    .can_handle_multiple_set_per_time_instant
126            }
127            fn data_type(&self) -> VariableType {
128                VariableType::$name
129            }
130        }
131    };
132}
133
134macro_rules! impl_arrayable_variable {
135    ($name:ident) => {
136        impl ArrayableVariableTrait for $name {
137            fn dimensions(&self) -> &[Dimension] {
138                &self.init_var.typed_arrayable_var.arrayable_var.dimensions
139            }
140            fn intermediate_update(&self) -> Option<bool> {
141                self.init_var
142                    .typed_arrayable_var
143                    .arrayable_var
144                    .intermediate_update
145            }
146            fn previous(&self) -> Option<u32> {
147                self.init_var.typed_arrayable_var.arrayable_var.previous
148            }
149        }
150    };
151}
152
153macro_rules! impl_typed_arrayable_variable {
154    ($name:ident) => {
155        impl TypedArrayableVariableTrait for $name {
156            fn declared_type(&self) -> Option<&str> {
157                self.init_var.typed_arrayable_var.declared_type.as_deref()
158            }
159        }
160    };
161}
162
163macro_rules! impl_initializable_variable {
164    ($name:ident) => {
165        impl InitializableVariableTrait for $name {
166            fn initial(&self) -> Option<Initial> {
167                self.init_var.initial
168            }
169        }
170    };
171}
172
173macro_rules! impl_float_type {
174    ($name:ident, $root:literal, $type:ty, $float_attr:ident) => {
175        #[derive(Default, PartialEq, Debug, YaDeserialize, YaSerialize)]
176        #[yaserde(rename = $root)]
177        pub struct $name {
178            #[yaserde(flatten = true)]
179            pub base_attr: RealBaseAttributes,
180            #[yaserde(flatten = true)]
181            pub attr: $float_attr,
182            #[yaserde(flatten = true)]
183            pub init_var: InitializableVariable,
184            #[yaserde(attribute = true, rename = "start")]
185            pub start: Vec<$type>,
186            #[yaserde(flatten = true)]
187            pub real_var_attr: RealVariableAttributes,
188        }
189
190        impl_abstract_variable!($name, Variability::Continuous);
191        impl_arrayable_variable!($name);
192        impl_typed_arrayable_variable!($name);
193        impl_initializable_variable!($name);
194
195        impl $name {
196            pub fn start(&self) -> &[$type] {
197                &self.start
198            }
199
200            pub fn derivative(&self) -> Option<u32> {
201                self.real_var_attr.derivative
202            }
203
204            pub fn reinit(&self) -> Option<bool> {
205                self.real_var_attr.reinit
206            }
207        }
208    };
209}
210
211macro_rules! impl_integer_type {
212    ($name:ident, $root:literal, $type:ty, $int_attr:ident) => {
213        #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
214        #[yaserde(rename = $root)]
215        pub struct $name {
216            #[yaserde(flatten = true)]
217            pub base_attr: IntegerBaseAttributes,
218            #[yaserde(flatten = true)]
219            pub int_attr: $int_attr,
220            /// Initial or guess value of the variable. During instantiation, the FMU initializes its variables with their start values.
221            #[yaserde(attribute = true)]
222            pub start: Option<$type>,
223            #[yaserde(flatten = true)]
224            pub init_var: InitializableVariable,
225        }
226
227        impl_abstract_variable!($name, Variability::Discrete);
228    };
229}
230
231/// Enumeration that defines the causality of the variable.
232#[derive(Clone, Copy, Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
233pub enum Causality {
234    /// A data value that is constant during the simulation
235    #[yaserde(rename = "parameter")]
236    Parameter,
237    /// A data value that is constant during the simulation and is computed during initialization
238    /// or when tunable parameters change.
239    #[yaserde(rename = "calculatedParameter")]
240    CalculatedParameter,
241    /// The variable value can be provided by the importer.
242    #[yaserde(rename = "input")]
243    Input,
244    /// The values of these variables are computed in the FMU and they are designed to be used outside the FMU.
245    #[yaserde(rename = "output")]
246    Output,
247    /// Local variables of the FMU that must not be used for FMU connections
248    #[yaserde(rename = "local")]
249    #[default]
250    Local,
251    /// The independent variable (usually time [but could also be, for example, angle]).
252    #[yaserde(rename = "independent")]
253    Independent,
254    #[yaserde(rename = "dependent")]
255    Dependent,
256    /// The variable value can only be changed in Configuration Mode or Reconfiguration Mode.
257    #[yaserde(rename = "structuralParameter")]
258    StructuralParameter,
259}
260
261/// Enumeration that defines the time dependency of the variable, in other words, it defines the
262/// time instants when a variable may be changed by the importer or may change its value due to FMU
263/// internal computations, depending on their causality.
264///
265/// See [https://fmi-standard.org/docs/3.0.1/#variability]
266#[derive(Clone, Copy, Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
267pub enum Variability {
268    /// The value of the variable never changes.
269    #[yaserde(rename = "constant")]
270    Constant,
271    /// The value of the variable is fixed in super state Initialized, in other words, after
272    /// [`exit_initialization_mode()`] was called the variable value does not change anymore. The
273    /// default for variables of causality [`Causality::Parameter`],
274    /// [`Causality::StructuredParameter`] or [`Causality::CalculatedParameter`] is `Fixed`.
275    #[yaserde(rename = "fixed")]
276    Fixed,
277    /// The value of the variable is constant between events (ME and CS if Event Mode is supported)
278    /// and between communication points (CS and SE). A parameter with variability = tunable
279    /// may be changed only in Event Mode or, if Event Mode is not supported, at communication
280    /// points (CS and SE).
281    #[yaserde(rename = "tunable")]
282    Tunable,
283    /// * Model Exchange: The value of the variable may change only in Event Mode.
284    /// * Co-Simulation: If Event Mode is used (see `event_mode_used`), the value of the variable
285    ///   may only change in Event Mode. If Event Mode is not used, the value may change at
286    ///   communication points and the FMU must detect and handle such events internally. During
287    ///   Intermediate Update Mode, discrete variables are not allowed to change.
288    /// * Scheduled Execution: The value may change only at communication points.
289    #[yaserde(rename = "discrete")]
290    #[default]
291    Discrete,
292    /// Only variables of type [`FmiFloat32`]or [`FmiFloat64`] may be continuous. The default for
293    /// variables of type `FmiFloat32` and `FmiFloat64` and causality other than
294    /// [`Causality::Parameter`], [`Causality::StructuredParameter`] or
295    /// [`Causality::CalculatedParameter`] is continuous. Variables with variability continuous
296    /// may change in Initialization Mode and in super state Initialized.
297    #[yaserde(rename = "continuous")]
298    Continuous,
299}
300
301#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
302pub struct Dimension {
303    /// Defines a constant unsigned 64-bit integer size for this dimension. The variability of the
304    /// dimension size is constant in this case.
305    #[yaserde(attribute = true)]
306    pub start: Option<u64>,
307    /// If the present, it defines the size of this dimension to be the value of the variable with
308    /// the value reference given by the `value_reference` attribute. The referenced variable
309    /// must be a variable of type `UInt64`, and must either be a constant (i.e. with
310    /// variability = constant) or a structural parameter (i.e. with causality =
311    /// structuralParameter). The variability of the dimension size is in this case the variability
312    /// of the referenced variable. A structural parameter must be a variable of type `UInt64`
313    /// only if it is referenced in `Dimension`.
314    #[yaserde(attribute = true, rename = "valueReference")]
315    pub value_reference: Option<u32>,
316}
317
318#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
319pub struct AbstractVariable {
320    #[yaserde(attribute = true)]
321    pub name: String,
322    #[yaserde(attribute = true, rename = "valueReference")]
323    pub value_reference: u32,
324    #[yaserde(attribute = true)]
325    pub description: Option<String>,
326    #[yaserde(attribute = true, default = "default_wrapper")]
327    pub causality: Causality,
328    #[yaserde(attribute = true)]
329    pub variability: Option<Variability>,
330    #[yaserde(attribute = true, rename = "canHandleMultipleSetPerTimeInstant")]
331    pub can_handle_multiple_set_per_time_instant: Option<bool>,
332}
333
334#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
335pub struct ArrayableVariable {
336    #[yaserde(flatten = true)]
337    pub abstract_var: AbstractVariable,
338    /// Each `Dimension` element specifies the size of one dimension of the array
339    #[yaserde(rename = "Dimension")]
340    pub dimensions: Vec<Dimension>,
341    #[yaserde(attribute = true, rename = "intermediateUpdate")]
342    pub intermediate_update: Option<bool>,
343    #[yaserde(attribute = true, rename = "previous")]
344    pub previous: Option<u32>,
345}
346
347#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
348pub struct TypedArrayableVariable {
349    #[yaserde(flatten = true)]
350    pub arrayable_var: ArrayableVariable,
351    #[yaserde(attribute = true, rename = "declaredType")]
352    pub declared_type: Option<String>,
353}
354
355#[derive(Clone, Copy, Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
356pub enum Initial {
357    #[yaserde(rename = "exact")]
358    #[default]
359    Exact,
360    #[yaserde(rename = "approx")]
361    Approx,
362    #[yaserde(rename = "calculated")]
363    Calculated,
364}
365
366#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
367pub struct InitializableVariable {
368    #[yaserde(flatten = true)]
369    pub typed_arrayable_var: TypedArrayableVariable,
370    #[yaserde(attribute = true)]
371    pub initial: Option<Initial>,
372}
373
374impl_float_type!(FmiFloat32, "Float32", f32, Float32Attributes);
375impl_float_type!(FmiFloat64, "Float64", f64, Float64Attributes);
376
377impl_integer_type!(FmiInt8, "Int8", i8, Int8Attributes);
378impl_arrayable_variable!(FmiInt8);
379impl_typed_arrayable_variable!(FmiInt8);
380impl_initializable_variable!(FmiInt8);
381
382impl_integer_type!(FmiUInt8, "UInt8", u8, UInt8Attributes);
383impl_arrayable_variable!(FmiUInt8);
384impl_typed_arrayable_variable!(FmiUInt8);
385impl_initializable_variable!(FmiUInt8);
386
387impl_integer_type!(FmiInt16, "Int16", i16, Int16Attributes);
388impl_arrayable_variable!(FmiInt16);
389impl_typed_arrayable_variable!(FmiInt16);
390impl_initializable_variable!(FmiInt16);
391
392impl_integer_type!(FmiUInt16, "UInt16", u16, UInt16Attributes);
393impl_arrayable_variable!(FmiUInt16);
394impl_typed_arrayable_variable!(FmiUInt16);
395impl_initializable_variable!(FmiUInt16);
396
397impl_integer_type!(FmiInt32, "Int32", i32, Int32Attributes);
398impl_arrayable_variable!(FmiInt32);
399impl_typed_arrayable_variable!(FmiInt32);
400impl_initializable_variable!(FmiInt32);
401
402impl_integer_type!(FmiUInt32, "UInt32", u32, UInt32Attributes);
403impl_arrayable_variable!(FmiUInt32);
404impl_typed_arrayable_variable!(FmiUInt32);
405impl_initializable_variable!(FmiUInt32);
406
407impl_integer_type!(FmiInt64, "Int64", i64, Int64Attributes);
408impl_arrayable_variable!(FmiInt64);
409impl_typed_arrayable_variable!(FmiInt64);
410impl_initializable_variable!(FmiInt64);
411
412impl_integer_type!(FmiUInt64, "UInt64", u64, UInt64Attributes);
413impl_arrayable_variable!(FmiUInt64);
414impl_typed_arrayable_variable!(FmiUInt64);
415impl_initializable_variable!(FmiUInt64);
416
417#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
418pub struct FmiBoolean {
419    #[yaserde(attribute = true, flatten = true)]
420    pub start: Vec<bool>,
421    #[yaserde(flatten = true)]
422    pub init_var: InitializableVariable,
423}
424
425impl_abstract_variable!(FmiBoolean, Variability::Discrete);
426impl_arrayable_variable!(FmiBoolean);
427impl_typed_arrayable_variable!(FmiBoolean);
428impl_initializable_variable!(FmiBoolean);
429
430#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
431pub struct StringStart {
432    #[yaserde(attribute = true, rename = "value")]
433    pub value: String,
434}
435
436#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
437pub struct FmiString {
438    #[yaserde(rename = "Start")]
439    pub start: Vec<StringStart>,
440    #[yaserde(flatten = true)]
441    pub init_var: InitializableVariable,
442}
443
444impl FmiString {
445    /// Get an iterator over the start values.
446    pub fn start(&self) -> impl Iterator<Item = &str> {
447        self.start.iter().map(|s| s.value.as_str())
448    }
449}
450
451impl_abstract_variable!(FmiString, Variability::Discrete);
452impl_arrayable_variable!(FmiString);
453impl_typed_arrayable_variable!(FmiString);
454impl_initializable_variable!(FmiString);
455
456#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
457pub struct BinaryStart {
458    #[yaserde(attribute = true, rename = "value")]
459    pub value: String,
460}
461
462impl BinaryStart {
463    /// Parse the hex string into a byte vector.
464    pub fn as_bytes(&self) -> Result<Vec<u8>, Error> {
465        let raw: &str = self.value.as_ref();
466        let s = raw
467            .strip_prefix("0x")
468            .or_else(|| raw.strip_prefix("0X"))
469            .unwrap_or(raw);
470        let s: String = s
471            .chars()
472            .filter(|c| !c.is_ascii_whitespace() && *c != '_')
473            .collect();
474        assert!(
475            s.len() % 2 == 0,
476            "hex string must have an even number of digits"
477        );
478        (0..s.len())
479            .step_by(2)
480            .map(|i| u8::from_str_radix(&s[i..i + 2], 16))
481            .collect::<Result<Vec<u8>, _>>()
482            .map_err(|e| Error::Model(format!("failed to parse hex string: {}", e)))
483    }
484}
485
486#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
487pub struct FmiBinary {
488    #[yaserde(rename = "Start")]
489    pub start: Vec<BinaryStart>,
490    #[yaserde(attribute = true, rename = "mimeType", default = "default_mime_type")]
491    pub mime_type: String,
492    #[yaserde(attribute = true, rename = "maxSize")]
493    pub max_size: Option<u32>,
494    #[yaserde(flatten = true)]
495    pub init_var: InitializableVariable,
496}
497
498fn default_mime_type() -> String {
499    "application/octet-stream".into()
500}
501
502impl FmiBinary {
503    /// Get an iterator over the start values.
504    pub fn start(&self) -> impl Iterator<Item = &BinaryStart> {
505        self.start.iter()
506    }
507}
508
509impl_abstract_variable!(FmiBinary, Variability::Discrete);
510impl_arrayable_variable!(FmiBinary);
511impl_typed_arrayable_variable!(FmiBinary);
512impl_initializable_variable!(FmiBinary);
513
514// #[derive(Debug, YaSerialize, YaDeserialize)]
515// #[yaserde(root = "ModelVariables")]
516// pub enum Fmi3Variable {
517// #[yaserde(flatten, rename = "Float32")]
518// Float32(FmiFloat32),
519// #[yaserde(flatten, rename = "Float64")]
520// Float64(FmiFloat64),
521// }
522//
523// impl Default for Fmi3Variable {
524// fn default() -> Self {
525// Fmi3Variable::Float32(FmiFloat32::default())
526// }
527// }
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532
533    #[test]
534    fn test_int16() {
535        let xml = r#"<Int16 name="Int16_input" valueReference="15" causality="input" start="0"/>"#;
536        let var: FmiInt16 = yaserde::de::from_str(xml).unwrap();
537
538        assert_eq!(var.name(), "Int16_input");
539        assert_eq!(var.value_reference(), 15);
540        assert_eq!(var.causality(), Causality::Input);
541        assert_eq!(var.start, Some(0));
542        assert_eq!(var.variability(), Variability::Discrete); // The default for non-float types should be discrete
543    }
544
545    #[test]
546    fn test_float64() {
547        let xml = r#"<Float64
548        name="g"
549        valueReference="5"
550        causality="parameter"
551        variability="fixed"
552        initial="exact"
553        declaredType="Acceleration"
554        start="-9.81"
555        derivative="1"
556        description="Gravity acting on the ball"
557    />"#;
558        let var: FmiFloat64 = yaserde::de::from_str(xml).unwrap();
559
560        assert_eq!(var.name(), "g");
561        assert_eq!(var.value_reference(), 5);
562        assert_eq!(var.variability(), Variability::Fixed);
563        assert_eq!(var.initial(), Some(Initial::Exact));
564        assert_eq!(var.causality(), Causality::Parameter);
565        assert_eq!(var.declared_type(), Some("Acceleration"));
566        assert_eq!(var.start(), &[-9.81]);
567        assert_eq!(var.derivative(), Some(1));
568        assert_eq!(var.description(), Some("Gravity acting on the ball"));
569        assert_eq!(var.can_handle_multiple_set_per_time_instant(), None);
570        assert_eq!(var.intermediate_update(), None);
571    }
572
573    #[test]
574    fn test_dim_f64() {
575        let xml = r#"<Float64
576        name="A"
577        valueReference="4"
578        description="Matrix coefficient A"
579        causality="parameter"
580        variability="tunable"
581        start="1 0 0 0 1 0 0 0 1">
582        <Dimension valueReference="2"/>
583        <Dimension valueReference="2"/>
584        </Float64>"#;
585
586        let var: FmiFloat64 = yaserde::de::from_str(xml).unwrap();
587        assert_eq!(var.name(), "A");
588        assert_eq!(var.value_reference(), 4);
589        assert_eq!(var.variability(), Variability::Tunable);
590        assert_eq!(var.causality(), Causality::Parameter);
591        assert_eq!(var.description(), Some("Matrix coefficient A"));
592        assert_eq!(var.start, vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
593        assert_eq!(var.dimensions().len(), 2);
594        assert_eq!(var.dimensions()[0].value_reference, Some(2));
595    }
596
597    #[test]
598    fn test_string() {
599        let xml = r#"<String name="String_parameter" valueReference="29" causality="parameter" variability="fixed">
600        <Start value="Set me!"/>
601    </String>"#;
602
603        let var: FmiString = yaserde::de::from_str(xml).unwrap();
604        assert_eq!(var.name(), "String_parameter");
605        assert_eq!(var.value_reference(), 29);
606        assert_eq!(var.variability(), Variability::Fixed);
607        assert_eq!(var.causality(), Causality::Parameter);
608        assert_eq!(var.start().next().unwrap(), "Set me!");
609    }
610
611    #[test]
612    fn test_binary() {
613        let xml = r#"
614            <Binary name="Binary_input" valueReference="31" causality="input">
615                <Start value="666f6f"/>
616            </Binary>"#;
617
618        let var: FmiBinary = yaserde::de::from_str(xml).unwrap();
619        assert_eq!(var.name(), "Binary_input");
620        assert_eq!(var.value_reference(), 31);
621        assert_eq!(var.causality(), Causality::Input);
622        let start0 = var.start().next().unwrap();
623        assert_eq!(start0.value.as_str(), "666f6f");
624        assert_eq!(start0.as_bytes(), Ok(vec![0x66, 0x6f, 0x6f]));
625    }
626
627    #[test]
628    fn test_float32() {
629        let xml =
630            r#"<Float32 name="float32_var" valueReference="10" causality="output" start="3.14"/>"#;
631        let var: FmiFloat32 = yaserde::de::from_str(xml).unwrap();
632
633        assert_eq!(var.name(), "float32_var");
634        assert_eq!(var.value_reference(), 10);
635        assert_eq!(var.causality(), Causality::Output);
636        assert_eq!(var.start(), &[3.14]);
637        assert_eq!(var.variability(), Variability::Continuous); // Default for float types
638        assert_eq!(var.derivative(), None);
639        assert_eq!(var.reinit(), None);
640    }
641
642    #[test]
643    fn test_int8() {
644        let xml = r#"<Int8 name="int8_var" valueReference="20" causality="parameter" variability="fixed" start="-128"/>"#;
645        let var: FmiInt8 = yaserde::de::from_str(xml).unwrap();
646
647        assert_eq!(var.name(), "int8_var");
648        assert_eq!(var.value_reference(), 20);
649        assert_eq!(var.causality(), Causality::Parameter);
650        assert_eq!(var.start, Some(-128));
651        assert_eq!(var.variability(), Variability::Fixed);
652    }
653
654    #[test]
655    fn test_uint8() {
656        let xml = r#"<UInt8 name="uint8_var" valueReference="21" causality="local" start="255"/>"#;
657        let var: FmiUInt8 = yaserde::de::from_str(xml).unwrap();
658
659        assert_eq!(var.name(), "uint8_var");
660        assert_eq!(var.value_reference(), 21);
661        assert_eq!(var.causality(), Causality::Local);
662        assert_eq!(var.start, Some(255));
663        assert_eq!(var.variability(), Variability::Discrete); // Default for integer types
664    }
665
666    #[test]
667    fn test_uint16() {
668        let xml = r#"<UInt16 name="uint16_var" valueReference="22" causality="calculatedParameter" start="65535"/>"#;
669        let var: FmiUInt16 = yaserde::de::from_str(xml).unwrap();
670
671        assert_eq!(var.name(), "uint16_var");
672        assert_eq!(var.value_reference(), 22);
673        assert_eq!(var.causality(), Causality::CalculatedParameter);
674        assert_eq!(var.start, Some(65535));
675    }
676
677    #[test]
678    fn test_int32() {
679        let xml = r#"<Int32 name="int32_var" valueReference="23" causality="structuralParameter" variability="tunable" start="-2147483648"/>"#;
680        let var: FmiInt32 = yaserde::de::from_str(xml).unwrap();
681
682        assert_eq!(var.name(), "int32_var");
683        assert_eq!(var.value_reference(), 23);
684        assert_eq!(var.causality(), Causality::StructuralParameter);
685        assert_eq!(var.start, Some(-2147483648));
686        assert_eq!(var.variability(), Variability::Tunable);
687    }
688
689    #[test]
690    fn test_uint32() {
691        let xml = r#"<UInt32 name="uint32_var" valueReference="24" causality="independent" start="4294967295"/>"#;
692        let var: FmiUInt32 = yaserde::de::from_str(xml).unwrap();
693
694        assert_eq!(var.name(), "uint32_var");
695        assert_eq!(var.value_reference(), 24);
696        assert_eq!(var.causality(), Causality::Independent);
697        assert_eq!(var.start, Some(4294967295));
698    }
699
700    #[test]
701    fn test_int64() {
702        let xml = r#"<Int64 name="int64_var" valueReference="25" causality="dependent" start="-9223372036854775808"/>"#;
703        let var: FmiInt64 = yaserde::de::from_str(xml).unwrap();
704
705        assert_eq!(var.name(), "int64_var");
706        assert_eq!(var.value_reference(), 25);
707        assert_eq!(var.causality(), Causality::Dependent);
708        assert_eq!(var.start, Some(-9223372036854775808));
709    }
710
711    #[test]
712    fn test_uint64() {
713        let xml = r#"<UInt64 name="uint64_var" valueReference="26" causality="input" variability="constant" start="18446744073709551615"/>"#;
714        let var: FmiUInt64 = yaserde::de::from_str(xml).unwrap();
715
716        assert_eq!(var.name(), "uint64_var");
717        assert_eq!(var.value_reference(), 26);
718        assert_eq!(var.causality(), Causality::Input);
719        assert_eq!(var.start, Some(18446744073709551615));
720        assert_eq!(var.variability(), Variability::Constant);
721    }
722
723    #[test]
724    fn test_boolean() {
725        let xml = r#"<Boolean name="boolean_var" valueReference="30" causality="output" start="true false true"/>"#;
726        let var: FmiBoolean = yaserde::de::from_str(xml).unwrap();
727
728        assert_eq!(var.name(), "boolean_var");
729        assert_eq!(var.value_reference(), 30);
730        assert_eq!(var.causality(), Causality::Output);
731        assert_eq!(var.start, vec![true, false, true]);
732        assert_eq!(var.variability(), Variability::Discrete); // Default for boolean
733    }
734
735    #[test]
736    fn test_variable_with_all_attributes() {
737        let xml = r#"<Float64
738            name="complex_var"
739            valueReference="100"
740            description="A complex variable with many attributes"
741            causality="output"
742            variability="continuous"
743            canHandleMultipleSetPerTimeInstant="true"
744            intermediateUpdate="false"
745            previous="99"
746            initial="calculated"
747            declaredType="CustomType"
748            start="1.0 2.0"
749            derivative="101"
750            reinit="true">
751            <Dimension start="2"/>
752        </Float64>"#;
753
754        let var: FmiFloat64 = yaserde::de::from_str(xml).unwrap();
755        assert_eq!(var.name(), "complex_var");
756        assert_eq!(var.value_reference(), 100);
757        assert_eq!(
758            var.description(),
759            Some("A complex variable with many attributes")
760        );
761        assert_eq!(var.causality(), Causality::Output);
762        assert_eq!(var.variability(), Variability::Continuous);
763        assert_eq!(var.can_handle_multiple_set_per_time_instant(), Some(true));
764        assert_eq!(var.intermediate_update(), Some(false));
765        assert_eq!(var.previous(), Some(99));
766        assert_eq!(var.initial(), Some(Initial::Calculated));
767        assert_eq!(var.declared_type(), Some("CustomType"));
768        assert_eq!(var.start(), &[1.0, 2.0]);
769        assert_eq!(var.derivative(), Some(101));
770        assert_eq!(var.reinit(), Some(true));
771        assert_eq!(var.dimensions().len(), 1);
772        assert_eq!(var.dimensions()[0].start, Some(2));
773    }
774
775    #[test]
776    fn test_dimension_with_value_reference() {
777        let xml = r#"<Float32
778            name="matrix_var"
779            valueReference="200"
780            causality="parameter"
781            start="1.0 2.0 3.0 4.0">
782            <Dimension valueReference="201"/>
783            <Dimension start="2"/>
784        </Float32>"#;
785
786        let var: FmiFloat32 = yaserde::de::from_str(xml).unwrap();
787        assert_eq!(var.name(), "matrix_var");
788        assert_eq!(var.dimensions().len(), 2);
789        assert_eq!(var.dimensions()[0].value_reference, Some(201));
790        assert_eq!(var.dimensions()[0].start, None);
791        assert_eq!(var.dimensions()[1].value_reference, None);
792        assert_eq!(var.dimensions()[1].start, Some(2));
793        assert_eq!(var.start(), &[1.0, 2.0, 3.0, 4.0]);
794    }
795
796    #[test]
797    fn test_string_multiple_starts() {
798        let xml = r#"<String name="multi_string" valueReference="300" causality="parameter">
799            <Start value="First string"/>
800            <Start value="Second string"/>
801            <Start value="Third string"/>
802        </String>"#;
803
804        let var: FmiString = yaserde::de::from_str(xml).unwrap();
805        assert_eq!(var.name(), "multi_string");
806        let start_values: Vec<&str> = var.start().collect();
807        assert_eq!(
808            start_values,
809            vec!["First string", "Second string", "Third string"]
810        );
811    }
812
813    #[test]
814    fn test_binary_multiple_starts_and_attributes() {
815        let xml = r#"<Binary 
816            name="multi_binary" 
817            valueReference="400" 
818            causality="input"
819            mimeType="application/custom"
820            maxSize="1024">
821            <Start value="48656c6c6f"/>
822            <Start value="576f726c64"/>
823        </Binary>"#;
824
825        let var: FmiBinary = yaserde::de::from_str(xml).unwrap();
826        assert_eq!(var.name(), "multi_binary");
827        assert_eq!(var.mime_type, "application/custom");
828        assert_eq!(var.max_size, Some(1024));
829
830        let start_values: Vec<&BinaryStart> = var.start().collect();
831        assert_eq!(start_values.len(), 2);
832        assert_eq!(start_values[0].value, "48656c6c6f");
833        assert_eq!(start_values[1].value, "576f726c64");
834
835        // Test hex parsing
836        assert_eq!(
837            start_values[0].as_bytes(),
838            Ok(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f])
839        ); // "Hello"
840        assert_eq!(
841            start_values[1].as_bytes(),
842            Ok(vec![0x57, 0x6f, 0x72, 0x6c, 0x64])
843        ); // "World"
844    }
845
846    #[test]
847    fn test_binary_hex_parsing_with_prefix() {
848        let xml = r#"<Binary name="hex_binary" valueReference="500" causality="input">
849            <Start value="0x48656C6C6F"/>
850        </Binary>"#;
851
852        let var: FmiBinary = yaserde::de::from_str(xml).unwrap();
853        let start0 = var.start().next().unwrap();
854        assert_eq!(start0.as_bytes(), Ok(vec![0x48, 0x65, 0x6C, 0x6C, 0x6F])); // "HeLLO"
855    }
856
857    #[test]
858    fn test_binary_hex_parsing_with_whitespace() {
859        let xml = r#"<Binary name="spaced_binary" valueReference="600" causality="input">
860            <Start value="48 65 6c 6c 6f 20 57 6f 72 6c 64"/>
861        </Binary>"#;
862
863        let var: FmiBinary = yaserde::de::from_str(xml).unwrap();
864        let start0 = var.start().next().unwrap();
865        assert_eq!(
866            start0.as_bytes(),
867            Ok(vec![
868                0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64
869            ])
870        ); // "Hello World"
871    }
872
873    #[test]
874    fn test_initial_values() {
875        let xml_exact =
876            r#"<Float64 name="exact_var" valueReference="700" initial="exact" start="1.0"/>"#;
877        let var_exact: FmiFloat64 = yaserde::de::from_str(xml_exact).unwrap();
878        assert_eq!(var_exact.initial(), Some(Initial::Exact));
879
880        let xml_approx =
881            r#"<Float64 name="approx_var" valueReference="701" initial="approx" start="1.0"/>"#;
882        let var_approx: FmiFloat64 = yaserde::de::from_str(xml_approx).unwrap();
883        assert_eq!(var_approx.initial(), Some(Initial::Approx));
884
885        let xml_calculated =
886            r#"<Float64 name="calc_var" valueReference="702" initial="calculated" start="1.0"/>"#;
887        let var_calculated: FmiFloat64 = yaserde::de::from_str(xml_calculated).unwrap();
888        assert_eq!(var_calculated.initial(), Some(Initial::Calculated));
889    }
890
891    #[test]
892    fn test_data_type_enum() {
893        let float32_var: FmiFloat32 = Default::default();
894        assert_eq!(float32_var.data_type(), VariableType::FmiFloat32);
895
896        let float64_var: FmiFloat64 = Default::default();
897        assert_eq!(float64_var.data_type(), VariableType::FmiFloat64);
898
899        let int8_var: FmiInt8 = Default::default();
900        assert_eq!(int8_var.data_type(), VariableType::FmiInt8);
901
902        let uint8_var: FmiUInt8 = Default::default();
903        assert_eq!(uint8_var.data_type(), VariableType::FmiUInt8);
904
905        let int16_var: FmiInt16 = Default::default();
906        assert_eq!(int16_var.data_type(), VariableType::FmiInt16);
907
908        let uint16_var: FmiUInt16 = Default::default();
909        assert_eq!(uint16_var.data_type(), VariableType::FmiUInt16);
910
911        let int32_var: FmiInt32 = Default::default();
912        assert_eq!(int32_var.data_type(), VariableType::FmiInt32);
913
914        let uint32_var: FmiUInt32 = Default::default();
915        assert_eq!(uint32_var.data_type(), VariableType::FmiUInt32);
916
917        let int64_var: FmiInt64 = Default::default();
918        assert_eq!(int64_var.data_type(), VariableType::FmiInt64);
919
920        let uint64_var: FmiUInt64 = Default::default();
921        assert_eq!(uint64_var.data_type(), VariableType::FmiUInt64);
922
923        let boolean_var: FmiBoolean = Default::default();
924        assert_eq!(boolean_var.data_type(), VariableType::FmiBoolean);
925
926        let string_var: FmiString = Default::default();
927        assert_eq!(string_var.data_type(), VariableType::FmiString);
928
929        let binary_var: FmiBinary = Default::default();
930        assert_eq!(binary_var.data_type(), VariableType::FmiBinary);
931    }
932
933    #[cfg(feature = "arrow")]
934    #[test]
935    fn test_arrow_data_type_conversion() {
936        use arrow::datatypes::DataType;
937
938        assert_eq!(DataType::from(VariableType::FmiFloat32), DataType::Float32);
939        assert_eq!(DataType::from(VariableType::FmiFloat64), DataType::Float64);
940        assert_eq!(DataType::from(VariableType::FmiInt8), DataType::Int8);
941        assert_eq!(DataType::from(VariableType::FmiUInt8), DataType::UInt8);
942        assert_eq!(DataType::from(VariableType::FmiInt16), DataType::Int16);
943        assert_eq!(DataType::from(VariableType::FmiUInt16), DataType::UInt16);
944        assert_eq!(DataType::from(VariableType::FmiInt32), DataType::Int32);
945        assert_eq!(DataType::from(VariableType::FmiUInt32), DataType::UInt32);
946        assert_eq!(DataType::from(VariableType::FmiInt64), DataType::Int64);
947        assert_eq!(DataType::from(VariableType::FmiUInt64), DataType::UInt64);
948        assert_eq!(DataType::from(VariableType::FmiBoolean), DataType::Boolean);
949        assert_eq!(DataType::from(VariableType::FmiString), DataType::Utf8);
950        assert_eq!(DataType::from(VariableType::FmiBinary), DataType::Binary);
951    }
952}