Skip to main content

fmi_sim/sim/
mod.rs

1use arrow::array::RecordBatch;
2use std::path::Path;
3
4use fmi::{
5    EventFlags,
6    traits::{FmiEventHandler, FmiImport, FmiInstance},
7};
8
9pub use io::{InputState, RecorderState};
10
11use crate::{Error, options};
12
13use self::{
14    interpolation::Linear,
15    params::SimParams,
16    traits::{FmiSim, InstRecordValues, InstSetValues, SimDefaultInitialize, SimHandleEvents},
17};
18
19#[cfg(feature = "fmi2")]
20pub mod fmi2;
21#[cfg(feature = "fmi3")]
22pub mod fmi3;
23mod interpolation;
24mod io;
25mod me;
26pub mod params;
27pub mod solver;
28pub mod traits;
29pub mod util;
30
31pub struct SimState<Inst>
32where
33    Inst: FmiInstance,
34{
35    sim_params: SimParams,
36    input_state: InputState<Inst>,
37    recorder_state: RecorderState<Inst>,
38    inst: Inst,
39    event_flags: EventFlags,
40}
41
42impl<Inst: FmiInstance> SimState<Inst> {
43    /// Get the next event time, or `f64::INFINITY` if none is scheduled.
44    pub fn next_event_time(&self) -> f64 {
45        self.event_flags.next_event_time.unwrap_or(f64::INFINITY)
46    }
47}
48
49pub trait SimStateTrait<Inst: FmiInstance, Import: FmiImport> {
50    fn new(
51        import: &Import,
52        sim_params: SimParams,
53        input_state: InputState<Inst>,
54        output_state: RecorderState<Inst>,
55    ) -> Result<Self, Error>
56    where
57        Self: Sized;
58}
59
60impl<Inst> SimHandleEvents for SimState<Inst>
61where
62    Inst: FmiEventHandler + InstSetValues + InstRecordValues,
63{
64    fn handle_events(&mut self, time: f64, input_event: bool) -> Result<(bool, bool), Error> {
65        self.inst.record_outputs(time, &mut self.recorder_state)?;
66        self.inst.enter_event_mode().map_err(Into::into)?;
67        if input_event {
68            self.input_state
69                .apply_input::<Linear>(time, &mut self.inst, true, true, true)?;
70        }
71        let mut reset_solver = false;
72
73        self.event_flags.discrete_states_need_update = true;
74        while self.event_flags.discrete_states_need_update {
75            self.inst
76                .update_discrete_states(&mut self.event_flags)
77                .map_err(Into::into)?;
78            if self.event_flags.terminate_simulation {
79                break;
80            }
81            reset_solver |= self.event_flags.nominals_of_continuous_states_changed
82                || self.event_flags.values_of_continuous_states_changed;
83        }
84        Ok((reset_solver, self.event_flags.terminate_simulation))
85    }
86}
87
88#[derive(Default, Debug)]
89pub struct SimStats {
90    /// End time of the simulation
91    pub end_time: f64,
92    /// Number of steps taken
93    pub num_steps: usize,
94    /// Number of events handled
95    pub num_events: usize,
96}
97
98/// Lower-level simulation function that takes an FMI import and a set of options.
99pub fn simulate_with<Imp: FmiSim>(
100    input_data: Option<RecordBatch>,
101    interface: &options::Interface,
102    import: Imp,
103) -> Result<(RecordBatch, SimStats), Error> {
104    match interface {
105        #[cfg(feature = "me")]
106        options::Interface::ModelExchange(options) => import.simulate_me(options, input_data),
107        #[cfg(feature = "cs")]
108        options::Interface::CoSimulation(options) => import.simulate_cs(options, input_data),
109        #[cfg(feature = "se")]
110        options::Interface::ScheduledExecution(_options) => unimplemented!(),
111        #[cfg(any(not(feature = "me"), not(feature = "cs")))]
112        _ => Err(fmi::Error::UnsupportedInterface(format!("{}", interface)).into()),
113    }
114}
115
116macro_rules! impl_sim_default_initialize {
117    ($inst:ty) => {
118        impl SimDefaultInitialize for SimState<$inst> {
119            fn default_initialize(&mut self) -> Result<(), Error> {
120                self.inst
121                    .enter_initialization_mode(
122                        self.sim_params.tolerance,
123                        self.sim_params.start_time,
124                        Some(self.sim_params.stop_time),
125                    )
126                    .map_err(fmi::Error::from)?;
127
128                self.inst
129                    .exit_initialization_mode()
130                    .map_err(fmi::Error::from)?;
131
132                if self.sim_params.event_mode_used {
133                    // update discrete states
134                    self.event_flags.discrete_states_need_update = true;
135                    while self.event_flags.discrete_states_need_update {
136                        self.inst
137                            .update_discrete_states(&mut self.event_flags)
138                            .map_err(fmi::Error::from)?;
139
140                        if self.event_flags.terminate_simulation {
141                            self.inst.terminate().map_err(fmi::Error::from)?;
142                            log::warn!("update_discrete_states() requested termination.");
143                            break;
144                        }
145                    }
146                }
147                Ok(())
148            }
149        }
150    };
151}
152
153#[cfg(feature = "me")]
154impl_sim_default_initialize!(fmi::fmi2::instance::InstanceME);
155#[cfg(feature = "cs")]
156impl SimDefaultInitialize for SimState<fmi::fmi2::instance::InstanceCS> {
157    fn default_initialize(&mut self) -> Result<(), Error> {
158        self.inst
159            .enter_initialization_mode(
160                self.sim_params.tolerance,
161                self.sim_params.start_time,
162                Some(self.sim_params.stop_time),
163            )
164            .map_err(fmi::Error::from)?;
165        self.inst
166            .exit_initialization_mode()
167            .map_err(fmi::Error::from)?;
168
169        Ok(())
170    }
171}
172
173#[cfg(feature = "me")]
174impl_sim_default_initialize!(fmi::fmi3::instance::InstanceME);
175#[cfg(feature = "cs")]
176impl_sim_default_initialize!(fmi::fmi3::instance::InstanceCS);
177
178macro_rules! impl_sim_initialize {
179    ($inst:ty) => {
180        impl traits::SimInitialize<$inst> for SimState<$inst> {
181            fn initialize<P: AsRef<Path>>(
182                &mut self,
183                start_values: io::StartValues<<$inst as FmiInstance>::ValueRef>,
184                initial_fmu_state_file: Option<P>,
185            ) -> Result<(), Error> {
186                if let Some(_initial_state_file) = &initial_fmu_state_file {
187                    unimplemented!("initial_fmu_state_file");
188                    // self.inst.restore_fmu_state_from_file(initial_state_file)?;
189                }
190
191                // set start values
192                traits::SimApplyStartValues::apply_start_values(self, &start_values)?;
193
194                self.input_state.apply_input::<interpolation::Linear>(
195                    self.sim_params.start_time,
196                    &mut self.inst,
197                    true,
198                    true,
199                    false,
200                )?;
201
202                // Default initialization
203                if initial_fmu_state_file.is_none() {
204                    self.default_initialize()?;
205                }
206
207                Ok(())
208            }
209        }
210    };
211}
212
213#[cfg(feature = "me")]
214impl_sim_initialize!(fmi::fmi2::instance::InstanceME);
215#[cfg(feature = "me")]
216impl_sim_initialize!(fmi::fmi3::instance::InstanceME);
217#[cfg(feature = "cs")]
218impl_sim_initialize!(fmi::fmi2::instance::InstanceCS);
219#[cfg(feature = "cs")]
220impl_sim_initialize!(fmi::fmi3::instance::InstanceCS);