fmi 0.7.0

A Rust interface to FMUs (Functional Mockup Units) that follow the FMI Standard. See http://www.fmi-standard.org/
Documentation
//! FMI 3.0 API

pub mod import;
pub mod instance;
pub(crate) mod logger;
#[cfg(false)]
pub mod model;
mod traits;
use std::fmt::Display;

// Re-export
pub use crate::schema::fmi3 as schema;
#[doc = "Autogenerated bindings for the FMI 3.0 API"]
pub use fmi_sys::fmi3 as binding;

pub use traits::{
    CoSimulation, Common, Fmi3Model, GetSet, ModelExchange, ScheduledExecution, VariableDependency,
};

use crate::{Error, traits::FmiStatus};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Fmi3Res {
    /// The call was successful. The output argument values are defined.
    OK,
    /// A non-critical problem was detected, but the computation may continue. The output argument
    /// values are defined. Function logMessage should be called by the FMU with further
    /// information before returning this status, respecting the current logging settings.
    Warning,
}

#[derive(Debug, thiserror::Error, PartialEq)]
pub enum Fmi3Error {
    /// The call was not successful and the FMU is in the same state as before the call. The output
    /// argument values are undefined, but the computation may continue. Function logMessage should
    /// be called by the FMU with further information before returning this status, respecting the
    /// current logging settings. Advanced importers may try alternative approaches to continue the
    /// simulation by calling the function with different arguments or calling another function -
    /// except in FMI for Scheduled Execution where repeating failed function calls is not allowed.
    /// Otherwise the simulation algorithm must treat this return code like [`Fmi3Error::Error`]
    /// and must terminate the simulation.
    ///
    /// [Examples for usage of `Discard` are handling of min/max violation, or signal numerical
    /// problems during model evaluation forcing smaller step sizes.]
    #[error("Discard")]
    Discard,
    /// The call failed. The output argument values are undefined and the simulation must not be
    /// continued. Function logMessage should be called by the FMU with further information before
    /// returning this status, respecting the current logging settings. If a function returns
    /// [`Fmi3Error::Error`], it is possible to restore a previously retrieved FMU state by calling
    /// `set_fmu_state`. Otherwise `Reset` must be called (or the instance freed).
    ///
    /// When detecting illegal arguments or a function call not allowed in the current state according
    /// to the respective state machine, the FMU must return [`Fmi3Error::Error`]. Other instances of
    /// this FMU are not affected by the error.
    #[error("Error")]
    Error,
    #[error("Fatal")]
    Fatal,
}

#[derive(Debug)]
pub struct Fmi3Status(binding::fmi3Status);

impl Display for Fmi3Status {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self.0 {
            binding::fmi3Status_fmi3OK => write!(f, "Fmi3Status(OK)"),
            binding::fmi3Status_fmi3Warning => write!(f, "Fmi3Status(Warning)"),
            binding::fmi3Status_fmi3Discard => write!(f, "Fmi3Status(Discard)"),
            binding::fmi3Status_fmi3Error => write!(f, "Fmi3Status(Error)"),
            binding::fmi3Status_fmi3Fatal => write!(f, "Fmi3Status(Fatal)"),
            _ => write!(f, "Fmi3Status(Unknown)"),
        }
    }
}

impl FmiStatus for Fmi3Status {
    type Res = Fmi3Res;
    type Err = Fmi3Error;

    /// Convert to [`Result<Fmi3Res, Fmi3Err>`]
    #[inline]
    fn ok(self) -> Result<Fmi3Res, Fmi3Error> {
        self.into()
    }

    #[inline]
    fn is_error(&self) -> bool {
        self.0 == binding::fmi3Status_fmi3Error || self.0 == binding::fmi3Status_fmi3Fatal
    }
}

impl From<binding::fmi3Status> for Fmi3Status {
    fn from(status: binding::fmi3Status) -> Self {
        Self(status)
    }
}

impl From<Fmi3Status> for binding::fmi3Status {
    fn from(Fmi3Status(status): Fmi3Status) -> Self {
        status
    }
}

impl From<Fmi3Res> for Fmi3Status {
    fn from(res: Fmi3Res) -> Self {
        match res {
            Fmi3Res::OK => Self(binding::fmi3Status_fmi3OK),
            Fmi3Res::Warning => Self(binding::fmi3Status_fmi3Warning),
        }
    }
}

impl From<Fmi3Error> for Fmi3Status {
    fn from(err: Fmi3Error) -> Self {
        match err {
            Fmi3Error::Discard => Self(binding::fmi3Status_fmi3Discard),
            Fmi3Error::Error => Self(binding::fmi3Status_fmi3Error),
            Fmi3Error::Fatal => Self(binding::fmi3Status_fmi3Fatal),
        }
    }
}

impl From<Fmi3Status> for Result<Fmi3Res, Fmi3Error> {
    fn from(Fmi3Status(status): Fmi3Status) -> Self {
        match status {
            binding::fmi3Status_fmi3OK => Ok(Fmi3Res::OK),
            binding::fmi3Status_fmi3Warning => Ok(Fmi3Res::Warning),
            binding::fmi3Status_fmi3Discard => Err(Fmi3Error::Discard),
            binding::fmi3Status_fmi3Error => Err(Fmi3Error::Error),
            binding::fmi3Status_fmi3Fatal => Err(Fmi3Error::Fatal),
            _ => unreachable!("Invalid status"),
        }
    }
}

/// Get the platform folder name within an FMU for the given OS and architecture.
///
/// See <https://fmi-standard.org/docs/3.0.1/#platform-tupe-definition>
pub fn platform_folder(os: &str, arch: &str) -> Result<&'static str, Error> {
    match (os, arch) {
        ("windows", "x86") => Ok("x86-windows"),
        ("windows", "x86_64") => Ok("x86_64-windows"),
        ("linux", "x86") => Ok("x86-linux"),
        ("linux", "x86_64") => Ok("x86_64-linux"),
        ("linux", "aarch64") => Ok("aarch64-linux"),
        ("macos", "x86") => Ok("x86-darwin"),
        ("macos", "x86_64") => Ok("x86_64-darwin"),
        ("macos", "aarch64") => Ok("aarch64-darwin"),
        _ => Err(Error::UnsupportedPlatform {
            os: os.to_string(),
            arch: arch.to_string(),
        }),
    }
}