fmi-rs 0.1.0

Functional Mock-up Interface (FMI) for Rust
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]

pub mod cs;
pub mod csv;
pub mod input;
pub mod me;
pub mod recorder;

use crate::{
    fmi2::{
        FMU2,
        types::{
            fmi2Boolean, fmi2False, fmi2Integer, fmi2Real, fmi2Status, fmi2True, fmi2ValueReference,
        },
    },
    model_description::fmi2::{ModelDescription, ScalarVariable, VariableType},
    sim::SimulationError,
};

use std::{collections::HashMap, fs, ptr, sync::Arc};

use std::path::{Path, PathBuf};

pub struct SimulationSettings {
    pub unzipdir: PathBuf,
    pub model_description: Arc<ModelDescription>,
    pub start_time: f64,
    pub stop_time: f64,
    pub logging_on: bool,
    pub set_stop_time: bool,
    pub output_interval: f64,
    pub log_time_scale: bool,
    pub tolerance: f64,
    pub set_tolerance: bool,
    pub start_values: Vec<(String, String)>,
    pub log_fmi_calls: bool,
    pub input_file: Option<PathBuf>,
    pub early_return_allowed: bool,
    pub event_mode_used: bool,
    pub log_file: Option<PathBuf>,
    pub initial_fmu_state_file: Option<PathBuf>,
    pub final_fmu_state_file: Option<PathBuf>,
}

#[derive(Debug, PartialEq, Clone)]
pub enum VariableValue {
    Real(fmi2Real),
    Integer(fmi2Integer),
    Boolean(fmi2Boolean),
    String(String),
}

impl VariableValue {
    pub fn to_f64(&self) -> Option<f64> {
        match self {
            VariableValue::Real(value) => Some(*value),
            VariableValue::Integer(value) => Some(*value as f64),
            VariableValue::Boolean(value) => {
                if *value != fmi2False {
                    Some(1.0)
                } else {
                    Some(0.0)
                }
            }
            VariableValue::String(_) => None,
        }
    }

    pub fn to_literal(&self) -> String {
        match self {
            VariableValue::Real(v) => v.to_string(),
            VariableValue::Integer(v) => v.to_string(),
            VariableValue::Boolean(v) => v.to_string(),
            VariableValue::String(v) => v.clone(),
        }
    }
}

#[derive(Debug)]
pub struct Trajectories {
    pub model_description: Arc<ModelDescription>,
    pub variable_indices: Vec<usize>,
    pub time: Vec<f64>,
    pub rows: Vec<Vec<VariableValue>>,
}

impl Trajectories {
    pub fn new(model_description: Arc<ModelDescription>, variable_indices: Vec<usize>) -> Self {
        Trajectories {
            model_description,
            variable_indices,
            time: vec![],
            rows: vec![],
        }
    }

    pub fn variables(&self) -> impl Iterator<Item = &ScalarVariable> {
        self.variable_indices
            .iter()
            .copied()
            .filter_map(|idx| self.model_description.modelVariables.get(idx))
    }

    pub fn validate(&self) -> Result<(), String> {
        if self.time.len() != self.rows.len() {
            return Err(format!(
                "Time vector length ({}) does not match rows length ({}).",
                self.time.len(),
                self.rows.len()
            ));
        }

        for (i, window) in self.time.windows(2).enumerate() {
            if let &[t0, t1] = window
                && t0 > t1
            {
                return Err(format!(
                    "Time is decreasing at row {} ({t0} -> {t1}).",
                    i.saturating_add(2)
                ));
            }
        }

        for (i, row) in self.rows.iter().enumerate() {
            if row.len() != self.variable_indices.len() {
                return Err(format!(
                    "Row {} has {} columns, but {} variables are defined.",
                    i,
                    row.len(),
                    self.variable_indices.len()
                ));
            }
        }

        Ok(())
    }

    pub fn events(&self) -> Vec<f64> {
        let mut events: Vec<f64> = self
            .time
            .windows(2)
            .filter_map(|w| match w {
                &[a, b] if a == b => Some(a),
                _ => None,
            })
            .collect();
        events.dedup();
        events
    }

    pub fn len(&self) -> usize {
        self.variable_indices.len()
    }

    pub fn is_empty(&self) -> bool {
        self.variable_indices.is_empty()
    }

    pub fn step_count(&self) -> usize {
        self.time.len()
    }

    pub fn trajectory_by_index(&self, index: usize) -> Option<Vec<VariableValue>> {
        self.rows
            .iter()
            .map(|row| row.get(index).cloned())
            .collect()
    }
}

