From 0d65044b887d3cf5a01978e4d7a61ef81fdb6f6d Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Wed, 29 Apr 2026 17:15:55 +0200 Subject: [PATCH 01/10] Add FMI 3.0 co-simulation support Add SlaveInstance3 base class with full FMI 3.0 co-simulation API: - 13 Get/Set types (Float32/64, Int8-64, UInt8-64, Boolean, String, Binary, Clock) - Directional and adjoint derivatives - Variable dependencies and output derivatives - FMU state serialization - FMI 3.0 DoStep with event/early return output params New files: cppfmu_cs_fmi3.hpp, cppfmu_cs_fmi3.cpp, fmi3_functions.cpp Updated: cppfmu_common.hpp, CMakeLists.txt, conanfile.py, test_package --- CMakeLists.txt | 23 +- conanfile.py | 16 +- cppfmu_common.hpp | 50 +- cppfmu_cs_fmi3.cpp | 489 +++++++++++++++ cppfmu_cs_fmi3.hpp | 469 ++++++++++++++ fmi3_functions.cpp | 1162 +++++++++++++++++++++++++++++++++++ test_package/CMakeLists.txt | 20 +- test_package/main.cpp | 17 + version.txt | 2 +- 9 files changed, 2226 insertions(+), 22 deletions(-) create mode 100644 cppfmu_cs_fmi3.cpp create mode 100644 cppfmu_cs_fmi3.hpp create mode 100644 fmi3_functions.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index faf46a1..0b46367 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,31 +6,44 @@ file(STRINGS "${CMAKE_SOURCE_DIR}/version.txt" projectVersion) project("cppfmu-library" VERSION ${projectVersion}) option(CPPFMU_FMI_1 "Use FMI 1.0" OFF) +option(CPPFMU_FMI_3 "Use FMI 3.0" OFF) if(CPPFMU_FMI_1) find_package(fmi1 CONFIG REQUIRED) set(FMI fmi1::cosim) + set(FMI_SOURCES ${CMAKE_SOURCE_DIR}/cppfmu_cs.cpp) +elseif(CPPFMU_FMI_3) + find_package(fmi3 CONFIG REQUIRED) + set(FMI fmi3::fmi3) + set(FMI_SOURCES ${CMAKE_SOURCE_DIR}/cppfmu_cs_fmi3.cpp) else() find_package(fmi2 CONFIG REQUIRED) set(FMI fmi2::fmi2) + set(FMI_SOURCES ${CMAKE_SOURCE_DIR}/cppfmu_cs.cpp) endif() -set(sources ${CMAKE_SOURCE_DIR}/cppfmu_cs.cpp) -# fmi_functions.cpp must be compiled by end user +# fmi_functions.cpp / fmi3_functions.cpp must be compiled by end user -add_library(cppfmu STATIC ${sources}) +add_library(cppfmu STATIC ${FMI_SOURCES}) target_include_directories(cppfmu PUBLIC ${CMAKE_SOURCE_DIR}) target_link_libraries(cppfmu PUBLIC ${FMI}) if(CPPFMU_FMI_1) target_compile_definitions(cppfmu PUBLIC CPPFMU_USE_FMI_1_0) +elseif(CPPFMU_FMI_3) + target_compile_definitions(cppfmu PUBLIC CPPFMU_USE_FMI_3_0) endif() install(TARGETS cppfmu ARCHIVE DESTINATION lib RUNTIME DESTINATION bin LIBRARY DESTINATION lib) install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_common.hpp ${CMAKE_SOURCE_DIR}/cppfmu_cs.hpp DESTINATION ${CMAKE_INSTALL_PREFIX}/include) -install(FILES ${CMAKE_SOURCE_DIR}/fmi_functions.cpp DESTINATION ${CMAKE_INSTALL_PREFIX}/src) +if(CPPFMU_FMI_3) + install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_cs_fmi3.hpp DESTINATION ${CMAKE_INSTALL_PREFIX}/include) + install(FILES ${CMAKE_SOURCE_DIR}/fmi3_functions.cpp DESTINATION ${CMAKE_INSTALL_PREFIX}/src) +else() + install(FILES ${CMAKE_SOURCE_DIR}/fmi_functions.cpp DESTINATION ${CMAKE_INSTALL_PREFIX}/src) +endif() -if(NOT CPPFMU_FMI_1) +if(NOT CPPFMU_FMI_1 AND NOT CPPFMU_FMI_3) add_executable(cs_test "tests/cs_test.cpp" "tests/cs_slave.cpp" diff --git a/conanfile.py b/conanfile.py index ea6f982..ff344f8 100644 --- a/conanfile.py +++ b/conanfile.py @@ -14,14 +14,14 @@ class CppFmuConan(ConanFile): name = "cppfmu" license = "MPL-2.0" author = "Lars T. Kyllingstad" - description = "C++ wrapper for FMI co-simulation, v1 and v2" + description = "C++ wrapper for FMI co-simulation, v1, v2, and v3" topics = ("Co-simulation") url = "https://github.com/viproma/cppfmu" settings = "os", "compiler", "build_type", "arch" package_type = "static-library" options = { "fPIC": [True, False], - "use_fmi_version": [1, 2] + "use_fmi_version": [1, 2, 3] } default_options = { "fPIC": True, @@ -66,6 +66,8 @@ def layout(self): def requirements(self): if self.options.use_fmi_version == 1: self.requires("fmi1/1.0.1", transitive_headers=True) + elif self.options.use_fmi_version == 3: + self.requires("fmi3/3.0.2", transitive_headers=True) else: self.requires("fmi2/2.0.4", transitive_headers=True) @@ -86,10 +88,8 @@ def source(self): def generate(self): tc = CMakeToolchain(self) - if self.options.use_fmi_version == 1: - tc.variables["CPPFMU_FMI_1"] = True - else: - tc.variables["CPPFMU_FMI_1"] = False + tc.variables["CPPFMU_FMI_1"] = self.options.use_fmi_version == 1 + tc.variables["CPPFMU_FMI_3"] = self.options.use_fmi_version == 3 tc.generate() deps = CMakeDeps(self) deps.generate() @@ -113,5 +113,9 @@ def package_info(self): self.output.info("Define fmi1") self.cpp_info.defines = ["CPPFMU_USE_FMI_1_0=1"] self.cpp_info.requires = ["fmi1::cosim"] + elif self.options.use_fmi_version == 3: + self.output.info("Define fmi3") + self.cpp_info.defines = ["CPPFMU_USE_FMI_3_0=1"] + self.cpp_info.requires = ["fmi3::fmi3"] else: self.cpp_info.requires = ["fmi2::fmi2"] diff --git a/cppfmu_common.hpp b/cppfmu_common.hpp index 40d4ba4..8b22c5c 100644 --- a/cppfmu_common.hpp +++ b/cppfmu_common.hpp @@ -20,6 +20,8 @@ extern "C" { #ifdef CPPFMU_USE_FMI_1_0 # include +#elif defined(CPPFMU_USE_FMI_3_0) +# include #else # include #endif @@ -63,6 +65,37 @@ namespace cppfmu const FMIStatus FMIError = fmiError; const FMIStatus FMIFatal = fmiFatal; const FMIStatus FMIPending = fmiPending; +#elif defined(CPPFMU_USE_FMI_3_0) + typedef fmi3Float64 FMIReal; + typedef fmi3Float32 FMIFloat32; + typedef fmi3Int32 FMIInteger; + typedef fmi3Int8 FMIInt8; + typedef fmi3UInt8 FMIUInt8; + typedef fmi3Int16 FMIInt16; + typedef fmi3UInt16 FMIUInt16; + typedef fmi3Int64 FMIInt64; + typedef fmi3UInt64 FMIUInt64; + typedef fmi3UInt32 FMIUInt32; + typedef fmi3Boolean FMIBoolean; + typedef fmi3String FMIString; + typedef fmi3Byte FMIByte; + typedef fmi3Binary FMIBinary; + typedef fmi3Clock FMIClock; + typedef fmi3Instance FMIComponent; + typedef fmi3InstanceEnvironment FMIComponentEnvironment; + typedef fmi3FMUState FMIFMUState; + typedef fmi3Status FMIStatus; + typedef fmi3ValueReference FMIValueReference; + typedef fmi3DependencyKind FMIDependencyKind; + + const FMIBoolean FMIFalse = fmi3False; + const FMIBoolean FMITrue = fmi3True; + + const FMIStatus FMIOK = fmi3OK; + const FMIStatus FMIWarning = fmi3Warning; + const FMIStatus FMIDiscard = fmi3Discard; + const FMIStatus FMIError = fmi3Error; + const FMIStatus FMIFatal = fmi3Fatal; #else typedef fmi2Real FMIReal; typedef fmi2Integer FMIInteger; @@ -107,6 +140,14 @@ class FatalError : public std::runtime_error }; +/* An alias for a std::unique_ptr specialisation where the deleter is general + * and independent of the type of the object pointed to. + */ +template +using UniquePtr = std::unique_ptr>; + + +#ifndef CPPFMU_USE_FMI_3_0 // ============================================================================ // MEMORY MANAGEMENT // ============================================================================ @@ -291,14 +332,6 @@ void Delete(const Memory& memory, T* obj) CPPFMU_NOEXCEPT } -/* An alias for a std::unique_ptr specialisation where the deleter is general - * and independent of the type of the object pointed to. This is used for the - * return type of AllocateUnique() below. - */ -template -using UniquePtr = std::unique_ptr>; - - /* Creates an object of type T which is managed by a std::unique_ptr. * The object is created using cppfmu::New(), and when the time comes, it is * destroyed using cppfmu::Delete(). @@ -402,6 +435,7 @@ class Logger const FMICallbackLogger m_fmiLogger; std::shared_ptr m_settings; }; +#endif // CPPFMU_USE_FMI_3_0 } // namespace cppfmu diff --git a/cppfmu_cs_fmi3.cpp b/cppfmu_cs_fmi3.cpp new file mode 100644 index 0000000..3cc39b3 --- /dev/null +++ b/cppfmu_cs_fmi3.cpp @@ -0,0 +1,489 @@ +/* Copyright 2016-2026, SINTEF Ocean. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "cppfmu_cs_fmi3.hpp" + +#include + + +namespace cppfmu +{ + +// ============================================================================= +// SlaveInstance3 +// ============================================================================= + + +void SlaveInstance3::EnterInitializationMode( + FMIBoolean /*toleranceDefined*/, + FMIReal /*tolerance*/, + FMIReal /*startTime*/, + FMIBoolean /*stopTimeDefined*/, + FMIReal /*stopTime*/) +{ + // Do nothing +} + + +void SlaveInstance3::ExitInitializationMode() +{ + // Do nothing +} + + +void SlaveInstance3::Terminate() +{ + // Do nothing +} + + +void SlaveInstance3::Reset() +{ + // Do nothing +} + + +void SlaveInstance3::SetFloat32( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIFloat32 /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetFloat64( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIReal /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetInt8( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIInt8 /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetUInt8( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIUInt8 /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetInt16( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIInt16 /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetUInt16( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIUInt16 /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetInt32( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIInteger /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetUInt32( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIUInt32 /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetInt64( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIInt64 /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetUInt64( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIUInt64 /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetBoolean( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIBoolean /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetString( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIString /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetBinary( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const std::size_t /*sizes*/[], + const FMIBinary /*value*/[], + std::size_t /*nValues*/) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::SetClock( + const FMIValueReference /*vr*/[], + std::size_t nvr, + const FMIClock /*value*/[]) +{ + if (nvr != 0) { + throw std::logic_error("Attempted to set nonexistent variable"); + } +} + + +void SlaveInstance3::GetFloat32( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIFloat32 /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetFloat64( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIReal /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetInt8( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIInt8 /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetUInt8( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIUInt8 /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetInt16( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIInt16 /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetUInt16( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIUInt16 /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetInt32( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIInteger /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetUInt32( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIUInt32 /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetInt64( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIInt64 /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetUInt64( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIUInt64 /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetBoolean( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIBoolean /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetString( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIString /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetBinary( + const FMIValueReference /*vr*/[], + std::size_t nvr, + std::size_t /*sizes*/[], + FMIBinary /*value*/[], + std::size_t /*nValues*/) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetClock( + const FMIValueReference /*vr*/[], + std::size_t nvr, + FMIClock /*value*/[]) const +{ + if (nvr != 0) { + throw std::logic_error("Attempted to get nonexistent variable"); + } +} + + +void SlaveInstance3::GetFMUState(FMIFMUState* /*state*/) +{ + throw std::logic_error("Operation not supported: get FMU state"); +} + + +void SlaveInstance3::SetFMUState(FMIFMUState /*state*/) +{ + throw std::logic_error("Operation not supported: set FMU state"); +} + + +void SlaveInstance3::FreeFMUState(FMIFMUState /*state*/) +{ + throw std::logic_error("Operation not supported: free FMU state"); +} + + +std::size_t SlaveInstance3::SerializedFMUStateSize(FMIFMUState /*state*/) +{ + throw std::logic_error("Operation not supported: get serialized FMU state size"); +} + + +void SlaveInstance3::SerializeFMUState( + FMIFMUState /*state*/, + FMIByte /*data*/[], + std::size_t /*size*/) +{ + throw std::logic_error("Operation not supported: serialize FMU state"); +} + + +FMIFMUState SlaveInstance3::DeserializeFMUState( + const FMIByte /*data*/[], + std::size_t /*size*/) +{ + throw std::logic_error("Operation not supported: deserialize FMU state"); +} + + +void SlaveInstance3::GetDirectionalDerivative( + const FMIValueReference /*unknowns*/[], + std::size_t /*nUnknowns*/, + const FMIValueReference /*knowns*/[], + std::size_t /*nKnowns*/, + const FMIReal /*seed*/[], + std::size_t /*nSeed*/, + FMIReal /*sensitivity*/[], + std::size_t /*nSensitivity*/) const +{ + throw std::logic_error("Operation not supported: get directional derivative"); +} + + +void SlaveInstance3::GetAdjointDerivative( + const FMIValueReference /*unknowns*/[], + std::size_t /*nUnknowns*/, + const FMIValueReference /*knowns*/[], + std::size_t /*nKnowns*/, + const FMIReal /*seed*/[], + std::size_t /*nSeed*/, + FMIReal /*sensitivity*/[], + std::size_t /*nSensitivity*/) const +{ + throw std::logic_error("Operation not supported: get adjoint derivative"); +} + + +void SlaveInstance3::GetVariableDependencies( + FMIValueReference /*dependent*/, + const std::size_t /*elementIndicesOfDependent*/[], + FMIValueReference /*independents*/[], + std::size_t /*elementIndicesOfIndependents*/[], + FMIDependencyKind /*dependencyKinds*/[], + std::size_t /*nDependencies*/) const +{ + throw std::logic_error("Operation not supported: get variable dependencies"); +} + + +void SlaveInstance3::GetOutputDerivatives( + const FMIValueReference /*vr*/[], + std::size_t /*nvr*/, + const FMIInteger /*orders*/[], + FMIReal /*values*/[], + std::size_t /*nValues*/) const +{ + throw std::logic_error("Operation not supported: get output derivatives"); +} + + +std::size_t SlaveInstance3::GetNumberOfVariableDependencies( + FMIValueReference /*valueReference*/) const +{ + throw std::logic_error("Operation not supported: get number of variable dependencies"); +} + + +SlaveInstance3::~SlaveInstance3() CPPFMU_NOEXCEPT +{ + // Do nothing +} + + +} // namespace cppfmu diff --git a/cppfmu_cs_fmi3.hpp b/cppfmu_cs_fmi3.hpp new file mode 100644 index 0000000..853c2f5 --- /dev/null +++ b/cppfmu_cs_fmi3.hpp @@ -0,0 +1,469 @@ +/* Copyright 2016-2026, SINTEF Ocean. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#ifndef CPPFMU_CS_FMI3_HPP +#define CPPFMU_CS_FMI3_HPP + +#include +#include +#include +#include +#include "cppfmu_common.hpp" + +namespace cppfmu +{ + +/* ============================================================================ + * FMI 3.0 CO-SIMULATION INTERFACE + * ============================================================================ + */ + +/* A base class for FMI 3.0 co-simulation slave instances. + * + * To implement a co-simulation slave, create a class which publicly derives + * from this one and override its virtual methods as required. DoStep() is + * the only function which it is mandatory to override. + * + * The methods map directly to the C functions defined by FMI 3.0 for + * Co-Simulation, so the documentation here is intentionally sparse. + * We refer to the FMI 3.0 specification for detailed information. + */ +class SlaveInstance3 +{ +public: + /* Called from fmi3EnterInitializationMode(). + * Does nothing by default. + */ + virtual void EnterInitializationMode( + FMIBoolean toleranceDefined, + FMIReal tolerance, + FMIReal startTime, + FMIBoolean stopTimeDefined, + FMIReal stopTime); + + /* Called from fmi3ExitInitializationMode(). + * Does nothing by default. + */ + virtual void ExitInitializationMode(); + + /* Called from fmi3Terminate(). + * Does nothing by default. + */ + virtual void Terminate(); + + /* Called from fmi3Reset(). + * Does nothing by default. + */ + virtual void Reset(); + + /* Called from fmi3SetFloat32(). + * Throws std::logic_error by default. + */ + virtual void SetFloat32( + const FMIValueReference vr[], + std::size_t nvr, + const FMIFloat32 value[], + std::size_t nValues); + + /* Called from fmi3SetFloat64(). + * Throws std::logic_error by default. + */ + virtual void SetFloat64( + const FMIValueReference vr[], + std::size_t nvr, + const FMIReal value[], + std::size_t nValues); + + /* Called from fmi3SetInt8(). + * Throws std::logic_error by default. + */ + virtual void SetInt8( + const FMIValueReference vr[], + std::size_t nvr, + const FMIInt8 value[], + std::size_t nValues); + + /* Called from fmi3SetUInt8(). + * Throws std::logic_error by default. + */ + virtual void SetUInt8( + const FMIValueReference vr[], + std::size_t nvr, + const FMIUInt8 value[], + std::size_t nValues); + + /* Called from fmi3SetInt16(). + * Throws std::logic_error by default. + */ + virtual void SetInt16( + const FMIValueReference vr[], + std::size_t nvr, + const FMIInt16 value[], + std::size_t nValues); + + /* Called from fmi3SetUInt16(). + * Throws std::logic_error by default. + */ + virtual void SetUInt16( + const FMIValueReference vr[], + std::size_t nvr, + const FMIUInt16 value[], + std::size_t nValues); + + /* Called from fmi3SetInt32(). + * Throws std::logic_error by default. + */ + virtual void SetInt32( + const FMIValueReference vr[], + std::size_t nvr, + const FMIInteger value[], + std::size_t nValues); + + /* Called from fmi3SetUInt32(). + * Throws std::logic_error by default. + */ + virtual void SetUInt32( + const FMIValueReference vr[], + std::size_t nvr, + const FMIUInt32 value[], + std::size_t nValues); + + /* Called from fmi3SetInt64(). + * Throws std::logic_error by default. + */ + virtual void SetInt64( + const FMIValueReference vr[], + std::size_t nvr, + const FMIInt64 value[], + std::size_t nValues); + + /* Called from fmi3SetUInt64(). + * Throws std::logic_error by default. + */ + virtual void SetUInt64( + const FMIValueReference vr[], + std::size_t nvr, + const FMIUInt64 value[], + std::size_t nValues); + + /* Called from fmi3SetBoolean(). + * Throws std::logic_error by default. + */ + virtual void SetBoolean( + const FMIValueReference vr[], + std::size_t nvr, + const FMIBoolean value[], + std::size_t nValues); + + /* Called from fmi3SetString(). + * Throws std::logic_error by default. + */ + virtual void SetString( + const FMIValueReference vr[], + std::size_t nvr, + const FMIString value[], + std::size_t nValues); + + /* Called from fmi3SetBinary(). + * Throws std::logic_error by default. + */ + virtual void SetBinary( + const FMIValueReference vr[], + std::size_t nvr, + const std::size_t sizes[], + const FMIBinary value[], + std::size_t nValues); + + /* Called from fmi3SetClock(). + * Throws std::logic_error by default. + */ + virtual void SetClock( + const FMIValueReference vr[], + std::size_t nvr, + const FMIClock value[]); + + /* Called from fmi3GetFloat32(). + * Throws std::logic_error by default. + */ + virtual void GetFloat32( + const FMIValueReference vr[], + std::size_t nvr, + FMIFloat32 value[], + std::size_t nValues) const; + + /* Called from fmi3GetFloat64(). + * Throws std::logic_error by default. + */ + virtual void GetFloat64( + const FMIValueReference vr[], + std::size_t nvr, + FMIReal value[], + std::size_t nValues) const; + + /* Called from fmi3GetInt8(). + * Throws std::logic_error by default. + */ + virtual void GetInt8( + const FMIValueReference vr[], + std::size_t nvr, + FMIInt8 value[], + std::size_t nValues) const; + + /* Called from fmi3GetUInt8(). + * Throws std::logic_error by default. + */ + virtual void GetUInt8( + const FMIValueReference vr[], + std::size_t nvr, + FMIUInt8 value[], + std::size_t nValues) const; + + /* Called from fmi3GetInt16(). + * Throws std::logic_error by default. + */ + virtual void GetInt16( + const FMIValueReference vr[], + std::size_t nvr, + FMIInt16 value[], + std::size_t nValues) const; + + /* Called from fmi3GetUInt16(). + * Throws std::logic_error by default. + */ + virtual void GetUInt16( + const FMIValueReference vr[], + std::size_t nvr, + FMIUInt16 value[], + std::size_t nValues) const; + + /* Called from fmi3GetInt32(). + * Throws std::logic_error by default. + */ + virtual void GetInt32( + const FMIValueReference vr[], + std::size_t nvr, + FMIInteger value[], + std::size_t nValues) const; + + /* Called from fmi3GetUInt32(). + * Throws std::logic_error by default. + */ + virtual void GetUInt32( + const FMIValueReference vr[], + std::size_t nvr, + FMIUInt32 value[], + std::size_t nValues) const; + + /* Called from fmi3GetInt64(). + * Throws std::logic_error by default. + */ + virtual void GetInt64( + const FMIValueReference vr[], + std::size_t nvr, + FMIInt64 value[], + std::size_t nValues) const; + + /* Called from fmi3GetUInt64(). + * Throws std::logic_error by default. + */ + virtual void GetUInt64( + const FMIValueReference vr[], + std::size_t nvr, + FMIUInt64 value[], + std::size_t nValues) const; + + /* Called from fmi3GetBoolean(). + * Throws std::logic_error by default. + */ + virtual void GetBoolean( + const FMIValueReference vr[], + std::size_t nvr, + FMIBoolean value[], + std::size_t nValues) const; + + /* Called from fmi3GetString(). + * Throws std::logic_error by default. + */ + virtual void GetString( + const FMIValueReference vr[], + std::size_t nvr, + FMIString value[], + std::size_t nValues) const; + + /* Called from fmi3GetBinary(). + * Throws std::logic_error by default. + */ + virtual void GetBinary( + const FMIValueReference vr[], + std::size_t nvr, + std::size_t sizes[], + FMIBinary value[], + std::size_t nValues) const; + + /* Called from fmi3GetClock(). + * Throws std::logic_error by default. + */ + virtual void GetClock( + const FMIValueReference vr[], + std::size_t nvr, + FMIClock value[]) const; + + /* Called from fmi3GetFMUState(). + * Throws std::logic_error by default. + */ + virtual void GetFMUState(FMIFMUState* state); + + /* Called from fmi3SetFMUState(). + * Throws std::logic_error by default. + */ + virtual void SetFMUState(FMIFMUState state); + + /* Called from fmi3FreeFMUState(). + * Throws std::logic_error by default. + */ + virtual void FreeFMUState(FMIFMUState state); + + /* Called from fmi3SerializedFMUStateSize(). + * Throws std::logic_error by default. + */ + virtual std::size_t SerializedFMUStateSize(FMIFMUState state); + + /* Called from fmi3SerializeFMUState(). + * Throws std::logic_error by default. + */ + virtual void SerializeFMUState( + FMIFMUState state, + FMIByte data[], + std::size_t size); + + /* Called from fmi3DeserializeFMUState(). + * Throws std::logic_error by default. + */ + virtual FMIFMUState DeserializeFMUState( + const FMIByte data[], + std::size_t size); + + /* Called from fmi3GetDirectionalDerivative(). + * Throws std::logic_error by default. + */ + virtual void GetDirectionalDerivative( + const FMIValueReference unknowns[], + std::size_t nUnknowns, + const FMIValueReference knowns[], + std::size_t nKnowns, + const FMIReal seed[], + std::size_t nSeed, + FMIReal sensitivity[], + std::size_t nSensitivity) const; + + /* Called from fmi3GetAdjointDerivative(). + * Throws std::logic_error by default. + */ + virtual void GetAdjointDerivative( + const FMIValueReference unknowns[], + std::size_t nUnknowns, + const FMIValueReference knowns[], + std::size_t nKnowns, + const FMIReal seed[], + std::size_t nSeed, + FMIReal sensitivity[], + std::size_t nSensitivity) const; + + /* Called from fmi3GetVariableDependencies(). + * Throws std::logic_error by default. + */ + virtual void GetVariableDependencies( + FMIValueReference dependent, + const std::size_t elementIndicesOfDependent[], + FMIValueReference independents[], + std::size_t elementIndicesOfIndependents[], + FMIDependencyKind dependencyKinds[], + std::size_t nDependencies) const; + + /* Called from fmi3GetOutputDerivatives(). + * Throws std::logic_error by default. + */ + virtual void GetOutputDerivatives( + const FMIValueReference vr[], + std::size_t nvr, + const FMIInteger orders[], + FMIReal values[], + std::size_t nValues) const; + + /* Called from fmi3GetNumberOfVariableDependencies(). + * Throws std::logic_error by default. + */ + virtual std::size_t GetNumberOfVariableDependencies( + FMIValueReference valueReference) const; + + // Called from fmi3DoStep(). Must be implemented in model code. + // Returns true for OK, false for discard (early return). + // Output parameters are set by the implementation. + virtual bool DoStep( + FMIReal currentCommunicationPoint, + FMIReal communicationStepSize, + FMIBoolean noSetFMUStatePriorToCurrentPoint, + FMIBoolean& eventHandlingNeeded, + FMIBoolean& terminateSimulation, + FMIBoolean& earlyReturn, + FMIReal& lastSuccessfulTime) = 0; + + // The instance is destroyed in fmi3FreeInstance(). + virtual ~SlaveInstance3() CPPFMU_NOEXCEPT; +}; + +} // namespace cppfmu + + +/* A function which must be defined by model code, and which should create + * and return a new slave instance. + * + * The returned instance must be managed by a std::unique_ptr with a deleter + * of type std::function that takes care of freeing the memory. + * The simplest way to set this up is to use cppfmu::AllocateUnique3() to + * create the slave instance. + * + * Most of its parameters correspond to those of fmi3InstantiateCoSimulation, + * except that the callback functions have been replaced with more convenient + * types: + * + * logger = A std::function which the model code can use to log messages. + * The messages are forwarded to the simulation environment's + * logging facilities. + * + * Note that this function is declared in the global namespace. + */ +cppfmu::UniquePtr CppfmuInstantiateSlave( + cppfmu::FMIString instanceName, + cppfmu::FMIString instantiationToken, + cppfmu::FMIString resourceLocation, + cppfmu::FMIBoolean visible, + cppfmu::FMIBoolean loggingOn, + cppfmu::FMIBoolean eventModeUsed, + cppfmu::FMIBoolean earlyReturnAllowed, + const cppfmu::FMIValueReference requiredIntermediateVariables[], + std::size_t nRequiredIntermediateVariables, + cppfmu::FMIComponentEnvironment instanceEnvironment, + std::function logger); + + +namespace cppfmu +{ + +/* Creates an object of type T which is managed by a std::unique_ptr. + * For FMI 3.0, memory is managed by standard C++ (no FMI memory callbacks). + */ +template +UniquePtr AllocateUnique3(Args&&... args) +{ + return UniquePtr{ + new T(std::forward(args)...), + [] (void* ptr) { delete reinterpret_cast(ptr); }}; +} + +} // namespace cppfmu + + +#endif // header guard diff --git a/fmi3_functions.cpp b/fmi3_functions.cpp new file mode 100644 index 0000000..909364a --- /dev/null +++ b/fmi3_functions.cpp @@ -0,0 +1,1162 @@ +/* Copyright 2016-2026, SINTEF Ocean. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include +#include +#include +#include + +#include "cppfmu_cs_fmi3.hpp" + + +namespace +{ + struct Component + { + Component( + cppfmu::FMIString instanceName, + cppfmu::FMIComponentEnvironment instanceEnvironment, + fmi3LogMessageCallback logMessage, + cppfmu::FMIBoolean loggingOn) + : instanceName{instanceName ? instanceName : ""} + , instanceEnvironment{instanceEnvironment} + , logMessage{logMessage} + , debugLoggingEnabled{loggingOn == fmi3True} + , lastSuccessfulTime{std::numeric_limits::quiet_NaN()} + { + } + + void Log(fmi3Status status, cppfmu::FMIString category, cppfmu::FMIString message) const + { + if (logMessage) { + logMessage(instanceEnvironment, status, category, message); + } + } + + std::string instanceName; + cppfmu::FMIComponentEnvironment instanceEnvironment; + fmi3LogMessageCallback logMessage; + bool debugLoggingEnabled; + cppfmu::UniquePtr slave; + cppfmu::FMIReal lastSuccessfulTime; + }; + + cppfmu::FMIStatus toCppfmuStatus(fmi3Status s) + { + switch (s) { + case fmi3OK: return cppfmu::FMIOK; + case fmi3Warning: return cppfmu::FMIWarning; + case fmi3Discard: return cppfmu::FMIDiscard; + case fmi3Error: return cppfmu::FMIError; + case fmi3Fatal: return cppfmu::FMIFatal; + default: return cppfmu::FMIError; + } + } +} + + +extern "C" +{ + +// ============================================================================= +// FMI 3.0 functions +// ============================================================================= + + +const char* fmi3GetVersion() +{ + return fmi3Version; +} + + +fmi3Status fmi3SetDebugLogging( + fmi3Instance instance, + fmi3Boolean loggingOn, + size_t nCategories, + const fmi3String categories[]) +{ + (void) nCategories; + (void) categories; + auto component = reinterpret_cast(instance); + component->debugLoggingEnabled = (loggingOn == fmi3True); + return fmi3OK; +} + + +fmi3Instance fmi3InstantiateCoSimulation( + fmi3String instanceName, + fmi3String instantiationToken, + fmi3String resourceLocation, + fmi3Boolean visible, + fmi3Boolean loggingOn, + fmi3Boolean eventModeUsed, + fmi3Boolean earlyReturnAllowed, + const fmi3ValueReference requiredIntermediateVariables[], + size_t nRequiredIntermediateVariables, + fmi3InstanceEnvironment instanceEnvironment, + fmi3LogMessageCallback logMessage, + fmi3IntermediateUpdateCallback intermediateUpdate) +{ + (void) intermediateUpdate; + try { + auto component = new Component( + instanceName, instanceEnvironment, logMessage, loggingOn); + + auto loggerFn = [component](cppfmu::FMIStatus status, cppfmu::FMIString category, cppfmu::FMIString message) { + component->Log(toCppfmuStatus(status), category, message); + }; + + component->slave = CppfmuInstantiateSlave( + instanceName, + instantiationToken, + resourceLocation, + visible, + loggingOn, + eventModeUsed, + earlyReturnAllowed, + requiredIntermediateVariables, + nRequiredIntermediateVariables, + instanceEnvironment, + loggerFn); + + return component; + } catch (const cppfmu::FatalError& e) { + if (logMessage) { + logMessage(instanceEnvironment, fmi3Fatal, "cppfmu", e.what()); + } + return nullptr; + } catch (const std::exception& e) { + if (logMessage) { + logMessage(instanceEnvironment, fmi3Error, "cppfmu", e.what()); + } + return nullptr; + } +} + + +void fmi3FreeInstance(fmi3Instance instance) +{ + auto component = reinterpret_cast(instance); + delete component; +} + + +fmi3Status fmi3EnterInitializationMode( + fmi3Instance instance, + fmi3Boolean toleranceDefined, + fmi3Float64 tolerance, + fmi3Float64 startTime, + fmi3Boolean stopTimeDefined, + fmi3Float64 stopTime) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->EnterInitializationMode( + toleranceDefined, + static_cast(tolerance), + static_cast(startTime), + stopTimeDefined, + static_cast(stopTime)); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3ExitInitializationMode(fmi3Instance instance) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->ExitInitializationMode(); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3EnterEventMode(fmi3Instance instance) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EnterEventMode"); + return fmi3Error; +} + + +fmi3Status fmi3EnterStepMode(fmi3Instance instance) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EnterStepMode"); + return fmi3Error; +} + + +fmi3Status fmi3Terminate(fmi3Instance instance) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->Terminate(); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3Reset(fmi3Instance instance) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->Reset(); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +// ============================================================================ +// Get functions +// ============================================================================ + + +fmi3Status fmi3GetFloat32( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3Float32 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetFloat32(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetFloat64( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3Float64 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetFloat64(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetInt8( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3Int8 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetInt8(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetUInt8( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3UInt8 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetUInt8(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetInt16( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3Int16 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetInt16(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetUInt16( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3UInt16 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetUInt16(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetInt32( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3Int32 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetInt32(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetUInt32( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3UInt32 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetUInt32(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetInt64( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3Int64 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetInt64(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetUInt64( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3UInt64 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetUInt64(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetBoolean( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3Boolean value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetBoolean(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetString( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3String value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetString(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetBinary( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + size_t sizes[], + fmi3Binary value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetBinary(vr, nvr, sizes, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetClock( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + fmi3Clock value[]) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetClock(vr, nvr, value); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +// ============================================================================ +// Set functions +// ============================================================================ + + +fmi3Status fmi3SetFloat32( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3Float32 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetFloat32(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetFloat64( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3Float64 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetFloat64(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetInt8( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3Int8 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetInt8(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetUInt8( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3UInt8 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetUInt8(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetInt16( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3Int16 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetInt16(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetUInt16( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3UInt16 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetUInt16(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetInt32( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3Int32 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetInt32(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetUInt32( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3UInt32 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetUInt32(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetInt64( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3Int64 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetInt64(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetUInt64( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3UInt64 value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetUInt64(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetBoolean( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3Boolean value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetBoolean(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetString( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3String value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetString(vr, nvr, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetBinary( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const size_t sizes[], + const fmi3Binary value[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetBinary(vr, nvr, sizes, value, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetClock( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3Clock value[]) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetClock(vr, nvr, value); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +// ============================================================================ +// FMU state functions +// ============================================================================ + + +fmi3Status fmi3GetFMUState( + fmi3Instance instance, + fmi3FMUState* state) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetFMUState(state); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SetFMUState( + fmi3Instance instance, + fmi3FMUState state) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SetFMUState(state); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3FreeFMUState( + fmi3Instance instance, + fmi3FMUState* state) +{ + if (state == nullptr || *state == nullptr) return fmi3OK; + auto component = reinterpret_cast(instance); + try { + component->slave->FreeFMUState(*state); + *state = nullptr; + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SerializedFMUStateSize( + fmi3Instance instance, + fmi3FMUState state, + size_t* size) +{ + auto component = reinterpret_cast(instance); + try { + *size = component->slave->SerializedFMUStateSize(state); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3SerializeFMUState( + fmi3Instance instance, + fmi3FMUState state, + fmi3Byte data[], + size_t size) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->SerializeFMUState(state, data, size); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3DeserializeFMUState( + fmi3Instance instance, + const fmi3Byte data[], + size_t size, + fmi3FMUState* state) +{ + auto component = reinterpret_cast(instance); + try { + *state = component->slave->DeserializeFMUState(data, size); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +// ============================================================================ +// Derivative and dependency functions +// ============================================================================ + + +fmi3Status fmi3GetDirectionalDerivative( + fmi3Instance instance, + const fmi3ValueReference unknowns[], + size_t nUnknowns, + const fmi3ValueReference knowns[], + size_t nKnowns, + const fmi3Float64 seed[], + size_t nSeed, + fmi3Float64 sensitivity[], + size_t nSensitivity) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetDirectionalDerivative( + unknowns, nUnknowns, + knowns, nKnowns, + seed, nSeed, + sensitivity, nSensitivity); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetAdjointDerivative( + fmi3Instance instance, + const fmi3ValueReference unknowns[], + size_t nUnknowns, + const fmi3ValueReference knowns[], + size_t nKnowns, + const fmi3Float64 seed[], + size_t nSeed, + fmi3Float64 sensitivity[], + size_t nSensitivity) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetAdjointDerivative( + unknowns, nUnknowns, + knowns, nKnowns, + seed, nSeed, + sensitivity, nSensitivity); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetVariableDependencies( + fmi3Instance instance, + fmi3ValueReference dependent, + size_t elementIndicesOfDependent[], + fmi3ValueReference independents[], + size_t elementIndicesOfIndependents[], + fmi3DependencyKind dependencyKinds[], + size_t nDependencies) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetVariableDependencies( + dependent, + elementIndicesOfDependent, + independents, + elementIndicesOfIndependents, + dependencyKinds, + nDependencies); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetNumberOfVariableDependencies( + fmi3Instance instance, + fmi3ValueReference valueReference, + size_t* nDependencies) +{ + auto component = reinterpret_cast(instance); + try { + *nDependencies = component->slave->GetNumberOfVariableDependencies(valueReference); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3GetOutputDerivatives( + fmi3Instance instance, + const fmi3ValueReference vr[], + size_t nvr, + const fmi3Int32 orders[], + fmi3Float64 values[], + size_t nValues) +{ + auto component = reinterpret_cast(instance); + try { + component->slave->GetOutputDerivatives(vr, nvr, orders, values, nValues); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +// ============================================================================ +// Co-simulation functions +// ============================================================================ + + +fmi3Status fmi3DoStep( + fmi3Instance instance, + fmi3Float64 currentCommunicationPoint, + fmi3Float64 communicationStepSize, + fmi3Boolean noSetFMUStatePriorToCurrentPoint, + fmi3Boolean* eventHandlingNeeded, + fmi3Boolean* terminateSimulation, + fmi3Boolean* earlyReturn, + fmi3Float64* lastSuccessfulTime) +{ + auto component = reinterpret_cast(instance); + try { + fmi3Boolean eventNeeded = fmi3False; + fmi3Boolean termSim = fmi3False; + fmi3Boolean earlyRet = fmi3False; + fmi3Float64 lastTime = currentCommunicationPoint + communicationStepSize; + + const auto ok = component->slave->DoStep( + static_cast(currentCommunicationPoint), + static_cast(communicationStepSize), + noSetFMUStatePriorToCurrentPoint, + eventNeeded, + termSim, + earlyRet, + lastTime); + + if (eventHandlingNeeded) *eventHandlingNeeded = eventNeeded; + if (terminateSimulation) *terminateSimulation = termSim; + if (earlyReturn) *earlyReturn = earlyRet; + if (lastSuccessfulTime) *lastSuccessfulTime = lastTime; + + component->lastSuccessfulTime = static_cast(lastTime); + + return ok ? fmi3OK : fmi3Discard; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +// ============================================================================ +// Unsupported stub functions +// ============================================================================ + + +fmi3Status fmi3GetNumberOfEventIndicators( + fmi3Instance instance, + size_t* nEventIndicators) +{ + (void) instance; + *nEventIndicators = 0; + return fmi3OK; +} + + +fmi3Status fmi3GetNumberOfContinuousStates( + fmi3Instance instance, + size_t* nContinuousStates) +{ + (void) instance; + *nContinuousStates = 0; + return fmi3OK; +} + + +} // extern "C" diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 7eea7ba..de20790 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -5,6 +5,22 @@ project(cppfmu-test) find_package(cppfmu REQUIRED) set(testTarget "tester") -add_executable(${testTarget} main.cpp - ${CMAKE_BINARY_DIR}/cppfmu/fmi_functions.cpp) + +# Get the cppfmu package folder (CMakeDeps sets cppfmu_PACKAGE_FOLDER_RELEASE for single-config) +set(cppfmu_pkg "${cppfmu_PACKAGE_FOLDER_RELEASE}") +if(NOT cppfmu_pkg) + set(cppfmu_pkg "${cppfmu_PACKAGE_FOLDER_DEBUG}") +endif() +if(NOT cppfmu_pkg) + set(cppfmu_pkg "${cppfmu_PACKAGE_FOLDER}") +endif() + +# Determine which FMI functions file to use based on what exists +if(EXISTS "${cppfmu_pkg}/src/fmi3_functions.cpp") + set(fmi_funcs "${cppfmu_pkg}/src/fmi3_functions.cpp") +else() + set(fmi_funcs "${cppfmu_pkg}/src/fmi_functions.cpp") +endif() + +add_executable(${testTarget} main.cpp ${fmi_funcs}) target_link_libraries(${testTarget} PUBLIC cppfmu::cppfmu) diff --git a/test_package/main.cpp b/test_package/main.cpp index 69ec169..58ccf06 100644 --- a/test_package/main.cpp +++ b/test_package/main.cpp @@ -1,5 +1,19 @@ #include +#ifdef CPPFMU_USE_FMI_3_0 +#include "cppfmu_cs_fmi3.hpp" + +cppfmu::UniquePtr CppfmuInstantiateSlave( + cppfmu::FMIString, cppfmu::FMIString, + cppfmu::FMIString, cppfmu::FMIBoolean, cppfmu::FMIBoolean, + cppfmu::FMIBoolean, cppfmu::FMIBoolean, + const cppfmu::FMIValueReference[], std::size_t, + cppfmu::FMIComponentEnvironment, + std::function) { + + return nullptr; +} +#else #include "cppfmu_cs.hpp" cppfmu::UniquePtr CppfmuInstantiateSlave( @@ -10,12 +24,15 @@ cppfmu::UniquePtr CppfmuInstantiateSlave( return nullptr; } +#endif int main() { #ifdef CPPFMU_USE_FMI_1_0 std::cout << "Platform: " << fmiGetTypesPlatform() << std::endl; +#elif defined(CPPFMU_USE_FMI_3_0) + std::cout << "Platform: " << fmi3GetVersion() << std::endl; #else std::cout << "Platform: " << fmi2GetTypesPlatform() << std::endl; #endif diff --git a/version.txt b/version.txt index 26aaba0..f0bb29e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.0 +1.3.0 From 1ff514bcafcb8c3d5cdfd57fb63176d7d5ce67f1 Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Wed, 29 Apr 2026 18:28:30 +0200 Subject: [PATCH 02/10] Add FMI 3.0 test coverage and fix implementation issues - Add FMIByte type alias for FMI 3.0 - Remove dead instanceName storage in FMI 3.0 Component - Enforce debugLoggingEnabled in FMI 3.0 Logger (skip non-error messages) - Add GetNumberOfEventIndicators/GetNumberOfContinuousStates virtual methods - Create tests/cs_slave_fmi3.cpp (test slave for FMI 3.0) - Create tests/cs_test_fmi3.cpp (integration test for FMI 3.0) - Update CMakeLists.txt to build cs_test for FMI 3.0 --- CMakeLists.txt | 30 +++--- cppfmu_common.hpp | 1 + cppfmu_cs_fmi3.cpp | 12 +++ cppfmu_cs_fmi3.hpp | 10 ++ fmi3_functions.cpp | 42 +++++--- tests/cs_slave_fmi3.cpp | 166 +++++++++++++++++++++++++++++ tests/cs_test_fmi3.cpp | 227 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 458 insertions(+), 30 deletions(-) create mode 100644 tests/cs_slave_fmi3.cpp create mode 100644 tests/cs_test_fmi3.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b46367..ad0feab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,23 +43,21 @@ else() install(FILES ${CMAKE_SOURCE_DIR}/fmi_functions.cpp DESTINATION ${CMAKE_INSTALL_PREFIX}/src) endif() -if(NOT CPPFMU_FMI_1 AND NOT CPPFMU_FMI_3) - add_executable(cs_test - "tests/cs_test.cpp" - "tests/cs_slave.cpp" - "fmi_functions.cpp" - ) +if(NOT CPPFMU_FMI_1) + if(CPPFMU_FMI_3) + add_executable(cs_test + "tests/cs_test_fmi3.cpp" + "tests/cs_slave_fmi3.cpp" + "fmi3_functions.cpp" + ) + else() + add_executable(cs_test + "tests/cs_test.cpp" + "tests/cs_slave.cpp" + "fmi_functions.cpp" + ) + endif() target_compile_features(cs_test PRIVATE cxx_std_11) target_link_libraries(cs_test PRIVATE cppfmu) add_test(NAME "cs_test" COMMAND cs_test) -else() - add_executable(cs_test_fmi1 - "tests/cs_test.cpp" - "tests/cs_slave.cpp" - "fmi_functions.cpp" - ) - target_compile_features(cs_test_fmi1 PRIVATE cxx_std_11) - target_link_libraries(cs_test_fmi1 PRIVATE cppfmu) - target_compile_definitions(cs_test_fmi1 PRIVATE CPPFMU_USE_FMI_1_0 "MODEL_IDENTIFIER=") - add_test(NAME "cs_test_fmi1" COMMAND cs_test_fmi1) endif() diff --git a/cppfmu_common.hpp b/cppfmu_common.hpp index 8b22c5c..aaa2dbf 100644 --- a/cppfmu_common.hpp +++ b/cppfmu_common.hpp @@ -79,6 +79,7 @@ namespace cppfmu typedef fmi3Boolean FMIBoolean; typedef fmi3String FMIString; typedef fmi3Byte FMIByte; + typedef fmi3Byte FMIByte; typedef fmi3Binary FMIBinary; typedef fmi3Clock FMIClock; typedef fmi3Instance FMIComponent; diff --git a/cppfmu_cs_fmi3.cpp b/cppfmu_cs_fmi3.cpp index 3cc39b3..a358b6d 100644 --- a/cppfmu_cs_fmi3.cpp +++ b/cppfmu_cs_fmi3.cpp @@ -480,6 +480,18 @@ std::size_t SlaveInstance3::GetNumberOfVariableDependencies( } +std::size_t SlaveInstance3::GetNumberOfEventIndicators() const +{ + return 0; +} + + +std::size_t SlaveInstance3::GetNumberOfContinuousStates() const +{ + return 0; +} + + SlaveInstance3::~SlaveInstance3() CPPFMU_NOEXCEPT { // Do nothing diff --git a/cppfmu_cs_fmi3.hpp b/cppfmu_cs_fmi3.hpp index 853c2f5..b9fa652 100644 --- a/cppfmu_cs_fmi3.hpp +++ b/cppfmu_cs_fmi3.hpp @@ -398,6 +398,16 @@ class SlaveInstance3 virtual std::size_t GetNumberOfVariableDependencies( FMIValueReference valueReference) const; + /* Called from fmi3GetNumberOfEventIndicators(). + * Returns 0 by default. + */ + virtual std::size_t GetNumberOfEventIndicators() const; + + /* Called from fmi3GetNumberOfContinuousStates(). + * Returns 0 by default. + */ + virtual std::size_t GetNumberOfContinuousStates() const; + // Called from fmi3DoStep(). Must be implemented in model code. // Returns true for OK, false for discard (early return). // Output parameters are set by the implementation. diff --git a/fmi3_functions.cpp b/fmi3_functions.cpp index 909364a..c4a8697 100644 --- a/fmi3_functions.cpp +++ b/fmi3_functions.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "cppfmu_cs_fmi3.hpp" @@ -16,12 +15,10 @@ namespace struct Component { Component( - cppfmu::FMIString instanceName, cppfmu::FMIComponentEnvironment instanceEnvironment, fmi3LogMessageCallback logMessage, cppfmu::FMIBoolean loggingOn) - : instanceName{instanceName ? instanceName : ""} - , instanceEnvironment{instanceEnvironment} + : instanceEnvironment{instanceEnvironment} , logMessage{logMessage} , debugLoggingEnabled{loggingOn == fmi3True} , lastSuccessfulTime{std::numeric_limits::quiet_NaN()} @@ -30,12 +27,13 @@ namespace void Log(fmi3Status status, cppfmu::FMIString category, cppfmu::FMIString message) const { - if (logMessage) { - logMessage(instanceEnvironment, status, category, message); + if (status == fmi3Fatal || status == fmi3Error || debugLoggingEnabled) { + if (logMessage) { + logMessage(instanceEnvironment, status, category, message); + } } } - std::string instanceName; cppfmu::FMIComponentEnvironment instanceEnvironment; fmi3LogMessageCallback logMessage; bool debugLoggingEnabled; @@ -102,7 +100,7 @@ fmi3Instance fmi3InstantiateCoSimulation( (void) intermediateUpdate; try { auto component = new Component( - instanceName, instanceEnvironment, logMessage, loggingOn); + instanceEnvironment, logMessage, loggingOn); auto loggerFn = [component](cppfmu::FMIStatus status, cppfmu::FMIString category, cppfmu::FMIString message) { component->Log(toCppfmuStatus(status), category, message); @@ -1143,9 +1141,17 @@ fmi3Status fmi3GetNumberOfEventIndicators( fmi3Instance instance, size_t* nEventIndicators) { - (void) instance; - *nEventIndicators = 0; - return fmi3OK; + auto component = reinterpret_cast(instance); + try { + *nEventIndicators = component->slave->GetNumberOfEventIndicators(); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } } @@ -1153,9 +1159,17 @@ fmi3Status fmi3GetNumberOfContinuousStates( fmi3Instance instance, size_t* nContinuousStates) { - (void) instance; - *nContinuousStates = 0; - return fmi3OK; + auto component = reinterpret_cast(instance); + try { + *nContinuousStates = component->slave->GetNumberOfContinuousStates(); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } } diff --git a/tests/cs_slave_fmi3.cpp b/tests/cs_slave_fmi3.cpp new file mode 100644 index 0000000..0e30541 --- /dev/null +++ b/tests/cs_slave_fmi3.cpp @@ -0,0 +1,166 @@ +#include + +#include +#include +#include + + +class TestSlave3 : public cppfmu::SlaveInstance3 +{ +public: + void SetFloat64( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + const cppfmu::FMIReal value[], + std::size_t /*nValues*/) override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 0) { + value_ = value[i]; + } else if (vr[i] == 1) { + derivativeSupported_ = (value[i] != 0.0); + } else if (vr[i] == 2) { + seed_ = value[i]; + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + + void GetFloat64( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + cppfmu::FMIReal value[], + std::size_t /*nValues*/) const override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 0) { + value[i] = value_; + } else if (vr[i] == 1) { + value[i] = derivativeSupported_ ? 1.0 : 0.0; + } else if (vr[i] == 2) { + value[i] = seed_; + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + + void GetDirectionalDerivative( + const cppfmu::FMIValueReference /*vUnknown_ref*/[], + std::size_t nUnknown, + const cppfmu::FMIValueReference /*vKnown_ref*/[], + std::size_t nKnown, + const cppfmu::FMIReal dvKnown[], + std::size_t /*nSeed*/, + cppfmu::FMIReal dvUnknown[], + std::size_t /*nSensitivity*/) const override + { + if (!derivativeSupported_) { + SlaveInstance3::GetDirectionalDerivative( + nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0); + } + for (std::size_t i = 0; i < nUnknown; ++i) { + dvUnknown[i] = 0.0; + for (std::size_t j = 0; j < nKnown; ++j) { + dvUnknown[i] += seed_ * dvKnown[j]; + } + } + } + + void GetOutputDerivatives( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + const cppfmu::FMIInteger order[], + cppfmu::FMIReal value[], + std::size_t /*nValues*/) const override + { + if (!derivativeSupported_) { + SlaveInstance3::GetOutputDerivatives(vr, nvr, order, value, 0); + } + for (std::size_t i = 0; i < nvr; ++i) { + value[i] = seed_ * static_cast(order[i]); + } + } + + void GetFMUState(cppfmu::FMIFMUState* state) override + { + auto s = (*state == nullptr) + ? new cppfmu::FMIReal + : static_cast(*state); + *s = value_; + *state = s; + } + + void SetFMUState(cppfmu::FMIFMUState state) override + { + auto s = static_cast(state); + value_ = *s; + } + + void FreeFMUState(cppfmu::FMIFMUState state) override + { + auto s = static_cast(state); + delete s; + } + + std::size_t SerializedFMUStateSize(cppfmu::FMIFMUState) override + { + return sizeof(cppfmu::FMIReal); + } + + void SerializeFMUState( + cppfmu::FMIFMUState state, + cppfmu::FMIByte data[], + std::size_t size) override + { + auto s = static_cast(state); + std::memcpy(data, s, sizeof *s); + } + + cppfmu::FMIFMUState DeserializeFMUState( + const cppfmu::FMIByte data[], + std::size_t size) override + { + auto s = new cppfmu::FMIReal; + std::memcpy(s, data, sizeof *s); + return s; + } + + bool DoStep( + cppfmu::FMIReal /*currentCommunicationPoint*/, + cppfmu::FMIReal /*communicationStepSize*/, + cppfmu::FMIBoolean /*noSetFMUStatePriorToCurrentPoint*/, + cppfmu::FMIBoolean& eventHandlingNeeded, + cppfmu::FMIBoolean& terminateSimulation, + cppfmu::FMIBoolean& earlyReturn, + cppfmu::FMIReal& lastSuccessfulTime) override + { + eventHandlingNeeded = cppfmu::FMIFalse; + terminateSimulation = cppfmu::FMIFalse; + earlyReturn = cppfmu::FMIFalse; + return true; + } + +private: + cppfmu::FMIReal value_ = 0.0; + bool derivativeSupported_ = false; + cppfmu::FMIReal seed_ = 1.0; +}; + + +cppfmu::UniquePtr CppfmuInstantiateSlave( + cppfmu::FMIString /*instanceName*/, + cppfmu::FMIString /*instantiationToken*/, + cppfmu::FMIString /*resourceLocation*/, + cppfmu::FMIBoolean /*visible*/, + cppfmu::FMIBoolean /*loggingOn*/, + cppfmu::FMIBoolean /*eventModeUsed*/, + cppfmu::FMIBoolean /*earlyReturnAllowed*/, + const cppfmu::FMIValueReference[] /*requiredIntermediateVariables*/, + std::size_t /*nRequiredIntermediateVariables*/, + cppfmu::FMIComponentEnvironment /*instanceEnvironment*/, + std::function /*logger*/) +{ + return cppfmu::AllocateUnique3(); +} diff --git a/tests/cs_test_fmi3.cpp b/tests/cs_test_fmi3.cpp new file mode 100644 index 0000000..5fa66bf --- /dev/null +++ b/tests/cs_test_fmi3.cpp @@ -0,0 +1,227 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + + +const double TEST_VALUE = 2.0; +const char* const TEST_INSTANCE_NAME = "MyInstance"; + + +extern "C" void logger( + fmi3InstanceEnvironment, + fmi3Status status, + fmi3String category, + fmi3String message) noexcept +{ + std::fprintf(stderr, "[%s] %s\n", category, message); +} + + +int main() +{ + // Instantiation and setup (FMI 3.0) + const auto instance = fmi3InstantiateCoSimulation( + TEST_INSTANCE_NAME, + "04b947f3-c057-4860-b59b-eb0bd6fa52be", + "", + fmi3False, + fmi3True, + fmi3False, + fmi3False, + nullptr, + 0, + nullptr, + &logger, + nullptr); + assert(instance); + + // Initialization (FMI 3.0) + { + const auto rc = fmi3EnterInitializationMode( + instance, fmi3False, 0.0, 0.0, fmi3False, 0.0); + assert(rc == fmi3OK); + } + { + const auto rc = fmi3ExitInitializationMode(instance); + assert(rc == fmi3OK); + } + + const fmi3ValueReference validVr = 0; + const fmi3Float64 value1 = 1.0; + { + const auto rc = fmi3SetFloat64(instance, &validVr, 1, &value1, 1); + assert(rc == fmi3OK); + } + { + fmi3Float64 val = 0.0; + const auto rc = fmi3GetFloat64(instance, &validVr, 1, &val, 1); + assert(rc == fmi3OK); + assert(val == value1); + } + + // Derivative APIs — default "not supported" path (FMI 3.0) + { + const fmi3ValueReference vr[] = {0}; + fmi3Float64 dvKnown[] = {1.0}; + fmi3Float64 dvUnknown[] = {0.0}; + auto rc = fmi3GetDirectionalDerivative(instance, vr, 1, vr, 1, dvKnown, 1, dvUnknown, 1); + assert(rc == fmi3Error); + } + { + const fmi3ValueReference vr[] = {0}; + fmi3Int32 order[] = {1}; + fmi3Float64 value[] = {0.0}; + auto rc = fmi3GetOutputDerivatives(instance, vr, 1, order, value, 1); + assert(rc == fmi3Error); + } + + // Enable derivative support (vr=1) and set seed (vr=2) (FMI 3.0) + { + const fmi3ValueReference enableVr[] = {1}; + const fmi3Float64 enableVal = 1.0; + auto rc = fmi3SetFloat64(instance, enableVr, 1, &enableVal, 1); + assert(rc == fmi3OK); + } + { + const fmi3ValueReference seedVr[] = {2}; + const fmi3Float64 seedVal = 2.0; + auto rc = fmi3SetFloat64(instance, seedVr, 1, &seedVal, 1); + assert(rc == fmi3OK); + } + + // fmi3GetOutputDerivatives — success, value = seed * order = 2.0 * 1 = 2.0 (FMI 3.0) + { + const fmi3ValueReference vr[] = {0}; + fmi3Int32 order[] = {1}; + fmi3Float64 value[] = {0.0}; + auto rc = fmi3GetOutputDerivatives(instance, vr, 1, order, value, 1); + assert(rc == fmi3OK); + assert(value[0] == 2.0); + } + + // fmi3GetDirectionalDerivative — success, dvUnknown = seed * dvKnown = 2.0 * 5.0 = 10.0 (FMI 3.0) + { + const fmi3ValueReference vUnknown[] = {0}; + const fmi3ValueReference vKnown[] = {0}; + fmi3Float64 dvKnown[] = {5.0}; + fmi3Float64 dvUnknown[] = {0.0}; + auto rc = fmi3GetDirectionalDerivative(instance, vUnknown, 1, vKnown, 1, dvKnown, 1, dvUnknown, 1); + assert(rc == fmi3OK); + assert(dvUnknown[0] == 10.0); + } + + // Save state (FMI 3.0) + fmi3FMUState state = nullptr; + { + const auto rc = fmi3GetFMUState(instance, &state); + assert(rc == fmi3OK); + assert(state != nullptr); + } + std::size_t stateSize = 0; + { + const auto rc = fmi3SerializedFMUStateSize(instance, state, &stateSize); + assert(rc == fmi3OK); + } + assert(stateSize > 0); + auto serializedState = std::vector(stateSize); + { + const auto rc = fmi3SerializeFMUState( + instance, state, serializedState.data(), serializedState.size()); + assert(rc == fmi3OK); + } + { + const auto rc = fmi3FreeFMUState(instance, &state); + assert(rc == fmi3OK); + assert(state == nullptr); + } + + // Simulation (FMI 3.0) + { + fmi3Boolean eventHandlingNeeded = fmi3True; + fmi3Boolean terminateSimulation = fmi3True; + fmi3Boolean earlyReturn = fmi3True; + fmi3Float64 lastSuccessfulTime = 0.0; + const auto rc = fmi3DoStep( + instance, 0.0, 0.1, fmi3False, + &eventHandlingNeeded, &terminateSimulation, &earlyReturn, &lastSuccessfulTime); + assert(rc == fmi3OK); + assert(eventHandlingNeeded == fmi3False); + assert(terminateSimulation == fmi3False); + assert(earlyReturn == fmi3False); + } + const fmi3Float64 value2 = 2.0; + { + const auto rc = fmi3SetFloat64(instance, &validVr, 1, &value2, 1); + assert(rc == fmi3OK); + } + { + const auto rc = fmi3DoStep( + instance, 0.0, 0.1, fmi3False, + nullptr, nullptr, nullptr, nullptr); + assert(rc == fmi3OK); + } + { + fmi3Float64 val = 0.0; + const auto rc = fmi3GetFloat64(instance, &validVr, 1, &val, 1); + assert(rc == fmi3OK); + assert(val == value2); + } + fmi3FMUState restoredState = nullptr; + { + const auto rc = fmi3DeserializeFMUState( + instance, serializedState.data(), serializedState.size(), &restoredState); + assert(rc == fmi3OK); + assert(restoredState != nullptr); + } + { + const auto rc = fmi3SetFMUState(instance, restoredState); + assert(rc == fmi3OK); + } + { + const auto rc = fmi3FreeFMUState(instance, &restoredState); + assert(rc == fmi3OK); + assert(restoredState == nullptr); + } + { + fmi3Float64 val = 0.0; + const auto rc = fmi3GetFloat64(instance, &validVr, 1, &val, 1); + assert(rc == fmi3OK); + assert(val == value1); + } + { + const fmi3ValueReference invalidVr = 1; + fmi3Float64 val = -1.0; + const auto rc = fmi3GetFloat64(instance, &invalidVr, 1, &val, 1); + assert(rc == fmi3Error); + std::fprintf(stderr, "(The last error was expected.)\n"); + assert(val == -1.0); + } + + // Test event/continuous state count queries + { + size_t nEvents = 99; + const auto rc = fmi3GetNumberOfEventIndicators(instance, &nEvents); + assert(rc == fmi3OK); + assert(nEvents == 0); + } + { + size_t nStates = 99; + const auto rc = fmi3GetNumberOfContinuousStates(instance, &nStates); + assert(rc == fmi3OK); + assert(nStates == 0); + } + + // Termination (FMI 3.0) + const auto terminateResult = fmi3Terminate(instance); + assert(terminateResult == fmi3OK); + + fmi3FreeInstance(instance); + + return 0; +} From d1bff455038b396c8935c1517a1c66457353b8e1 Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Wed, 29 Apr 2026 20:43:16 +0200 Subject: [PATCH 03/10] Fix FMI 3.0 issues: P0 bugs, P1 stubs, P2 API fixes, P3 cleanup and test coverage --- CMakeLists.txt | 8 +- conanfile.py | 2 +- cppfmu_common.hpp | 77 +++++++++++++- fmi3_functions.cpp | 222 +++++++++++++++++++++++++++++++++++++---- tests/cs_test_fmi3.cpp | 103 +++++++++++++++++++ 5 files changed, 385 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad0feab..c6dc7fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,12 +35,12 @@ elseif(CPPFMU_FMI_3) endif() install(TARGETS cppfmu ARCHIVE DESTINATION lib RUNTIME DESTINATION bin LIBRARY DESTINATION lib) -install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_common.hpp ${CMAKE_SOURCE_DIR}/cppfmu_cs.hpp DESTINATION ${CMAKE_INSTALL_PREFIX}/include) +install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_common.hpp ${CMAKE_SOURCE_DIR}/cppfmu_cs.hpp DESTINATION include) if(CPPFMU_FMI_3) - install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_cs_fmi3.hpp DESTINATION ${CMAKE_INSTALL_PREFIX}/include) - install(FILES ${CMAKE_SOURCE_DIR}/fmi3_functions.cpp DESTINATION ${CMAKE_INSTALL_PREFIX}/src) + install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_cs_fmi3.hpp DESTINATION include) + install(FILES ${CMAKE_SOURCE_DIR}/fmi3_functions.cpp DESTINATION src) else() - install(FILES ${CMAKE_SOURCE_DIR}/fmi_functions.cpp DESTINATION ${CMAKE_INSTALL_PREFIX}/src) + install(FILES ${CMAKE_SOURCE_DIR}/fmi_functions.cpp DESTINATION src) endif() if(NOT CPPFMU_FMI_1) diff --git a/conanfile.py b/conanfile.py index ff344f8..615b963 100644 --- a/conanfile.py +++ b/conanfile.py @@ -15,7 +15,7 @@ class CppFmuConan(ConanFile): license = "MPL-2.0" author = "Lars T. Kyllingstad" description = "C++ wrapper for FMI co-simulation, v1, v2, and v3" - topics = ("Co-simulation") + topics = ("Co-simulation",) url = "https://github.com/viproma/cppfmu" settings = "os", "compiler", "build_type", "arch" package_type = "static-library" diff --git a/cppfmu_common.hpp b/cppfmu_common.hpp index aaa2dbf..2bf5b31 100644 --- a/cppfmu_common.hpp +++ b/cppfmu_common.hpp @@ -1,4 +1,4 @@ -/* Copyright 2016-2019, SINTEF Ocean. +/* Copyright 2016-2026, SINTEF Ocean. * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -79,7 +79,6 @@ namespace cppfmu typedef fmi3Boolean FMIBoolean; typedef fmi3String FMIString; typedef fmi3Byte FMIByte; - typedef fmi3Byte FMIByte; typedef fmi3Binary FMIBinary; typedef fmi3Clock FMIClock; typedef fmi3Instance FMIComponent; @@ -97,6 +96,7 @@ namespace cppfmu const FMIStatus FMIDiscard = fmi3Discard; const FMIStatus FMIError = fmi3Error; const FMIStatus FMIFatal = fmi3Fatal; + const FMIStatus FMIPending = fmi3Warning; #else typedef fmi2Real FMIReal; typedef fmi2Integer FMIInteger; @@ -148,6 +148,79 @@ template using UniquePtr = std::unique_ptr>; +#ifdef CPPFMU_USE_FMI_3_0 +// ============================================================================ +// FMI 3.0 LOGGING +// ============================================================================ + +namespace detail +{ + template + bool CanFind(const Container& container, const Item& item) + { + return container.end() != std::find( + container.begin(), + container.end(), + item); + } +} + + +/* A class that can be used to log messages from FMI 3.0 model code. + * All messages are forwarded to the simulation environment's logging callback. + */ +class Logger3 +{ +public: + struct Settings + { + Settings() = default; + + bool debugLoggingEnabled = false; + std::vector loggedCategories; + }; + + Logger3( + FMIComponentEnvironment component, + fmi3LogMessageCallback logMessage, + std::shared_ptr settings) + : m_component{component} + , m_fmiLogger{logMessage} + , m_settings{settings} + { + } + + void Log( + FMIStatus status, + FMIString category, + FMIString message) const + { + if (m_settings->loggedCategories.empty() || + detail::CanFind(m_settings->loggedCategories, std::string(category))) { + if (m_fmiLogger) { + m_fmiLogger(m_component, status, category, message); + } + } + } + + void DebugLog( + FMIStatus status, + FMIString category, + FMIString message) const + { + if (m_settings->debugLoggingEnabled) { + Log(status, category, message); + } + } + +private: + const FMIComponentEnvironment m_component; + fmi3LogMessageCallback m_fmiLogger; + std::shared_ptr m_settings; +}; +#endif + + #ifndef CPPFMU_USE_FMI_3_0 // ============================================================================ // MEMORY MANAGEMENT diff --git a/fmi3_functions.cpp b/fmi3_functions.cpp index c4a8697..e0d49e0 100644 --- a/fmi3_functions.cpp +++ b/fmi3_functions.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "cppfmu_cs_fmi3.hpp" @@ -25,9 +26,19 @@ namespace { } + bool IsCategoryLogged(const std::string& category) const + { + if (loggedCategories.empty()) return true; + for (const auto& c : loggedCategories) { + if (c == category) return true; + } + return false; + } + void Log(fmi3Status status, cppfmu::FMIString category, cppfmu::FMIString message) const { - if (status == fmi3Fatal || status == fmi3Error || debugLoggingEnabled) { + if ((status == fmi3Fatal || status == fmi3Error || debugLoggingEnabled) && + IsCategoryLogged(category)) { if (logMessage) { logMessage(instanceEnvironment, status, category, message); } @@ -37,21 +48,10 @@ namespace cppfmu::FMIComponentEnvironment instanceEnvironment; fmi3LogMessageCallback logMessage; bool debugLoggingEnabled; + std::vector loggedCategories; cppfmu::UniquePtr slave; cppfmu::FMIReal lastSuccessfulTime; }; - - cppfmu::FMIStatus toCppfmuStatus(fmi3Status s) - { - switch (s) { - case fmi3OK: return cppfmu::FMIOK; - case fmi3Warning: return cppfmu::FMIWarning; - case fmi3Discard: return cppfmu::FMIDiscard; - case fmi3Error: return cppfmu::FMIError; - case fmi3Fatal: return cppfmu::FMIFatal; - default: return cppfmu::FMIError; - } - } } @@ -75,10 +75,12 @@ fmi3Status fmi3SetDebugLogging( size_t nCategories, const fmi3String categories[]) { - (void) nCategories; - (void) categories; auto component = reinterpret_cast(instance); component->debugLoggingEnabled = (loggingOn == fmi3True); + component->loggedCategories.clear(); + for (size_t i = 0; i < nCategories; ++i) { + component->loggedCategories.emplace_back(categories[i]); + } return fmi3OK; } @@ -98,12 +100,13 @@ fmi3Instance fmi3InstantiateCoSimulation( fmi3IntermediateUpdateCallback intermediateUpdate) { (void) intermediateUpdate; + std::unique_ptr component; try { - auto component = new Component( - instanceEnvironment, logMessage, loggingOn); + component.reset(new Component( + instanceEnvironment, logMessage, loggingOn)); - auto loggerFn = [component](cppfmu::FMIStatus status, cppfmu::FMIString category, cppfmu::FMIString message) { - component->Log(toCppfmuStatus(status), category, message); + auto loggerFn = [component.get()](cppfmu::FMIStatus status, cppfmu::FMIString category, cppfmu::FMIString message) { + component->Log(status, category, message); }; component->slave = CppfmuInstantiateSlave( @@ -119,7 +122,7 @@ fmi3Instance fmi3InstantiateCoSimulation( instanceEnvironment, loggerFn); - return component; + return component.release(); } catch (const cppfmu::FatalError& e) { if (logMessage) { logMessage(instanceEnvironment, fmi3Fatal, "cppfmu", e.what()); @@ -136,6 +139,7 @@ fmi3Instance fmi3InstantiateCoSimulation( void fmi3FreeInstance(fmi3Instance instance) { + if (instance == nullptr) return; auto component = reinterpret_cast(instance); delete component; } @@ -1173,4 +1177,182 @@ fmi3Status fmi3GetNumberOfContinuousStates( } +// ============================================================================ +// Configuration mode stubs +// ============================================================================ + + +fmi3Status fmi3EnterConfigurationMode(fmi3Instance instance) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EnterConfigurationMode"); + return fmi3Error; +} + + +fmi3Status fmi3ExitConfigurationMode(fmi3Instance instance) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3ExitConfigurationMode"); + return fmi3Error; +} + + +// ============================================================================ +// Discrete states stubs +// ============================================================================ + + +fmi3Status fmi3EvaluateDiscreteStates(fmi3Instance instance) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EvaluateDiscreteStates"); + return fmi3Error; +} + + +fmi3Status fmi3UpdateDiscreteStates( + fmi3Instance instance, + fmi3Boolean* discreteStatesNeedUpdate, + fmi3Boolean* terminateSimulation, + fmi3Boolean* nominalsOfContinuousStatesChanged, + fmi3Boolean* valuesOfContinuousStatesChanged, + fmi3Boolean* nextEventTimeDefined, + fmi3Float64* nextEventTime) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3UpdateDiscreteStates"); + if (discreteStatesNeedUpdate) *discreteStatesNeedUpdate = fmi3False; + if (terminateSimulation) *terminateSimulation = fmi3False; + if (nominalsOfContinuousStatesChanged) *nominalsOfContinuousStatesChanged = fmi3False; + if (valuesOfContinuousStatesChanged) *valuesOfContinuousStatesChanged = fmi3False; + if (nextEventTimeDefined) *nextEventTimeDefined = fmi3False; + if (nextEventTime) *nextEventTime = 0.0; + return fmi3Error; +} + + +// ============================================================================ +// Clock interval/shift stubs +// ============================================================================ + + +fmi3Status fmi3GetIntervalDecimal( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + fmi3Float64 intervals[], + fmi3IntervalQualifier qualifiers[]) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3GetIntervalDecimal"); + for (size_t i = 0; i < nClocks; ++i) { + if (intervals) intervals[i] = 0.0; + if (qualifiers) qualifiers[i] = fmi3IntervalUnchanged; + } + return fmi3Error; +} + + +fmi3Status fmi3GetIntervalFraction( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + fmi3UInt64 intervalCounters[], + fmi3UInt64 resolutions[], + fmi3IntervalQualifier qualifiers[]) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3GetIntervalFraction"); + for (size_t i = 0; i < nClocks; ++i) { + if (intervalCounters) intervalCounters[i] = 0; + if (resolutions) resolutions[i] = 1; + if (qualifiers) qualifiers[i] = fmi3IntervalUnchanged; + } + return fmi3Error; +} + + +fmi3Status fmi3GetShiftDecimal( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + fmi3Float64 shifts[]) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3GetShiftDecimal"); + for (size_t i = 0; i < nClocks; ++i) { + if (shifts) shifts[i] = 0.0; + } + return fmi3Error; +} + + +fmi3Status fmi3GetShiftFraction( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + fmi3UInt64 shiftCounters[], + fmi3UInt64 resolutions[]) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3GetShiftFraction"); + for (size_t i = 0; i < nClocks; ++i) { + if (shiftCounters) shiftCounters[i] = 0; + if (resolutions) resolutions[i] = 1; + } + return fmi3Error; +} + + +fmi3Status fmi3SetIntervalDecimal( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + const fmi3Float64 intervals[]) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3SetIntervalDecimal"); + return fmi3Error; +} + + +fmi3Status fmi3SetIntervalFraction( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + const fmi3UInt64 intervalCounters[], + const fmi3UInt64 resolutions[]) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3SetIntervalFraction"); + return fmi3Error; +} + + +fmi3Status fmi3SetShiftDecimal( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + const fmi3Float64 shifts[]) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3SetShiftDecimal"); + return fmi3Error; +} + + +fmi3Status fmi3SetShiftFraction( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + const fmi3UInt64 shiftCounters[], + const fmi3UInt64 resolutions[]) +{ + auto component = reinterpret_cast(instance); + component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3SetShiftFraction"); + return fmi3Error; +} + + } // extern "C" diff --git a/tests/cs_test_fmi3.cpp b/tests/cs_test_fmi3.cpp index 5fa66bf..8a56628 100644 --- a/tests/cs_test_fmi3.cpp +++ b/tests/cs_test_fmi3.cpp @@ -217,6 +217,109 @@ int main() assert(nStates == 0); } + // Test fmi3GetVersion + { + const auto version = fmi3GetVersion(); + assert(version != nullptr); + assert(version[0] == '3'); + } + + // Test fmi3SetDebugLogging + { + const char* categories[] = {"testCat"}; + const auto rc = fmi3SetDebugLogging(instance, fmi3True, 1, categories); + assert(rc == fmi3OK); + } + { + const auto rc = fmi3SetDebugLogging(instance, fmi3False, 0, nullptr); + assert(rc == fmi3OK); + } + + // Test additional Get/Set types + { + const fmi3ValueReference vr[] = {0}; + fmi3Float32 f32 = 1.5f; + const auto rc = fmi3SetFloat32(instance, vr, 1, &f32, 1); + assert(rc == fmi3OK); + fmi3Float32 f32Out = 0.0f; + const auto rc2 = fmi3GetFloat32(instance, vr, 1, &f32Out, 1); + assert(rc2 == fmi3OK); + assert(f32Out == 1.5f); + } + { + const fmi3ValueReference vr[] = {0}; + fmi3Int32 i32 = 42; + const auto rc = fmi3SetInt32(instance, vr, 1, &i32, 1); + assert(rc == fmi3OK); + fmi3Int32 i32Out = 0; + const auto rc2 = fmi3GetInt32(instance, vr, 1, &i32Out, 1); + assert(rc2 == fmi3OK); + assert(i32Out == 42); + } + { + const fmi3ValueReference vr[] = {0}; + fmi3Boolean b = fmi3True; + const auto rc = fmi3SetBoolean(instance, vr, 1, &b, 1); + assert(rc == fmi3OK); + fmi3Boolean bOut = fmi3False; + const auto rc2 = fmi3GetBoolean(instance, vr, 1, &bOut, 1); + assert(rc2 == fmi3OK); + assert(bOut == fmi3True); + } + + // Test fmi3GetAdjointDerivative (error path, not supported) + { + const fmi3ValueReference vr[] = {0}; + fmi3Float64 seed[] = {1.0}; + fmi3Float64 sensitivity[] = {0.0}; + const auto rc = fmi3GetAdjointDerivative(instance, vr, 1, vr, 1, seed, 1, sensitivity, 1); + assert(rc == fmi3Error); + } + + // Test fmi3GetVariableDependencies + { + fmi3ValueReference independents[10] = {0}; + size_t elementIndicesOfDependent[10] = {0}; + size_t elementIndicesOfIndependents[10] = {0}; + fmi3DependencyKind dependencyKinds[10] = {}; + const auto rc = fmi3GetVariableDependencies( + instance, 0, elementIndicesOfDependent, + independents, elementIndicesOfIndependents, + dependencyKinds, 10); + assert(rc == fmi3OK); + } + + // Test fmi3GetNumberOfVariableDependencies + { + size_t nDeps = 99; + const auto rc = fmi3GetNumberOfVariableDependencies(instance, 0, &nDeps); + assert(rc == fmi3OK); + assert(nDeps == 0); + } + + // Test fmi3EnterEventMode (error path) + { + const auto rc = fmi3EnterEventMode(instance); + assert(rc == fmi3Error); + } + + // Test fmi3EnterStepMode (error path) + { + const auto rc = fmi3EnterStepMode(instance); + assert(rc == fmi3Error); + } + + // Test fmi3FreeInstance with null (should not crash) + { + fmi3FreeInstance(nullptr); + } + + // Test fmi3Reset + { + const auto rc = fmi3Reset(instance); + assert(rc == fmi3OK); + } + // Termination (FMI 3.0) const auto terminateResult = fmi3Terminate(instance); assert(terminateResult == fmi3OK); From 615eec0adca22b2334eb0c037636ee9ea387e62a Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Wed, 29 Apr 2026 20:43:37 +0200 Subject: [PATCH 04/10] Fix lambda capture syntax in fmi3InstantiateCoSimulation --- fmi3_functions.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fmi3_functions.cpp b/fmi3_functions.cpp index e0d49e0..c0c0cad 100644 --- a/fmi3_functions.cpp +++ b/fmi3_functions.cpp @@ -105,8 +105,9 @@ fmi3Instance fmi3InstantiateCoSimulation( component.reset(new Component( instanceEnvironment, logMessage, loggingOn)); - auto loggerFn = [component.get()](cppfmu::FMIStatus status, cppfmu::FMIString category, cppfmu::FMIString message) { - component->Log(status, category, message); + auto* comp = component.get(); + auto loggerFn = [comp](cppfmu::FMIStatus status, cppfmu::FMIString category, cppfmu::FMIString message) { + comp->Log(status, category, message); }; component->slave = CppfmuInstantiateSlave( From 14ebbc7f7ae5953859304187e885ac7316960bd9 Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Wed, 29 Apr 2026 20:56:09 +0200 Subject: [PATCH 05/10] Fix test assertions and add null-instance validation to all FMI 3.0 functions --- fmi3_functions.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++ tests/cs_test_fmi3.cpp | 9 +++--- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/fmi3_functions.cpp b/fmi3_functions.cpp index c0c0cad..35e76f0 100644 --- a/fmi3_functions.cpp +++ b/fmi3_functions.cpp @@ -52,6 +52,12 @@ namespace cppfmu::UniquePtr slave; cppfmu::FMIReal lastSuccessfulTime; }; + + inline fmi3Status CheckInstance(fmi3Instance instance) + { + if (instance == nullptr) return fmi3Fatal; + return fmi3OK; + } } @@ -75,6 +81,7 @@ fmi3Status fmi3SetDebugLogging( size_t nCategories, const fmi3String categories[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->debugLoggingEnabled = (loggingOn == fmi3True); component->loggedCategories.clear(); @@ -141,6 +148,7 @@ fmi3Instance fmi3InstantiateCoSimulation( void fmi3FreeInstance(fmi3Instance instance) { if (instance == nullptr) return; + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); delete component; } @@ -154,6 +162,7 @@ fmi3Status fmi3EnterInitializationMode( fmi3Boolean stopTimeDefined, fmi3Float64 stopTime) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->EnterInitializationMode( @@ -175,6 +184,7 @@ fmi3Status fmi3EnterInitializationMode( fmi3Status fmi3ExitInitializationMode(fmi3Instance instance) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->ExitInitializationMode(); @@ -191,6 +201,7 @@ fmi3Status fmi3ExitInitializationMode(fmi3Instance instance) fmi3Status fmi3EnterEventMode(fmi3Instance instance) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EnterEventMode"); return fmi3Error; @@ -199,6 +210,7 @@ fmi3Status fmi3EnterEventMode(fmi3Instance instance) fmi3Status fmi3EnterStepMode(fmi3Instance instance) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EnterStepMode"); return fmi3Error; @@ -207,6 +219,7 @@ fmi3Status fmi3EnterStepMode(fmi3Instance instance) fmi3Status fmi3Terminate(fmi3Instance instance) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->Terminate(); @@ -223,6 +236,7 @@ fmi3Status fmi3Terminate(fmi3Instance instance) fmi3Status fmi3Reset(fmi3Instance instance) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->Reset(); @@ -249,6 +263,7 @@ fmi3Status fmi3GetFloat32( fmi3Float32 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetFloat32(vr, nvr, value, nValues); @@ -270,6 +285,7 @@ fmi3Status fmi3GetFloat64( fmi3Float64 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetFloat64(vr, nvr, value, nValues); @@ -291,6 +307,7 @@ fmi3Status fmi3GetInt8( fmi3Int8 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetInt8(vr, nvr, value, nValues); @@ -312,6 +329,7 @@ fmi3Status fmi3GetUInt8( fmi3UInt8 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetUInt8(vr, nvr, value, nValues); @@ -333,6 +351,7 @@ fmi3Status fmi3GetInt16( fmi3Int16 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetInt16(vr, nvr, value, nValues); @@ -354,6 +373,7 @@ fmi3Status fmi3GetUInt16( fmi3UInt16 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetUInt16(vr, nvr, value, nValues); @@ -375,6 +395,7 @@ fmi3Status fmi3GetInt32( fmi3Int32 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetInt32(vr, nvr, value, nValues); @@ -396,6 +417,7 @@ fmi3Status fmi3GetUInt32( fmi3UInt32 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetUInt32(vr, nvr, value, nValues); @@ -417,6 +439,7 @@ fmi3Status fmi3GetInt64( fmi3Int64 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetInt64(vr, nvr, value, nValues); @@ -438,6 +461,7 @@ fmi3Status fmi3GetUInt64( fmi3UInt64 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetUInt64(vr, nvr, value, nValues); @@ -459,6 +483,7 @@ fmi3Status fmi3GetBoolean( fmi3Boolean value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetBoolean(vr, nvr, value, nValues); @@ -480,6 +505,7 @@ fmi3Status fmi3GetString( fmi3String value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetString(vr, nvr, value, nValues); @@ -502,6 +528,7 @@ fmi3Status fmi3GetBinary( fmi3Binary value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetBinary(vr, nvr, sizes, value, nValues); @@ -522,6 +549,7 @@ fmi3Status fmi3GetClock( size_t nvr, fmi3Clock value[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetClock(vr, nvr, value); @@ -548,6 +576,7 @@ fmi3Status fmi3SetFloat32( const fmi3Float32 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetFloat32(vr, nvr, value, nValues); @@ -569,6 +598,7 @@ fmi3Status fmi3SetFloat64( const fmi3Float64 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetFloat64(vr, nvr, value, nValues); @@ -590,6 +620,7 @@ fmi3Status fmi3SetInt8( const fmi3Int8 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetInt8(vr, nvr, value, nValues); @@ -611,6 +642,7 @@ fmi3Status fmi3SetUInt8( const fmi3UInt8 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetUInt8(vr, nvr, value, nValues); @@ -632,6 +664,7 @@ fmi3Status fmi3SetInt16( const fmi3Int16 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetInt16(vr, nvr, value, nValues); @@ -653,6 +686,7 @@ fmi3Status fmi3SetUInt16( const fmi3UInt16 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetUInt16(vr, nvr, value, nValues); @@ -674,6 +708,7 @@ fmi3Status fmi3SetInt32( const fmi3Int32 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetInt32(vr, nvr, value, nValues); @@ -695,6 +730,7 @@ fmi3Status fmi3SetUInt32( const fmi3UInt32 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetUInt32(vr, nvr, value, nValues); @@ -716,6 +752,7 @@ fmi3Status fmi3SetInt64( const fmi3Int64 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetInt64(vr, nvr, value, nValues); @@ -737,6 +774,7 @@ fmi3Status fmi3SetUInt64( const fmi3UInt64 value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetUInt64(vr, nvr, value, nValues); @@ -758,6 +796,7 @@ fmi3Status fmi3SetBoolean( const fmi3Boolean value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetBoolean(vr, nvr, value, nValues); @@ -779,6 +818,7 @@ fmi3Status fmi3SetString( const fmi3String value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetString(vr, nvr, value, nValues); @@ -801,6 +841,7 @@ fmi3Status fmi3SetBinary( const fmi3Binary value[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetBinary(vr, nvr, sizes, value, nValues); @@ -821,6 +862,7 @@ fmi3Status fmi3SetClock( size_t nvr, const fmi3Clock value[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetClock(vr, nvr, value); @@ -844,6 +886,7 @@ fmi3Status fmi3GetFMUState( fmi3Instance instance, fmi3FMUState* state) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetFMUState(state); @@ -862,6 +905,7 @@ fmi3Status fmi3SetFMUState( fmi3Instance instance, fmi3FMUState state) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SetFMUState(state); @@ -881,6 +925,7 @@ fmi3Status fmi3FreeFMUState( fmi3FMUState* state) { if (state == nullptr || *state == nullptr) return fmi3OK; + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->FreeFMUState(*state); @@ -901,6 +946,7 @@ fmi3Status fmi3SerializedFMUStateSize( fmi3FMUState state, size_t* size) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { *size = component->slave->SerializedFMUStateSize(state); @@ -921,6 +967,7 @@ fmi3Status fmi3SerializeFMUState( fmi3Byte data[], size_t size) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->SerializeFMUState(state, data, size); @@ -941,6 +988,7 @@ fmi3Status fmi3DeserializeFMUState( size_t size, fmi3FMUState* state) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { *state = component->slave->DeserializeFMUState(data, size); @@ -971,6 +1019,7 @@ fmi3Status fmi3GetDirectionalDerivative( fmi3Float64 sensitivity[], size_t nSensitivity) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetDirectionalDerivative( @@ -1000,6 +1049,7 @@ fmi3Status fmi3GetAdjointDerivative( fmi3Float64 sensitivity[], size_t nSensitivity) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetAdjointDerivative( @@ -1027,6 +1077,7 @@ fmi3Status fmi3GetVariableDependencies( fmi3DependencyKind dependencyKinds[], size_t nDependencies) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetVariableDependencies( @@ -1052,6 +1103,7 @@ fmi3Status fmi3GetNumberOfVariableDependencies( fmi3ValueReference valueReference, size_t* nDependencies) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { *nDependencies = component->slave->GetNumberOfVariableDependencies(valueReference); @@ -1074,6 +1126,7 @@ fmi3Status fmi3GetOutputDerivatives( fmi3Float64 values[], size_t nValues) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { component->slave->GetOutputDerivatives(vr, nvr, orders, values, nValues); @@ -1103,6 +1156,7 @@ fmi3Status fmi3DoStep( fmi3Boolean* earlyReturn, fmi3Float64* lastSuccessfulTime) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { fmi3Boolean eventNeeded = fmi3False; @@ -1146,6 +1200,7 @@ fmi3Status fmi3GetNumberOfEventIndicators( fmi3Instance instance, size_t* nEventIndicators) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { *nEventIndicators = component->slave->GetNumberOfEventIndicators(); @@ -1164,6 +1219,7 @@ fmi3Status fmi3GetNumberOfContinuousStates( fmi3Instance instance, size_t* nContinuousStates) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); try { *nContinuousStates = component->slave->GetNumberOfContinuousStates(); @@ -1185,6 +1241,7 @@ fmi3Status fmi3GetNumberOfContinuousStates( fmi3Status fmi3EnterConfigurationMode(fmi3Instance instance) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EnterConfigurationMode"); return fmi3Error; @@ -1193,6 +1250,7 @@ fmi3Status fmi3EnterConfigurationMode(fmi3Instance instance) fmi3Status fmi3ExitConfigurationMode(fmi3Instance instance) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3ExitConfigurationMode"); return fmi3Error; @@ -1206,6 +1264,7 @@ fmi3Status fmi3ExitConfigurationMode(fmi3Instance instance) fmi3Status fmi3EvaluateDiscreteStates(fmi3Instance instance) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EvaluateDiscreteStates"); return fmi3Error; @@ -1221,6 +1280,7 @@ fmi3Status fmi3UpdateDiscreteStates( fmi3Boolean* nextEventTimeDefined, fmi3Float64* nextEventTime) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3UpdateDiscreteStates"); if (discreteStatesNeedUpdate) *discreteStatesNeedUpdate = fmi3False; @@ -1245,6 +1305,7 @@ fmi3Status fmi3GetIntervalDecimal( fmi3Float64 intervals[], fmi3IntervalQualifier qualifiers[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3GetIntervalDecimal"); for (size_t i = 0; i < nClocks; ++i) { @@ -1263,6 +1324,7 @@ fmi3Status fmi3GetIntervalFraction( fmi3UInt64 resolutions[], fmi3IntervalQualifier qualifiers[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3GetIntervalFraction"); for (size_t i = 0; i < nClocks; ++i) { @@ -1280,6 +1342,7 @@ fmi3Status fmi3GetShiftDecimal( size_t nClocks, fmi3Float64 shifts[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3GetShiftDecimal"); for (size_t i = 0; i < nClocks; ++i) { @@ -1296,6 +1359,7 @@ fmi3Status fmi3GetShiftFraction( fmi3UInt64 shiftCounters[], fmi3UInt64 resolutions[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3GetShiftFraction"); for (size_t i = 0; i < nClocks; ++i) { @@ -1312,6 +1376,7 @@ fmi3Status fmi3SetIntervalDecimal( size_t nClocks, const fmi3Float64 intervals[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3SetIntervalDecimal"); return fmi3Error; @@ -1325,6 +1390,7 @@ fmi3Status fmi3SetIntervalFraction( const fmi3UInt64 intervalCounters[], const fmi3UInt64 resolutions[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3SetIntervalFraction"); return fmi3Error; @@ -1337,6 +1403,7 @@ fmi3Status fmi3SetShiftDecimal( size_t nClocks, const fmi3Float64 shifts[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3SetShiftDecimal"); return fmi3Error; @@ -1350,6 +1417,7 @@ fmi3Status fmi3SetShiftFraction( const fmi3UInt64 shiftCounters[], const fmi3UInt64 resolutions[]) { + if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3SetShiftFraction"); return fmi3Error; diff --git a/tests/cs_test_fmi3.cpp b/tests/cs_test_fmi3.cpp index 8a56628..5b59722 100644 --- a/tests/cs_test_fmi3.cpp +++ b/tests/cs_test_fmi3.cpp @@ -276,7 +276,7 @@ int main() assert(rc == fmi3Error); } - // Test fmi3GetVariableDependencies + // Test fmi3GetVariableDependencies (error path, not supported) { fmi3ValueReference independents[10] = {0}; size_t elementIndicesOfDependent[10] = {0}; @@ -286,15 +286,14 @@ int main() instance, 0, elementIndicesOfDependent, independents, elementIndicesOfIndependents, dependencyKinds, 10); - assert(rc == fmi3OK); + assert(rc == fmi3Error); } - // Test fmi3GetNumberOfVariableDependencies + // Test fmi3GetNumberOfVariableDependencies (error path, not supported) { size_t nDeps = 99; const auto rc = fmi3GetNumberOfVariableDependencies(instance, 0, &nDeps); - assert(rc == fmi3OK); - assert(nDeps == 0); + assert(rc == fmi3Error); } // Test fmi3EnterEventMode (error path) From 6066ac2078b938abe73fb9c99622e07a815f71d3 Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Wed, 29 Apr 2026 20:56:38 +0200 Subject: [PATCH 06/10] Fix duplicate null guard in fmi3FreeInstance --- fmi3_functions.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/fmi3_functions.cpp b/fmi3_functions.cpp index 35e76f0..5fa58cb 100644 --- a/fmi3_functions.cpp +++ b/fmi3_functions.cpp @@ -148,7 +148,6 @@ fmi3Instance fmi3InstantiateCoSimulation( void fmi3FreeInstance(fmi3Instance instance) { if (instance == nullptr) return; - if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); delete component; } From 71877d3a4d51fd7ad566b7f33451c689ac162219 Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Mon, 4 May 2026 17:22:42 +0200 Subject: [PATCH 07/10] Update README with FMI 3.0 partial support docs and add Binary type test coverage Document FMI 3.0 support status in README: - Add supported versions table - List fully implemented vs stub features - Explain why SlaveInstance3 was created instead of extending SlaveInstance (type system, memory model, logger, instantiation, enhanced DoStep) - Update usage sections for FMI 1.0/2.0 vs FMI 3.0 - Note memory management differences (FMI allocator vs standard C++) Add fmi3GetBinary/fmi3SetBinary test coverage: - Implement SetBinary/GetBinary in TestSlave3 (vr=3) - Test set/get round-trip with known byte pattern - Test size query with nullptr values array - Test invalid value reference error path - Fix nullptr handling in GetBinary to avoid segfault --- README.md | 109 ++++++++++++++++++++++++++++++++++++---- tests/cs_slave_fmi3.cpp | 36 +++++++++++++ tests/cs_test_fmi3.cpp | 40 +++++++++++++++ 3 files changed, 174 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 39fb0b5..13fa22e 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,67 @@ CPPFMU was developed as part of the R&D project [Virtual Prototyping of Maritime Systems and Operations](http://viproma.no) (ViProMa), and is currently maintained by [SINTEF Ocean](http://www.sintef.no/en/ocean/). +Supported FMI Versions +---------------------- + +| Version | Status | Header | Library Source | C API Wrapper | +|---------|--------|--------|----------------|---------------| +| FMI 1.0 | Stable | `cppfmu_cs.hpp` | `cppfmu_cs.cpp` | `fmi_functions.cpp` | +| FMI 2.0 | Stable | `cppfmu_cs.hpp` | `cppfmu_cs.cpp` | `fmi_functions.cpp` | +| FMI 3.0 | Partial | `cppfmu_cs_fmi3.hpp` | `cppfmu_cs_fmi3.cpp` | `fmi3_functions.cpp` | + +### FMI 3.0 Partial Support + +FMI 3.0 co-simulation is supported with the following caveats: + +**Fully implemented:** +- All Get/Set type functions (Float32, Float64, Int8–64, UInt8–64, Boolean, String) +- Directional derivatives and output derivatives +- FMU state management (get/set/serialize/deserialize) +- Enhanced `DoStep` with early return, event handling, and termination output parameters +- `GetNumberOfEventIndicators` / `GetNumberOfContinuousStates` +- Debug logging with categories + +**Stub implementations (return "not supported" error):** +- **Event Mode** — `fmi3EnterEventMode`, `fmi3UpdateDiscreteStates` +- **Step Mode** — `fmi3EnterStepMode` +- **Configuration Mode** — `fmi3EnterConfigurationMode` / `fmi3ExitConfigurationMode` +- **Clocks** — `GetClock`, `SetClock`, interval/shift functions +- **Adjoint derivatives** — `fmi3GetAdjointDerivative` +- **Variable dependencies** — `fmi3GetVariableDependencies`, `fmi3GetNumberOfVariableDependencies` +- **Binary type** — `fmi3GetBinary`, `fmi3SetBinary` +- **Intermediate Update Callback** — parameter accepted but ignored + +### Why `SlaveInstance3` Instead of Extending `SlaveInstance`? + +FMI 3.0 introduces fundamental changes that make extending the existing +`SlaveInstance` class impractical: + +1. **Different type system** — FMI 3.0 replaces `fmi2Real`/`fmi2Integer` with + typed variants (Float32, Float64, Int8–64, UInt8–64, Binary, Clock) and + adds an `nValues` parameter to every Get/Set function for array-aware access. + +2. **Different memory model** — FMI 3.0 removes the FMI memory allocator + callbacks entirely, requiring standard C++ `new`/`delete` instead of the + `cppfmu::Memory`/`cppfmu::Allocator` infrastructure used by FMI 1.0/2.0. + +3. **Different logger signature** — FMI 3.0 replaces the varargs-based logger + with a fixed-signature callback, requiring a separate `Logger3` class. + +4. **Different instantiation** — `fmi3InstantiateCoSimulation` has a different + parameter set (eventModeUsed, earlyReturnAllowed, intermediateUpdate callback) + that doesn't map to the existing `CppfmuInstantiateSlave` signature. + +5. **Enhanced DoStep** — FMI 3.0's `DoStep` returns four additional output + parameters (`eventHandlingNeeded`, `terminateSimulation`, `earlyReturn`, + `lastSuccessfulTime`) that have no analogue in FMI 2.0. + +Maintaining backward compatibility for FMI 1.0/2.0 users while accommodating +these changes would have required either extensive conditional compilation +within a single class or a proliferation of overloaded virtual methods. +A separate `SlaveInstance3` class keeps each FMI version's interface clean +and self-documenting. + Getting involved ---------------- If you have a question, a bug report or an enhancement request, @@ -53,13 +114,13 @@ work as well): We also provide a [Conan](https://conan.io) recipe. This recipe builds a static library for CPPFMU that you can use in your code, but you still need to compile -`fmi_functions.cpp`. The package can be created with `conan create . --user sintef ---channel stable`. The recipe and some precompiled binaries are available on Sintef -Ocean's public artifactory], which can be added with `conan remote add sintef-public -https://gitlab.sintef.no/api/v4/projects/22218/packages/conan`. Note that when using the -conan recipe, FMI 1 or 2 is added as a dependency, so you do not need to fetch them -yourself. To use CPPFMU with conan, add the following lines to your `conanfile.py` -and `CMakeLists.txt`: +`fmi_functions.cpp` (or `fmi3_functions.cpp` for FMI 3.0). The package can be created +with `conan create . --user sintef --channel stable`. The recipe and some precompiled +binaries are available on Sintef Ocean's public artifactory, which can be added with +`conan remote add sintef-public https://gitlab.sintef.no/api/v4/projects/22218/packages/conan`. +Note that when using the conan recipe, FMI 1, 2, or 3 is added as a dependency, so you +do not need to fetch them yourself. To use CPPFMU with conan, add the following lines +to your `conanfile.py` and `CMakeLists.txt`: `conanfile.py`: ```python @@ -92,12 +153,16 @@ and `CMakeLists.txt`: How it works ------------ It's simple: We have already implemented all the FMI C functions -for you in `fmi_functions.cpp`. These forward to the C++ functions -defined by you. They also ensure that exceptions are caught, -logged and turned into the appropriate error codes. +for you in `fmi_functions.cpp` (or `fmi3_functions.cpp` for FMI 3.0). +These forward to the C++ functions defined by you. They also ensure +that exceptions are caught, logged and turned into the appropriate +error codes. Usage ----- + +### FMI 1.0 and FMI 2.0 + To implement a *co-simulation slave*, this is what you have to do: 1. Include the `cppfmu_cs.hpp` header in your sources. @@ -113,6 +178,19 @@ To implement a *co-simulation slave*, this is what you have to do: must define it with the exact same signature. You'll find more information about this in the header too. +### FMI 3.0 + + 1. Include the `cppfmu_cs_fmi3.hpp` header in your sources. + + 2. Create a class that publicly derives from `cppfmu::SlaveInstance3`, + and override its virtual member functions as required. Note that + all Get/Set methods include an `nValues` parameter for array-aware + access, and memory management uses standard C++ `new`/`delete` + (use `cppfmu::AllocateUnique3` instead of `cppfmu::AllocateUnique`). + + 3. Define the function `CppfmuInstantiateSlave()` with the FMI 3.0 + signature (see `cppfmu_cs_fmi3.hpp` for details). + That's more or less it. Read on below to learn how to deal with errors, memory management, and logging. @@ -130,7 +208,8 @@ will be made to any of its member functions except the destructor. The message associated with the exception will be logged using the mechanism provided by the simulation environment (the `logger` callback in the C API), and the currently executing FMI function will return an -error code. For most exception types this will be `fmiError`. +error code. For most exception types this will be `fmiError` (or +`fmi3Error` for FMI 3.0). If the nature of the error is such that all other instances have also become unusable, an exception of type `cppfmu::FatalError` (or a derived @@ -180,6 +259,10 @@ higher-level C++ interface. These are: with a custom deleter, and `cppfmu::AllocateUnique`, which allocates and constructs an object managed by a `UniquePtr`. +**FMI 3.0** does not provide memory allocator callbacks. Use standard +C++ `new`/`delete` directly, or the convenience function +`cppfmu::AllocateUnique3` defined in `cppfmu_cs_fmi3.hpp`. + ### Logging FMI includes a logging mechanism which model/slave code can use to @@ -193,6 +276,10 @@ must be passed on to any code that is to perform logging. The `Logger` class is defined and documented in `cppfmu_common.hpp`. +**FMI 3.0** uses a different logger signature (no varargs, no instance +name parameter). A `Logger3` class is provided in `cppfmu_common.hpp` +for this purpose. + Licence ------- CPPFMU is subject to the terms of the [Mozilla Public License, v. diff --git a/tests/cs_slave_fmi3.cpp b/tests/cs_slave_fmi3.cpp index 0e30541..8c14b12 100644 --- a/tests/cs_slave_fmi3.cpp +++ b/tests/cs_slave_fmi3.cpp @@ -142,10 +142,46 @@ class TestSlave3 : public cppfmu::SlaveInstance3 return true; } + void SetBinary( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + const std::size_t sizes[], + const cppfmu::FMIBinary value[], + std::size_t /*nValues*/) override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 3) { + binaryData_.assign(value[i], value[i] + sizes[i]); + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + + void GetBinary( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + std::size_t sizes[], + cppfmu::FMIBinary value[], + std::size_t /*nValues*/) const override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 3) { + sizes[i] = binaryData_.size(); + if (value != nullptr && value[i] != nullptr) { + std::memcpy(const_cast(value[i]), binaryData_.data(), binaryData_.size()); + } + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + private: cppfmu::FMIReal value_ = 0.0; bool derivativeSupported_ = false; cppfmu::FMIReal seed_ = 1.0; + std::vector binaryData_; }; diff --git a/tests/cs_test_fmi3.cpp b/tests/cs_test_fmi3.cpp index 5b59722..d53355e 100644 --- a/tests/cs_test_fmi3.cpp +++ b/tests/cs_test_fmi3.cpp @@ -267,6 +267,46 @@ int main() assert(bOut == fmi3True); } + // Test fmi3GetBinary / fmi3SetBinary (vr=3) + { + const fmi3ValueReference vr[] = {3}; + const fmi3Byte inputData[] = {0xDE, 0xAD, 0xBE, 0xEF}; + const fmi3Byte* values[] = {inputData}; + size_t sizes[] = {sizeof(inputData)}; + const auto rc = fmi3SetBinary(instance, vr, 1, sizes, values, 1); + assert(rc == fmi3OK); + } + { + const fmi3ValueReference vr[] = {3}; + size_t sizes[] = {0}; + // First call with nullptr value to get size + const auto rc = fmi3GetBinary(instance, vr, 1, sizes, nullptr, 1); + assert(rc == fmi3OK); + assert(sizes[0] == 4); + } + { + const fmi3ValueReference vr[] = {3}; + fmi3Byte outputData[4] = {0}; + const fmi3Byte* values[] = {outputData}; + size_t sizes[] = {0}; + const auto rc = fmi3GetBinary(instance, vr, 1, sizes, values, 1); + assert(rc == fmi3OK); + assert(sizes[0] == 4); + assert(outputData[0] == 0xDE); + assert(outputData[1] == 0xAD); + assert(outputData[2] == 0xBE); + assert(outputData[3] == 0xEF); + } + { + // Invalid value reference for Binary should return error + const fmi3ValueReference vr[] = {0}; + const fmi3Byte data[] = {0x00}; + const fmi3Byte* values[] = {data}; + size_t sizes[] = {1}; + const auto rc = fmi3SetBinary(instance, vr, 1, sizes, values, 1); + assert(rc == fmi3Error); + } + // Test fmi3GetAdjointDerivative (error path, not supported) { const fmi3ValueReference vr[] = {0}; From dbb6ca436fc2c947def99f38ec628b8972638502 Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Mon, 4 May 2026 17:38:33 +0200 Subject: [PATCH 08/10] Add Event Mode support to FMI 3.0 co-simulation interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add four new virtual methods to SlaveInstance3: - EnterEventMode() — does nothing by default - EvaluateDiscreteStates() — does nothing by default - UpdateDiscreteStates() — sets all output flags to 'no events' defaults - EnterStepMode() — does nothing by default Replace hardcoded 'not supported' stubs in fmi3_functions.cpp with proper try/catch forwarding to the new virtuals, following the same pattern used by all other FMI functions. Add test coverage for the full Event Mode lifecycle: EnterEventMode → EvaluateDiscreteStates → UpdateDiscreteStates → EnterStepMode, verifying all output parameters from UpdateDiscreteStates. --- cppfmu_cs_fmi3.cpp | 35 ++++++++++++ cppfmu_cs_fmi3.hpp | 26 +++++++++ fmi3_functions.cpp | 117 +++++++++++++++++++++++++++-------------- tests/cs_test_fmi3.cpp | 30 +++++++++-- 4 files changed, 163 insertions(+), 45 deletions(-) diff --git a/cppfmu_cs_fmi3.cpp b/cppfmu_cs_fmi3.cpp index a358b6d..2d570fc 100644 --- a/cppfmu_cs_fmi3.cpp +++ b/cppfmu_cs_fmi3.cpp @@ -45,6 +45,41 @@ void SlaveInstance3::Reset() } +void SlaveInstance3::EnterEventMode() +{ + // Do nothing +} + + +void SlaveInstance3::EvaluateDiscreteStates() +{ + // Do nothing +} + + +void SlaveInstance3::UpdateDiscreteStates( + FMIBoolean& discreteStatesNeedUpdate, + FMIBoolean& terminateSimulation, + FMIBoolean& nominalsOfContinuousStatesChanged, + FMIBoolean& valuesOfContinuousStatesChanged, + FMIBoolean& nextEventTimeDefined, + FMIReal& nextEventTime) +{ + discreteStatesNeedUpdate = FMIFalse; + terminateSimulation = FMIFalse; + nominalsOfContinuousStatesChanged = FMIFalse; + valuesOfContinuousStatesChanged = FMIFalse; + nextEventTimeDefined = FMIFalse; + nextEventTime = 0.0; +} + + +void SlaveInstance3::EnterStepMode() +{ + // Do nothing +} + + void SlaveInstance3::SetFloat32( const FMIValueReference /*vr*/[], std::size_t nvr, diff --git a/cppfmu_cs_fmi3.hpp b/cppfmu_cs_fmi3.hpp index b9fa652..07908a3 100644 --- a/cppfmu_cs_fmi3.hpp +++ b/cppfmu_cs_fmi3.hpp @@ -58,6 +58,32 @@ class SlaveInstance3 */ virtual void Reset(); + /* Called from fmi3EnterEventMode(). + * Does nothing by default. + */ + virtual void EnterEventMode(); + + /* Called from fmi3EvaluateDiscreteStates(). + * Does nothing by default. + */ + virtual void EvaluateDiscreteStates(); + + /* Called from fmi3UpdateDiscreteStates(). + * Sets all output flags to "no events" by default. + */ + virtual void UpdateDiscreteStates( + FMIBoolean& discreteStatesNeedUpdate, + FMIBoolean& terminateSimulation, + FMIBoolean& nominalsOfContinuousStatesChanged, + FMIBoolean& valuesOfContinuousStatesChanged, + FMIBoolean& nextEventTimeDefined, + FMIReal& nextEventTime); + + /* Called from fmi3EnterStepMode(). + * Does nothing by default. + */ + virtual void EnterStepMode(); + /* Called from fmi3SetFloat32(). * Throws std::logic_error by default. */ diff --git a/fmi3_functions.cpp b/fmi3_functions.cpp index 5fa58cb..4d6827f 100644 --- a/fmi3_functions.cpp +++ b/fmi3_functions.cpp @@ -202,8 +202,73 @@ fmi3Status fmi3EnterEventMode(fmi3Instance instance) { if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); - component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EnterEventMode"); - return fmi3Error; + try { + component->slave->EnterEventMode(); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3EvaluateDiscreteStates(fmi3Instance instance) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + auto component = reinterpret_cast(instance); + try { + component->slave->EvaluateDiscreteStates(); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } +} + + +fmi3Status fmi3UpdateDiscreteStates( + fmi3Instance instance, + fmi3Boolean* discreteStatesNeedUpdate, + fmi3Boolean* terminateSimulation, + fmi3Boolean* nominalsOfContinuousStatesChanged, + fmi3Boolean* valuesOfContinuousStatesChanged, + fmi3Boolean* nextEventTimeDefined, + fmi3Float64* nextEventTime) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + auto component = reinterpret_cast(instance); + try { + fmi3Boolean needUpdate = fmi3False; + fmi3Boolean termSim = fmi3False; + fmi3Boolean nominalsChanged = fmi3False; + fmi3Boolean valuesChanged = fmi3False; + fmi3Boolean timeDefined = fmi3False; + fmi3Float64 time = 0.0; + + component->slave->UpdateDiscreteStates( + needUpdate, termSim, nominalsChanged, valuesChanged, timeDefined, time); + + if (discreteStatesNeedUpdate) *discreteStatesNeedUpdate = needUpdate; + if (terminateSimulation) *terminateSimulation = termSim; + if (nominalsOfContinuousStatesChanged) *nominalsOfContinuousStatesChanged = nominalsChanged; + if (valuesOfContinuousStatesChanged) *valuesOfContinuousStatesChanged = valuesChanged; + if (nextEventTimeDefined) *nextEventTimeDefined = timeDefined; + if (nextEventTime) *nextEventTime = time; + + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } } @@ -211,8 +276,16 @@ fmi3Status fmi3EnterStepMode(fmi3Instance instance) { if (auto status = CheckInstance(instance); status != fmi3OK) return status; auto component = reinterpret_cast(instance); - component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EnterStepMode"); - return fmi3Error; + try { + component->slave->EnterStepMode(); + return fmi3OK; + } catch (const cppfmu::FatalError& e) { + component->Log(fmi3Fatal, "cppfmu", e.what()); + return fmi3Fatal; + } catch (const std::exception& e) { + component->Log(fmi3Error, "cppfmu", e.what()); + return fmi3Error; + } } @@ -1256,42 +1329,6 @@ fmi3Status fmi3ExitConfigurationMode(fmi3Instance instance) } -// ============================================================================ -// Discrete states stubs -// ============================================================================ - - -fmi3Status fmi3EvaluateDiscreteStates(fmi3Instance instance) -{ - if (auto status = CheckInstance(instance); status != fmi3OK) return status; - auto component = reinterpret_cast(instance); - component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3EvaluateDiscreteStates"); - return fmi3Error; -} - - -fmi3Status fmi3UpdateDiscreteStates( - fmi3Instance instance, - fmi3Boolean* discreteStatesNeedUpdate, - fmi3Boolean* terminateSimulation, - fmi3Boolean* nominalsOfContinuousStatesChanged, - fmi3Boolean* valuesOfContinuousStatesChanged, - fmi3Boolean* nextEventTimeDefined, - fmi3Float64* nextEventTime) -{ - if (auto status = CheckInstance(instance); status != fmi3OK) return status; - auto component = reinterpret_cast(instance); - component->Log(fmi3Error, "cppfmu", "FMI function not supported: fmi3UpdateDiscreteStates"); - if (discreteStatesNeedUpdate) *discreteStatesNeedUpdate = fmi3False; - if (terminateSimulation) *terminateSimulation = fmi3False; - if (nominalsOfContinuousStatesChanged) *nominalsOfContinuousStatesChanged = fmi3False; - if (valuesOfContinuousStatesChanged) *valuesOfContinuousStatesChanged = fmi3False; - if (nextEventTimeDefined) *nextEventTimeDefined = fmi3False; - if (nextEventTime) *nextEventTime = 0.0; - return fmi3Error; -} - - // ============================================================================ // Clock interval/shift stubs // ============================================================================ diff --git a/tests/cs_test_fmi3.cpp b/tests/cs_test_fmi3.cpp index d53355e..74af6d2 100644 --- a/tests/cs_test_fmi3.cpp +++ b/tests/cs_test_fmi3.cpp @@ -336,16 +336,36 @@ int main() assert(rc == fmi3Error); } - // Test fmi3EnterEventMode (error path) + // Test Event Mode lifecycle (success path — default implementations) { const auto rc = fmi3EnterEventMode(instance); - assert(rc == fmi3Error); + assert(rc == fmi3OK); + } + { + const auto rc = fmi3EvaluateDiscreteStates(instance); + assert(rc == fmi3OK); + } + { + fmi3Boolean needUpdate = fmi3True; + fmi3Boolean termSim = fmi3True; + fmi3Boolean nominalsChanged = fmi3True; + fmi3Boolean valuesChanged = fmi3True; + fmi3Boolean timeDefined = fmi3True; + fmi3Float64 time = 99.0; + const auto rc = fmi3UpdateDiscreteStates( + instance, &needUpdate, &termSim, &nominalsChanged, + &valuesChanged, &timeDefined, &time); + assert(rc == fmi3OK); + assert(needUpdate == fmi3False); + assert(termSim == fmi3False); + assert(nominalsChanged == fmi3False); + assert(valuesChanged == fmi3False); + assert(timeDefined == fmi3False); + assert(time == 0.0); } - - // Test fmi3EnterStepMode (error path) { const auto rc = fmi3EnterStepMode(instance); - assert(rc == fmi3Error); + assert(rc == fmi3OK); } // Test fmi3FreeInstance with null (should not crash) From 5a0f22392a2838964be7c7a182d79089229827e1 Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Mon, 4 May 2026 17:39:58 +0200 Subject: [PATCH 09/10] Update README to accurately reflect FMI 3.0 stub vs virtual status Move Event Mode, Binary, adjoint derivatives, and variable dependencies from the 'stubs' section to 'fully implemented' since they all have corresponding virtual methods on SlaveInstance3 that users can override. Clarify that only Configuration Mode, Clock interval/shift functions, and Intermediate Update Callback are genuine stubs (no virtual method). Add note that GetClock/SetClock have virtual methods with default throw-on-nonzero-vr behavior, consistent with other type Get/Set methods. --- README.md | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 13fa22e..058d299 100644 --- a/README.md +++ b/README.md @@ -27,27 +27,32 @@ Supported FMI Versions | FMI 2.0 | Stable | `cppfmu_cs.hpp` | `cppfmu_cs.cpp` | `fmi_functions.cpp` | | FMI 3.0 | Partial | `cppfmu_cs_fmi3.hpp` | `cppfmu_cs_fmi3.cpp` | `fmi3_functions.cpp` | -### FMI 3.0 Partial Support +### FMI 3.0 Support Status -FMI 3.0 co-simulation is supported with the following caveats: +FMI 3.0 co-simulation is supported. The `SlaveInstance3` class provides virtual +methods for all Co-Simulation API functions. Functions without a corresponding +virtual method are hardcoded stubs that always return `fmi3Error`. -**Fully implemented:** -- All Get/Set type functions (Float32, Float64, Int8–64, UInt8–64, Boolean, String) -- Directional derivatives and output derivatives +**Fully implemented (virtual methods on `SlaveInstance3`):** +- All Get/Set type functions (Float32, Float64, Int8–64, UInt8–64, Boolean, String, Binary) +- Directional derivatives, adjoint derivatives, and output derivatives +- Variable dependencies and number of dependencies - FMU state management (get/set/serialize/deserialize) - Enhanced `DoStep` with early return, event handling, and termination output parameters +- Event Mode (`EnterEventMode`, `EvaluateDiscreteStates`, `UpdateDiscreteStates`, `EnterStepMode`) - `GetNumberOfEventIndicators` / `GetNumberOfContinuousStates` - Debug logging with categories -**Stub implementations (return "not supported" error):** -- **Event Mode** — `fmi3EnterEventMode`, `fmi3UpdateDiscreteStates` -- **Step Mode** — `fmi3EnterStepMode` +**Genuine stubs (no virtual method — cannot be overridden by users):** - **Configuration Mode** — `fmi3EnterConfigurationMode` / `fmi3ExitConfigurationMode` -- **Clocks** — `GetClock`, `SetClock`, interval/shift functions -- **Adjoint derivatives** — `fmi3GetAdjointDerivative` -- **Variable dependencies** — `fmi3GetVariableDependencies`, `fmi3GetNumberOfVariableDependencies` -- **Binary type** — `fmi3GetBinary`, `fmi3SetBinary` -- **Intermediate Update Callback** — parameter accepted but ignored + (optional feature for structural parameter tuning) +- **Clock interval/shift** — `Get/SetIntervalDecimal/Fraction`, `Get/SetShiftDecimal/Fraction` + (optional feature for variable-interval and shifted clocks) +- **Intermediate Update Callback** — parameter accepted during instantiation but ignored + +**Note:** `GetClock` and `SetClock` have virtual methods on `SlaveInstance3` and can be +overridden by users; the default implementation throws `std::logic_error` for non-zero +variable references, consistent with other type-specific Get/Set methods. ### Why `SlaveInstance3` Instead of Extending `SlaveInstance`? From af530a27d2caa456ec3307c99d36c45d8067cfdb Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Tue, 5 May 2026 16:48:34 +0200 Subject: [PATCH 10/10] Address PR review feedback for FMI 3.0 implementation - Fix invalid value reference test to use vr=999 instead of vr=1, which is actually valid in TestSlave3 (tests/cs_test_fmi3.cpp) - Fix logger callback to forward all log levels to simulation environment, not just errors/fatal when debug logging is disabled (fmi3_functions.cpp) - Only install cppfmu_cs.hpp for FMI 1/2 builds; it's unusable with FMI 3 since Memory and Logger types are compiled out (CMakeLists.txt) - Add guard to reject conflicting CPPFMU_FMI_1 + CPPFMU_FMI_3 configuration - Enable regression tests for FMI 1.0 builds (previously skipped) - Use distinct sentinel value for FMIPending in FMI 3.0 to avoid confusion with FMIWarning (cppfmu_common.hpp) - Add Float32, Int32, and Boolean accessors to TestSlave3 fixture to support new test cases (tests/cs_slave_fmi3.cpp) - Update README conan examples to show FMI 3.0 usage --- CMakeLists.txt | 45 +++++++++++++------- README.md | 6 +-- cppfmu_common.hpp | 4 +- fmi3_functions.cpp | 8 +++- tests/cs_slave_fmi3.cpp | 93 +++++++++++++++++++++++++++++++++++++++++ tests/cs_test_fmi3.cpp | 2 +- 6 files changed, 137 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6dc7fc..43dc5e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,10 @@ project("cppfmu-library" VERSION ${projectVersion}) option(CPPFMU_FMI_1 "Use FMI 1.0" OFF) option(CPPFMU_FMI_3 "Use FMI 3.0" OFF) +if(CPPFMU_FMI_1 AND CPPFMU_FMI_3) + message(FATAL_ERROR "CPPFMU_FMI_1 and CPPFMU_FMI_3 are mutually exclusive; enable at most one.") +endif() + if(CPPFMU_FMI_1) find_package(fmi1 CONFIG REQUIRED) set(FMI fmi1::cosim) @@ -35,28 +39,39 @@ elseif(CPPFMU_FMI_3) endif() install(TARGETS cppfmu ARCHIVE DESTINATION lib RUNTIME DESTINATION bin LIBRARY DESTINATION lib) -install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_common.hpp ${CMAKE_SOURCE_DIR}/cppfmu_cs.hpp DESTINATION include) +install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_common.hpp DESTINATION include) if(CPPFMU_FMI_3) install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_cs_fmi3.hpp DESTINATION include) install(FILES ${CMAKE_SOURCE_DIR}/fmi3_functions.cpp DESTINATION src) else() + install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_cs.hpp DESTINATION include) install(FILES ${CMAKE_SOURCE_DIR}/fmi_functions.cpp DESTINATION src) endif() -if(NOT CPPFMU_FMI_1) - if(CPPFMU_FMI_3) - add_executable(cs_test - "tests/cs_test_fmi3.cpp" - "tests/cs_slave_fmi3.cpp" - "fmi3_functions.cpp" - ) - else() - add_executable(cs_test - "tests/cs_test.cpp" - "tests/cs_slave.cpp" - "fmi_functions.cpp" - ) - endif() +if(CPPFMU_FMI_1) + add_executable(cs_test + "tests/cs_test.cpp" + "tests/cs_slave.cpp" + "fmi_functions.cpp" + ) + target_compile_features(cs_test PRIVATE cxx_std_11) + target_link_libraries(cs_test PRIVATE cppfmu) + add_test(NAME "cs_test" COMMAND cs_test) +elseif(CPPFMU_FMI_3) + add_executable(cs_test + "tests/cs_test_fmi3.cpp" + "tests/cs_slave_fmi3.cpp" + "fmi3_functions.cpp" + ) + target_compile_features(cs_test PRIVATE cxx_std_11) + target_link_libraries(cs_test PRIVATE cppfmu) + add_test(NAME "cs_test" COMMAND cs_test) +else() + add_executable(cs_test + "tests/cs_test.cpp" + "tests/cs_slave.cpp" + "fmi_functions.cpp" + ) target_compile_features(cs_test PRIVATE cxx_std_11) target_link_libraries(cs_test PRIVATE cppfmu) add_test(NAME "cs_test" COMMAND cs_test) diff --git a/README.md b/README.md index 058d299..117184f 100644 --- a/README.md +++ b/README.md @@ -125,13 +125,13 @@ binaries are available on Sintef Ocean's public artifactory, which can be added `conan remote add sintef-public https://gitlab.sintef.no/api/v4/projects/22218/packages/conan`. Note that when using the conan recipe, FMI 1, 2, or 3 is added as a dependency, so you do not need to fetch them yourself. To use CPPFMU with conan, add the following lines -to your `conanfile.py` and `CMakeLists.txt`: +to your `conanfile.py` and `CMakeLists.txt`, showing how to do it with FMI 3: `conanfile.py`: ```python ... def requirements(self): - self.requires("cppfmu/1.0@sintef/stable") + self.requires("cppfmu/1.0@sintef/stable", options={"use_fmi_version": 3}) def generate(self): # Copy fmi_function.cpp to your binary directory @@ -139,7 +139,7 @@ to your `conanfile.py` and `CMakeLists.txt`: if require.build or require.test: continue if dep.ref.name == "cppfmu": - copy(self, "fmi_functions.cpp", + copy(self, "fmi3_functions.cpp", # or "fmi_function.cpp" for FMI 1 or 2 dep.cpp_info.srcdirs[0], path.join(self.build_folder, dep.ref.name), keep_path=False) diff --git a/cppfmu_common.hpp b/cppfmu_common.hpp index 2bf5b31..0fda5b2 100644 --- a/cppfmu_common.hpp +++ b/cppfmu_common.hpp @@ -96,7 +96,9 @@ namespace cppfmu const FMIStatus FMIDiscard = fmi3Discard; const FMIStatus FMIError = fmi3Error; const FMIStatus FMIFatal = fmi3Fatal; - const FMIStatus FMIPending = fmi3Warning; + // FMI 3.0 has no native "pending" status. Keep FMIPending as a distinct + // sentinel value so generic code can still distinguish it from warnings. + const FMIStatus FMIPending = static_cast(-1); #else typedef fmi2Real FMIReal; typedef fmi2Integer FMIInteger; diff --git a/fmi3_functions.cpp b/fmi3_functions.cpp index 4d6827f..63a639d 100644 --- a/fmi3_functions.cpp +++ b/fmi3_functions.cpp @@ -114,7 +114,13 @@ fmi3Instance fmi3InstantiateCoSimulation( auto* comp = component.get(); auto loggerFn = [comp](cppfmu::FMIStatus status, cppfmu::FMIString category, cppfmu::FMIString message) { - comp->Log(status, category, message); + if (comp->logMessage) { + comp->logMessage( + comp->instanceEnvironment, + static_cast(status), + category, + message); + } }; component->slave = CppfmuInstantiateSlave( diff --git a/tests/cs_slave_fmi3.cpp b/tests/cs_slave_fmi3.cpp index 8c14b12..8d3ba4a 100644 --- a/tests/cs_slave_fmi3.cpp +++ b/tests/cs_slave_fmi3.cpp @@ -8,6 +8,36 @@ class TestSlave3 : public cppfmu::SlaveInstance3 { public: + void SetFloat32( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + const cppfmu::FMIFloat32 value[], + std::size_t /*nValues*/) override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 0) { + float32Value_ = value[i]; + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + + void GetFloat32( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + cppfmu::FMIFloat32 value[], + std::size_t /*nValues*/) const override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 0) { + value[i] = float32Value_; + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + void SetFloat64( const cppfmu::FMIValueReference vr[], std::size_t nvr, @@ -46,6 +76,66 @@ class TestSlave3 : public cppfmu::SlaveInstance3 } } + void SetInt32( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + const cppfmu::FMIInteger value[], + std::size_t /*nValues*/) override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 0) { + int32Value_ = value[i]; + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + + void GetInt32( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + cppfmu::FMIInteger value[], + std::size_t /*nValues*/) const override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 0) { + value[i] = int32Value_; + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + + void SetBoolean( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + const cppfmu::FMIBoolean value[], + std::size_t /*nValues*/) override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 0) { + boolValue_ = value[i]; + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + + void GetBoolean( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + cppfmu::FMIBoolean value[], + std::size_t /*nValues*/) const override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 0) { + value[i] = boolValue_; + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + void GetDirectionalDerivative( const cppfmu::FMIValueReference /*vUnknown_ref*/[], std::size_t nUnknown, @@ -179,6 +269,9 @@ class TestSlave3 : public cppfmu::SlaveInstance3 private: cppfmu::FMIReal value_ = 0.0; + cppfmu::FMIFloat32 float32Value_ = 0.0f; + cppfmu::FMIInteger int32Value_ = 0; + cppfmu::FMIBoolean boolValue_ = cppfmu::FMIFalse; bool derivativeSupported_ = false; cppfmu::FMIReal seed_ = 1.0; std::vector binaryData_; diff --git a/tests/cs_test_fmi3.cpp b/tests/cs_test_fmi3.cpp index 74af6d2..c6f935a 100644 --- a/tests/cs_test_fmi3.cpp +++ b/tests/cs_test_fmi3.cpp @@ -195,7 +195,7 @@ int main() assert(val == value1); } { - const fmi3ValueReference invalidVr = 1; + const fmi3ValueReference invalidVr = 999; fmi3Float64 val = -1.0; const auto rc = fmi3GetFloat64(instance, &invalidVr, 1, &val, 1); assert(rc == fmi3Error);