Skip to main content

fmi_export/fmi3/traits/
mod.rs

1use std::{fmt::Display, path::PathBuf, str::FromStr};
2
3use fmi::{
4    EventFlags,
5    fmi3::{Fmi3Error, Fmi3Res, Fmi3Status, binding},
6    schema::fmi3::AppendToModelVariables,
7};
8
9use crate::fmi3::ModelState;
10
11mod model_get_set;
12mod wrappers;
13
14pub use model_get_set::{ModelGetSet, ModelGetSetStates};
15pub use wrappers::{Fmi3CoSimulation, Fmi3Common, Fmi3ModelExchange, Fmi3ScheduledExecution};
16
17/// Context trait for FMU instances
18pub trait Context<M: UserModel> {
19    /// Check if logging is enabled for the specified category.
20    fn logging_on(&self, category: M::LoggingCategory) -> bool;
21
22    /// Enable or disable logging for the specified category.
23    fn set_logging(&mut self, category: M::LoggingCategory, enabled: bool);
24
25    /// Log a message if the specified logging category is enabled.
26    fn log(&self, status: Fmi3Status, category: M::LoggingCategory, args: std::fmt::Arguments<'_>);
27
28    /// Get the path to the resources directory.
29    fn resource_path(&self) -> &PathBuf;
30
31    fn initialize(&mut self, start_time: f64, stop_time: Option<f64>);
32
33    /// Get the current simulation time.
34    fn time(&self) -> f64;
35
36    /// Set the current simulation time.
37    fn set_time(&mut self, time: f64);
38
39    /// Get the simulation stop time, if any.
40    fn stop_time(&self) -> Option<f64>;
41
42    /// Whether early return is allowed for this instance (relevant for CS).
43    fn early_return_allowed(&self) -> bool {
44        false
45    }
46
47    fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
48}
49
50/// Model trait. This trait should be implementing by deriving `FmuModel` on the user model struct.
51///
52/// It provides the necessary back-end functionality for the FMI 3.0 API, delegating user-specific
53/// behavior to the `UserModel` trait.
54pub trait Model: Default {
55    const MODEL_NAME: &'static str;
56    const INSTANTIATION_TOKEN: &'static str;
57
58    /// Number of event indicators
59    const MAX_EVENT_INDICATORS: usize;
60
61    /// Whether this model supports Model Exchange interface
62    const SUPPORTS_MODEL_EXCHANGE: bool;
63
64    /// Whether this model supports Co-Simulation interface
65    const SUPPORTS_CO_SIMULATION: bool;
66
67    /// Whether this model supports Scheduled Execution interface
68    const SUPPORTS_SCHEDULED_EXECUTION: bool;
69
70    /// Recursively build the model variables and structure by appending to the provided
71    /// `ModelVariables` and `ModelStructure` instances.
72    ///
73    /// Returns the number of variables that were added.
74    fn build_metadata(
75        variables: &mut fmi::schema::fmi3::ModelVariables,
76        model_structure: &mut fmi::schema::fmi3::ModelStructure,
77        vr_offset: u32,
78        prefix: Option<&str>,
79    ) -> u32;
80
81    /// Build the top-level model variables and structure, including the 'time' variable.
82    fn build_toplevel_metadata() -> (
83        fmi::schema::fmi3::ModelVariables,
84        fmi::schema::fmi3::ModelStructure,
85    ) {
86        let mut variables = fmi::schema::fmi3::ModelVariables::default();
87        let time = fmi::schema::fmi3::FmiFloat64::new(
88            "time".to_string(),
89            0,
90            None,
91            fmi::schema::fmi3::Causality::Independent,
92            fmi::schema::fmi3::Variability::Continuous,
93            None,
94            None,
95        );
96        AppendToModelVariables::append_to_variables(time, &mut variables);
97        let mut structure = fmi::schema::fmi3::ModelStructure::default();
98        let _num_vars = Self::build_metadata(&mut variables, &mut structure, 1, None);
99        (variables, structure)
100    }
101
102    /// Set start values
103    fn set_start_values(&mut self);
104
105    /// Validate that a variable can be set in the current model state
106    /// This method should be implemented by the generated code to check
107    /// causality and variability restrictions for each variable
108    fn validate_variable_setting(
109        vr: binding::fmi3ValueReference,
110        state: &ModelState,
111    ) -> Result<(), &'static str> {
112        // Default implementation allows all variable setting
113        // Generated implementations will provide specific validation rules
114        let _ = (vr, state);
115        Ok(())
116    }
117}
118
119pub trait ModelLoggingCategory: Display + FromStr + Ord + Copy + Default {
120    /// Return an iterator over all possible logging categories
121    fn all_categories() -> impl Iterator<Item = Self>;
122    /// Get the category for tracing FMI API calls
123    fn trace_category() -> Self;
124    /// Get the category for logging errors
125    fn error_category() -> Self;
126}
127
128/// Result payload for a Co-Simulation `do_step` implementation.
129#[derive(Debug, Clone, Copy, Default)]
130pub struct CSDoStepResult {
131    pub event_handling_needed: bool,
132    pub terminate_simulation: bool,
133    pub early_return: bool,
134    pub last_successful_time: f64,
135}
136
137impl CSDoStepResult {
138    pub fn completed(last_successful_time: f64) -> Self {
139        Self {
140            event_handling_needed: false,
141            terminate_simulation: false,
142            early_return: false,
143            last_successful_time,
144        }
145    }
146}
147
148/// User-defined model behavior trait
149///
150/// This trait should be hand-implemented by the user to define the specific behavior of their model.
151pub trait UserModel: Sized {
152    /// The logging category type for this model
153    ///
154    /// This is an enum that implements `ModelLoggingCategory`
155    type LoggingCategory: ModelLoggingCategory + 'static;
156
157    /// Configure the model (allocate memory, initialize states, etc.)
158    /// This method is called upon exiting initialization mode
159    fn configurate(&mut self, _context: &dyn Context<Self>) -> Result<(), Fmi3Error> {
160        Ok(())
161    }
162
163    /// Calculate values (derivatives, outputs, etc.)
164    /// This method is called whenever the model needs to update its calculated values
165    fn calculate_values(&mut self, _context: &dyn Context<Self>) -> Result<Fmi3Res, Fmi3Error> {
166        Ok(Fmi3Res::OK)
167    }
168
169    /// Called to update discrete states and check for events
170    ///
171    /// This method should:
172    /// - Update any discrete state variables
173    /// - Check for state events and time events
174    /// - Set appropriate flags to indicate what has changed
175    ///
176    /// Returns Ok with the appropriate Fmi3Res status, or Err if an error occurs
177    fn event_update(
178        &mut self,
179        _context: &dyn Context<Self>,
180        event_flags: &mut EventFlags,
181    ) -> Result<Fmi3Res, Fmi3Error> {
182        event_flags.reset();
183        Ok(Fmi3Res::OK)
184    }
185
186    /// Get event indicators for zero-crossing detection
187    ///
188    /// # Returns
189    /// - `Ok(true)` if event indicators were successfully computed
190    /// - `Ok(false)` if the FMU was not able to compute the event indicators because, for example,
191    ///     a numerical issue such as division by zero occurred (corresponding to the C API
192    ///     returning fmi3Discard)
193    /// - `Err(Fmi3Error)` for other error conditions
194    fn get_event_indicators(
195        &mut self,
196        _context: &dyn Context<Self>,
197        indicators: &mut [f64],
198    ) -> Result<bool, Fmi3Error> {
199        // Default implementation: no event indicators
200        for indicator in indicators.iter_mut() {
201            *indicator = 0.0;
202        }
203        Ok(true)
204    }
205
206    /// Co-Simulation step implementation.
207    ///
208    /// Default behavior advances time and reports a completed step.
209    fn do_step(
210        &mut self,
211        context: &mut dyn Context<Self>,
212        current_communication_point: f64,
213        communication_step_size: f64,
214        _no_set_fmu_state_prior_to_current_point: bool,
215    ) -> Result<CSDoStepResult, Fmi3Error> {
216        let target_time = current_communication_point + communication_step_size;
217        context.set_time(target_time);
218        Ok(CSDoStepResult::completed(target_time))
219    }
220}