fn call(status: fmi2Status) -> Result<fmi2Status, SimulationError> {
    if matches!(status, fmi2Status::Ok | fmi2Status::Warning) {
        Ok(status)
    } else {
        Err(SimulationError::FMICall)
    }
}

pub fn parse_boolean(literal: &str) -> Result<fmi2Boolean, SimulationError> {
    match literal {
        "true" | "1" => Ok(fmi2True),
        "false" | "0" => Ok(fmi2False),
        _ => Err(SimulationError::Parse(format!(
            "Invalid Boolean literal: {literal}"
        ))),
    }
}

pub fn parse_variable_value(
    variable_type: &VariableType,
    literal: &str,
) -> Result<VariableValue, SimulationError> {
    match variable_type {
        VariableType::Real { .. } => Ok(VariableValue::Real(literal.parse().map_err(|e| {
            SimulationError::Parse(format!("Invalid Real literal '{literal}': {e}"))
        })?)),
        VariableType::Integer { .. } | VariableType::Enumeration { .. } => {
            Ok(VariableValue::Integer(literal.parse().map_err(|e| {
                SimulationError::Parse(format!("Invalid Integer literal '{literal}' {e}"))
            })?))
        }
        VariableType::Boolean { .. } => Ok(VariableValue::Boolean(parse_boolean(literal)?)),
        VariableType::String { .. } => Ok(VariableValue::String(literal.to_string())),
    }
}

pub fn set_variable_value<T>(
    fmu: &FMU2<T>,
    value_reference: fmi2ValueReference,
    value: &VariableValue,
) -> Result<fmi2Status, SimulationError> {
    match value {
        VariableValue::Real(value) => call(fmu.setReal(&[value_reference], &[*value])),
        VariableValue::Integer(value) => call(fmu.setInteger(&[value_reference], &[*value])),
        VariableValue::Boolean(value) => call(fmu.setBoolean(&[value_reference], &[*value])),
        VariableValue::String(value) => call(fmu.setString(&[value_reference], &[value.as_str()])),
    }
}

fn set_start_values<T>(
    start_values: &Vec<(String, String)>,
    model_description: &ModelDescription,
    fmu: &FMU2<T>,
) -> Result<fmi2Status, SimulationError> {
    let variable_map: HashMap<&str, &ScalarVariable> = model_description
        .modelVariables
        .iter()
        .map(|var| (var.name.as_str(), var))
        .collect();

    let mut remaining_start_values = vec![];

    for (var_name, literal) in start_values {
        if let Some(variable) = variable_map.get(var_name.as_str()) {
            match parse_variable_value(&variable.variableType, literal) {
                Ok(value) => {
                    set_variable_value(fmu, variable.valueReference, &value)?;
                }
                Err(e) => {
                    return Err(SimulationError::Parse(format!(
                        "Invalid value {literal:?} for variable {var_name:?}. {e}"
                    )));
                }
            }
        } else {
            remaining_start_values.push((var_name.clone(), literal.clone()));
        }
    }

    if !remaining_start_values.is_empty() {
        let variable_names = remaining_start_values
            .iter()
            .map(|(var_name, _)| format!("'{var_name}'"))
            .collect::<Vec<_>>()
            .join(", ");
        let message = format!(
            "The start values for the following variables could not be set because they don't exist in the model description: {variable_names}."
        );
        return Err(SimulationError::Parameter(message));
    }

    Ok(fmi2Status::Ok)
}

fn read_initial_fmu_state<I>(fmu: &FMU2<I>, path: &Path) -> Result<(), SimulationError> {
    let serialized_state = fs::read(path).map_err(SimulationError::io(&path))?;
    let mut fmu_state = ptr::null_mut();

    call(fmu.deSerializeFMUstate(&serialized_state, &mut fmu_state))?;
    call(fmu.setFMUstate(fmu_state))?;

    Ok(())
}

fn write_final_fmu_state<I>(fmu: &FMU2<I>, path: &Path) -> Result<(), SimulationError> {
    let mut fmu_state = ptr::null_mut();
    call(fmu.getFMUstate(&mut fmu_state))?;

    let mut size = 0usize;
    call(fmu.serializedFMUstateSize(fmu_state, &mut size))?;

    let mut serialized_state = vec![0; size];
    call(fmu.serializeFMUstate(fmu_state, &mut serialized_state))?;

    fs::write(path, &serialized_state).map_err(SimulationError::io(&path))?;

    Ok(())
}