use anyhow::anyhow;
use prettytable::{row, table, Row, Table};
mod fmi3;
mod options;
fn print_info(import: &fmi::Import) {
let mut table = table!(
[br -> "Model name:", import.descr.model_name],
[br -> "Model GUID:", import.descr.guid],
[br -> "Model version:", import.descr.version],
[br -> "Description:", import.descr.description],
[br -> "Generation Tool:", import.descr.generation_tool],
[br -> "Generation Date:", import.descr.generation_date_and_time]
);
table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
table.printstd();
let counts = import.descr.model_counts();
let mut table = Table::new();
table.set_titles(row!["By Variability", "By Causality", "By DataType"]);
table.add_row(row![
format!("{} constants\n{} continuous\n{} discrete", counts.num_constants, counts.num_continuous, counts.num_discrete),
format!(
"{} inputs\n{} outputs\n{} locals\n{} independents\n{} parameters\n{} calculated parameters",
counts.num_inputs,
counts.num_outputs,
counts.num_local,
counts.num_independent,
counts.num_parameters,
counts.num_calculated_parameters,
),
format!("{} real variables\n{} integer variables\n{} enumeration variables\n{} boolean variables\n{} string variables",
counts.num_real_vars,
counts.num_integer_vars,
counts.num_enum_vars,
counts.num_bool_vars,
counts.num_string_vars
),
]);
table.printstd();
}
struct FmiCheckState {
pub tolerance: Option<f64>,
pub start_time: f64,
pub stop_time: f64,
pub step_size: f64,
}
impl FmiCheckState {
pub fn from_options_and_experiment(
options: &options::FmiCheckOptions,
default_experiment: &Option<&fmi::model_descr::DefaultExperiment>,
) -> anyhow::Result<Self> {
let tolerance = default_experiment.and_then(|de| Some(de.tolerance));
let start_time = default_experiment.map(|de| de.start_time).unwrap_or(0.0);
let stop_time = options
.stop_time
.or_else(|| default_experiment.and_then(|de| Some(de.stop_time)))
.unwrap_or(10.0);
let step_size = options
.step_size
.unwrap_or(stop_time / options.num_steps as f64);
Ok(FmiCheckState {
tolerance,
start_time,
stop_time,
step_size,
})
}
}
fn setup_debug_logging<I: fmi::Common>(
import: &fmi::Import,
instance: &I,
logging_on: bool,
) -> fmi::Result<()> {
let categories = &import
.descr
.log_categories
.as_ref()
.map(|log_categories| {
log_categories
.categories
.iter()
.map(|cat| cat.name.as_ref())
.collect::<Vec<&str>>()
})
.unwrap_or_default();
instance
.set_debug_logging(logging_on, categories)
.and(Ok(()))
}
fn sim_prelude<'a, I: fmi::Common>(
import: &'a fmi::Import,
instance: &'a I,
fmi_sim: &FmiCheckState,
) -> fmi::Result<(Vec<UnknownsTuple<'a>>, Table)> {
let outputs = import.descr.outputs()?;
let mut data_table = Table::new();
data_table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
data_table.set_titles(Row::from(
["t".to_owned()]
.iter()
.chain(outputs.iter().map(|(sv, _)| &sv.name)),
));
setup_debug_logging(import, instance, true)?;
log::info!(
"Preparing simulation from t=[{},{}], dt={}, tol={}",
fmi_sim.start_time,
fmi_sim.stop_time,
fmi_sim.step_size,
fmi_sim.tolerance.unwrap_or(0.0)
);
instance.setup_experiment(
fmi_sim.tolerance,
fmi_sim.start_time,
Some(fmi_sim.stop_time),
)?;
instance.enter_initialization_mode()?;
instance.exit_initialization_mode()?;
data_table.add_row(Row::from([fmi_sim.start_time].iter().cloned().chain(
outputs.iter().map(|(sv, _)| match sv.elem {
fmi::model_descr::ScalarVariableElement::Real { .. } => instance.get_real(sv).unwrap(),
_ => 0.0,
}),
)));
Ok((outputs, data_table))
}
fn sim_cs(import: &fmi::Import, fmi_sim: &mut FmiCheckState) -> fmi::Result<Table> {
use fmi::instance::{CoSimulation, Common};
let instance = fmi::InstanceCS::new(import, "inst1", false, true)?;
let (outputs, mut data_table) = sim_prelude(import, &instance, fmi_sim)?;
log::info!(
"Initialized FMU for CS simulation starting at time {}.",
fmi_sim.start_time
);
let mut current_time = fmi_sim.start_time;
while current_time < fmi_sim.stop_time {
instance.do_step(current_time, fmi_sim.step_size, true)?;
data_table.add_row(Row::from([current_time].iter().cloned().chain(
outputs.iter().map(|(sv, _)| match sv.elem {
fmi::model_descr::ScalarVariableElement::Real { .. } => {
instance.get_real(sv).unwrap()
}
_ => 0.0,
}),
)));
current_time += fmi_sim.step_size;
}
Ok(data_table)
}
fn sim_me(import: &fmi::Import, fmi_sim: &mut FmiCheckState) -> fmi::Result<Table> {
use fmi::instance::{Common, ModelExchange};
let instance = fmi::InstanceME::new(import, "inst1", false, true)?;
let (outputs, mut data_table) = sim_prelude(import, &instance, fmi_sim)?;
let _ = instance.do_event_iteration()?;
instance.enter_continuous_time_mode()?;
let mut states = vec![0.0; import.descr.num_states()];
let mut states_der = vec![0.0; import.descr.num_states()];
let mut events = vec![0.0; import.descr.num_event_indicators()];
let mut events_prev = vec![0.0; import.descr.num_event_indicators()];
instance.get_continuous_states(&mut states)?;
instance.get_event_indicators(&mut events_prev)?;
log::info!(
"Initialized FMU for ME simulation starting at time {}: {:?}",
fmi_sim.start_time,
states
);
let mut current_time = fmi_sim.start_time;
let mut terminate_simulation = false;
while (current_time < fmi_sim.stop_time) && !terminate_simulation {
instance.get_derivatives(&mut states_der)?;
let mut next_time = current_time + fmi_sim.step_size;
if next_time > (fmi_sim.stop_time - fmi_sim.step_size / 1e16) {
next_time = fmi_sim.stop_time;
}
let time_event = false;
let current_step = next_time - current_time;
current_time = next_time;
log::trace!("Simulation time: {}", current_time);
instance.set_time(current_time)?;
for (x, dx) in states.iter_mut().zip(states_der.iter()) {
*x += (*dx) * current_step;
}
instance.set_continuous_states(&states)?;
instance.get_event_indicators(&mut events)?;
let zero_crossing_event = events
.iter()
.zip(events_prev.iter())
.find(|(e, e_prev)| (*e) * (*e_prev) < 0.0);
let ret = instance.completed_integrator_step(true)?;
let enter_event_mode = ret.0;
terminate_simulation = ret.1;
if enter_event_mode || zero_crossing_event.is_some() || time_event {
let event_kind = if enter_event_mode {
"step"
} else if zero_crossing_event.is_some() {
"state"
} else {
"time"
};
log::trace!("Handling a {} event", event_kind);
instance.enter_event_mode()?;
let (nominals_changed, values_changed) = instance.do_event_iteration()?;
if values_changed {
instance.get_continuous_states(&mut states)?;
}
if nominals_changed {
}
instance.get_event_indicators(&mut events_prev)?;
instance.enter_continuous_time_mode()?;
}
data_table.add_row(Row::from([current_time].iter().cloned().chain(
outputs.iter().map(|(sv, _)| match sv.elem {
fmi::model_descr::ScalarVariableElement::Real { .. } => {
instance.get_real(sv).unwrap()
}
_ => 0.0,
}),
)));
if terminate_simulation {
log::info!("FMU requested simulation termination");
break;
}
}
log::info!("Simulation finished successfully at time {}", current_time);
instance.terminate()?;
Ok(data_table)
}
fn main() -> anyhow::Result<()> {
let args: FmiCheckOptions = FmiCheckOptions::from_args();
sensible_env_logger::init!();
let import = fmi::Import::new(std::path::Path::new(&args.model))?;
if import.descr.fmi_version != "2.0" {
return Err(anyhow!("Unsupported FMI Version"));
}
print_info(&import);
let default_experiment = &import.descr.default_experiment.as_ref();
let mut fmi_sim = FmiCheckState::from_options_and_experiment(&args, default_experiment)?;
if args.sim_me || args.sim_cs || args.check_xml {
}
if !args.sim_me && !args.sim_cs {
log::info!("Simulation was not requested");
return Ok(());
}
if args.sim_me {
let data_table = sim_me(&import, &mut fmi_sim)?;
data_table.printstd();
}
if args.sim_cs {
let data_table = sim_cs(&import, &mut fmi_sim)?;
data_table.printstd();
}
Ok(())
}