diff --git a/CMakeLists.txt b/CMakeLists.txt index faf46a1..43dc5e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,31 +6,49 @@ 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 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) + 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) +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_1) add_executable(cs_test "tests/cs_test.cpp" "tests/cs_slave.cpp" @@ -39,14 +57,22 @@ if(NOT CPPFMU_FMI_1) 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_fmi1 + add_executable(cs_test "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) + target_compile_features(cs_test PRIVATE cxx_std_11) + target_link_libraries(cs_test PRIVATE cppfmu) + add_test(NAME "cs_test" COMMAND cs_test) endif() diff --git a/README.md b/README.md index 39fb0b5..117184f 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,72 @@ 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 Support Status + +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 (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 + +**Genuine stubs (no virtual method — cannot be overridden by users):** +- **Configuration Mode** — `fmi3EnterConfigurationMode` / `fmi3ExitConfigurationMode` + (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`? + +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,19 +119,19 @@ 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`, 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 @@ -73,7 +139,7 @@ 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) @@ -92,12 +158,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 +183,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 +213,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 +264,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 +281,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/conanfile.py b/conanfile.py index ea6f982..615b963 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" - topics = ("Co-simulation") + 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..0fda5b2 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/. @@ -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,40 @@ 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; + // 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; @@ -107,6 +143,87 @@ 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>; + + +#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 // ============================================================================ @@ -291,14 +408,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 +511,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..2d570fc --- /dev/null +++ b/cppfmu_cs_fmi3.cpp @@ -0,0 +1,536 @@ +/* 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::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, + 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"); +} + + +std::size_t SlaveInstance3::GetNumberOfEventIndicators() const +{ + return 0; +} + + +std::size_t SlaveInstance3::GetNumberOfContinuousStates() const +{ + return 0; +} + + +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..07908a3 --- /dev/null +++ b/cppfmu_cs_fmi3.hpp @@ -0,0 +1,505 @@ +/* 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 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. + */ + 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 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. + 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..63a639d --- /dev/null +++ b/fmi3_functions.cpp @@ -0,0 +1,1469 @@ +/* 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::FMIComponentEnvironment instanceEnvironment, + fmi3LogMessageCallback logMessage, + cppfmu::FMIBoolean loggingOn) + : instanceEnvironment{instanceEnvironment} + , logMessage{logMessage} + , debugLoggingEnabled{loggingOn == fmi3True} + , lastSuccessfulTime{std::numeric_limits::quiet_NaN()} + { + } + + 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) && + IsCategoryLogged(category)) { + if (logMessage) { + logMessage(instanceEnvironment, status, category, message); + } + } + } + + cppfmu::FMIComponentEnvironment instanceEnvironment; + fmi3LogMessageCallback logMessage; + bool debugLoggingEnabled; + std::vector loggedCategories; + cppfmu::UniquePtr slave; + cppfmu::FMIReal lastSuccessfulTime; + }; + + inline fmi3Status CheckInstance(fmi3Instance instance) + { + if (instance == nullptr) return fmi3Fatal; + return fmi3OK; + } +} + + +extern "C" +{ + +// ============================================================================= +// FMI 3.0 functions +// ============================================================================= + + +const char* fmi3GetVersion() +{ + return fmi3Version; +} + + +fmi3Status fmi3SetDebugLogging( + fmi3Instance instance, + fmi3Boolean loggingOn, + 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(); + for (size_t i = 0; i < nCategories; ++i) { + component->loggedCategories.emplace_back(categories[i]); + } + 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; + std::unique_ptr component; + try { + component.reset(new Component( + instanceEnvironment, logMessage, loggingOn)); + + auto* comp = component.get(); + auto loggerFn = [comp](cppfmu::FMIStatus status, cppfmu::FMIString category, cppfmu::FMIString message) { + if (comp->logMessage) { + comp->logMessage( + comp->instanceEnvironment, + static_cast(status), + category, + message); + } + }; + + component->slave = CppfmuInstantiateSlave( + instanceName, + instantiationToken, + resourceLocation, + visible, + loggingOn, + eventModeUsed, + earlyReturnAllowed, + requiredIntermediateVariables, + nRequiredIntermediateVariables, + instanceEnvironment, + loggerFn); + + return component.release(); + } 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) +{ + if (instance == nullptr) return; + auto component = reinterpret_cast(instance); + delete component; +} + + +fmi3Status fmi3EnterInitializationMode( + fmi3Instance instance, + fmi3Boolean toleranceDefined, + fmi3Float64 tolerance, + fmi3Float64 startTime, + fmi3Boolean stopTimeDefined, + fmi3Float64 stopTime) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + auto component = reinterpret_cast(instance); + 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; + } +} + + +fmi3Status fmi3EnterStepMode(fmi3Instance instance) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + auto component = reinterpret_cast(instance); + 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; + } +} + + +fmi3Status fmi3Terminate(fmi3Instance instance) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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[]) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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[]) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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; + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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) +{ + if (auto status = CheckInstance(instance); status != fmi3OK) return status; + 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; + } +} + + +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(); + 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; + } +} + + +// ============================================================================ +// Configuration mode stubs +// ============================================================================ + + +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; +} + + +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; +} + + +// ============================================================================ +// Clock interval/shift stubs +// ============================================================================ + + +fmi3Status fmi3GetIntervalDecimal( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + 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) { + 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[]) +{ + 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) { + 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[]) +{ + 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) { + if (shifts) shifts[i] = 0.0; + } + return fmi3Error; +} + + +fmi3Status fmi3GetShiftFraction( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + 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) { + 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[]) +{ + 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; +} + + +fmi3Status fmi3SetIntervalFraction( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + 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; +} + + +fmi3Status fmi3SetShiftDecimal( + fmi3Instance instance, + const fmi3ValueReference clocks[], + 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; +} + + +fmi3Status fmi3SetShiftFraction( + fmi3Instance instance, + const fmi3ValueReference clocks[], + size_t nClocks, + 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; +} + + +} // 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/tests/cs_slave_fmi3.cpp b/tests/cs_slave_fmi3.cpp new file mode 100644 index 0000000..8d3ba4a --- /dev/null +++ b/tests/cs_slave_fmi3.cpp @@ -0,0 +1,295 @@ +#include + +#include +#include +#include + + +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, + 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 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, + 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; + } + + 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; + 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_; +}; + + +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..c6f935a --- /dev/null +++ b/tests/cs_test_fmi3.cpp @@ -0,0 +1,389 @@ +#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 = 999; + 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); + } + + // 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 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}; + 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 (error path, not supported) + { + 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 == fmi3Error); + } + + // Test fmi3GetNumberOfVariableDependencies (error path, not supported) + { + size_t nDeps = 99; + const auto rc = fmi3GetNumberOfVariableDependencies(instance, 0, &nDeps); + assert(rc == fmi3Error); + } + + // Test Event Mode lifecycle (success path — default implementations) + { + const auto rc = fmi3EnterEventMode(instance); + 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); + } + { + const auto rc = fmi3EnterStepMode(instance); + assert(rc == fmi3OK); + } + + // 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); + + fmi3FreeInstance(instance); + + return 0; +} 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