From 4fd36950d4a557a03acfae309314735b38774bc2 Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Wed, 29 Apr 2026 17:15:14 +0200 Subject: [PATCH 1/3] Expose directional derivative and input/output derivative APIs Add GetDirectionalDerivative, SetRealInputDerivatives, and GetRealOutputDerivatives virtual methods to SlaveInstance, replacing the previous stub implementations that always returned errors. Available for both FMI 1.0 and FMI 2.0. Fix ctest invocation for Conan 2: cmake.ctest() in Conan 2 requires cli_args as a list, not a positional string argument. --- conanfile.py | 2 +- cppfmu_cs.cpp | 32 ++++++++++ cppfmu_cs.hpp | 29 +++++++++ fmi_functions.cpp | 153 ++++++++++++++++++++++++++++++++-------------- version.txt | 2 +- 5 files changed, 169 insertions(+), 49 deletions(-) diff --git a/conanfile.py b/conanfile.py index cd0ba4e..ea6f982 100644 --- a/conanfile.py +++ b/conanfile.py @@ -98,7 +98,7 @@ def build(self): cmake = CMake(self) cmake.configure() cmake.build() - cmake.ctest("--output-on-failure") + cmake.ctest(cli_args=["--output-on-failure"]) def package(self): copy(self, "LICENCE.txt", self.source_folder, diff --git a/cppfmu_cs.cpp b/cppfmu_cs.cpp index c540e64..bf3e369 100644 --- a/cppfmu_cs.cpp +++ b/cppfmu_cs.cpp @@ -139,6 +139,38 @@ void SlaveInstance::GetString( } +void SlaveInstance::GetDirectionalDerivative( + const FMIValueReference /*vUnknown_ref*/[], + std::size_t /*nUnknown*/, + const FMIValueReference /*vKnown_ref*/[], + std::size_t /*nKnown*/, + const FMIReal /*dvKnown*/[], + FMIReal /*dvUnknown*/[]) const +{ + throw std::logic_error("Operation not supported: get directional derivative"); +} + + +void SlaveInstance::SetRealInputDerivatives( + const FMIValueReference /*vr*/[], + std::size_t /*nvr*/, + const FMIInteger /*order*/[], + const FMIReal /*value*/[]) +{ + throw std::logic_error("Operation not supported: set real input derivatives"); +} + + +void SlaveInstance::GetRealOutputDerivatives( + const FMIValueReference /*vr*/[], + std::size_t /*nvr*/, + const FMIInteger /*order*/[], + FMIReal /*value*/[]) const +{ + throw std::logic_error("Operation not supported: get real output derivatives"); +} + + void SlaveInstance::GetFMUState(FMIFMUState* state) { throw std::logic_error("Operation not supported: get FMU state"); diff --git a/cppfmu_cs.hpp b/cppfmu_cs.hpp index 105435f..d9a8498 100644 --- a/cppfmu_cs.hpp +++ b/cppfmu_cs.hpp @@ -103,6 +103,35 @@ class SlaveInstance std::size_t nvr, FMIString value[]) const; + /* Called from fmi2GetDirectionalDerivative()/fmiGetDirectionalDerivative(). + * Throws std::logic_error by default. + */ + virtual void GetDirectionalDerivative( + const FMIValueReference vUnknown_ref[], + std::size_t nUnknown, + const FMIValueReference vKnown_ref[], + std::size_t nKnown, + const FMIReal dvKnown[], + FMIReal dvUnknown[]) const; + + /* Called from fmi2SetRealInputDerivatives()/fmiSetRealInputDerivatives(). + * Throws std::logic_error by default. + */ + virtual void SetRealInputDerivatives( + const FMIValueReference vr[], + std::size_t nvr, + const FMIInteger order[], + const FMIReal value[]); + + /* Called from fmi2GetRealOutputDerivatives()/fmiGetRealOutputDerivatives(). + * Throws std::logic_error by default. + */ + virtual void GetRealOutputDerivatives( + const FMIValueReference vr[], + std::size_t nvr, + const FMIInteger order[], + FMIReal value[]) const; + /* Called from fmi2GetFMUState(). * Never called with FMI 1.x. * Throws std::logic_error by default. diff --git a/fmi_functions.cpp b/fmi_functions.cpp index 131d193..90aab8f 100644 --- a/fmi_functions.cpp +++ b/fmi_functions.cpp @@ -318,33 +318,71 @@ fmiStatus fmiSetString (fmiComponent c, const fmiValueReference vr[], size_t nv } +fmiStatus fmiGetDirectionalDerivative( + fmiComponent c, + const fmiValueReference vUnknown_ref[], + size_t nUnknown, + const fmiValueReference vKnown_ref[], + size_t nKnown, + const fmiReal dvKnown[], + fmiReal dvUnknown[]) +{ + const auto component = reinterpret_cast(c); + try { + component->slave->GetDirectionalDerivative( + vUnknown_ref, nUnknown, + vKnown_ref, nKnown, + dvKnown, dvUnknown); + return fmiOK; + } catch (const cppfmu::FatalError& e) { + component->logger.Log(fmiFatal, "", e.what()); + return fmiFatal; + } catch (const std::exception& e) { + component->logger.Log(fmiError, "", e.what()); + return fmiError; + } +} + + fmiStatus fmiSetRealInputDerivatives( fmiComponent c, - const fmiValueReference /*vr*/[], - size_t /*nvr*/, - const fmiInteger /*order*/[], - const fmiReal /*value*/[]) + const fmiValueReference vr[], + size_t nvr, + const fmiInteger order[], + const fmiReal value[]) { - reinterpret_cast(c)->logger.Log( - fmiError, - "cppfmu", - "FMI function not supported: fmiSetRealInputDerivatives"); - return fmiError; + const auto component = reinterpret_cast(c); + try { + component->slave->SetRealInputDerivatives(vr, nvr, order, value); + return fmiOK; + } catch (const cppfmu::FatalError& e) { + component->logger.Log(fmiFatal, "", e.what()); + return fmiFatal; + } catch (const std::exception& e) { + component->logger.Log(fmiError, "", e.what()); + return fmiError; + } } fmiStatus fmiGetRealOutputDerivatives( fmiComponent c, - const fmiValueReference /*vr*/[], - size_t /*nvr*/, - const fmiInteger /*order*/[], - fmiReal /*value*/[]) + const fmiValueReference vr[], + size_t nvr, + const fmiInteger order[], + fmiReal value[]) { - reinterpret_cast(c)->logger.Log( - fmiError, - "cppfmu", - "FMI function not supported: fmiGetRealOutputDerivatives"); - return fmiError; + const auto component = reinterpret_cast(c); + try { + component->slave->GetRealOutputDerivatives(vr, nvr, order, value); + return fmiOK; + } catch (const cppfmu::FatalError& e) { + component->logger.Log(fmiFatal, "", e.what()); + return fmiFatal; + } catch (const std::exception& e) { + component->logger.Log(fmiError, "", e.what()); + return fmiError; + } } @@ -908,46 +946,67 @@ fmi2Status fmi2DeSerializeFMUstate( fmi2Status fmi2GetDirectionalDerivative( fmi2Component c, - const fmi2ValueReference[], - size_t, - const fmi2ValueReference[], - size_t, - const fmi2Real[], - fmi2Real[]) + const fmi2ValueReference vUnknown_ref[], + size_t nUnknown, + const fmi2ValueReference vKnown_ref[], + size_t nKnown, + const fmi2Real dvKnown[], + fmi2Real dvUnknown[]) { - reinterpret_cast(c)->logger.Log( - fmi2Error, - "cppfmu", - "FMI function not supported: fmi2GetDirectionalDerivative"); - return fmi2Error; + const auto component = reinterpret_cast(c); + try { + component->slave->GetDirectionalDerivative( + vUnknown_ref, nUnknown, + vKnown_ref, nKnown, + dvKnown, dvUnknown); + return fmi2OK; + } catch (const cppfmu::FatalError& e) { + component->logger.Log(fmi2Fatal, "", e.what()); + return fmi2Fatal; + } catch (const std::exception& e) { + component->logger.Log(fmi2Error, "", e.what()); + return fmi2Error; + } } fmi2Status fmi2SetRealInputDerivatives( fmi2Component c, - const fmi2ValueReference[], - size_t, - const fmi2Integer[], - const fmi2Real[]) + const fmi2ValueReference vr[], + size_t nvr, + const fmi2Integer order[], + const fmi2Real value[]) { - reinterpret_cast(c)->logger.Log( - fmi2Error, - "cppfmu", - "FMI function not supported: fmi2SetRealInputDerivatives"); - return fmi2Error; + const auto component = reinterpret_cast(c); + try { + component->slave->SetRealInputDerivatives(vr, nvr, order, value); + return fmi2OK; + } catch (const cppfmu::FatalError& e) { + component->logger.Log(fmi2Fatal, "", e.what()); + return fmi2Fatal; + } catch (const std::exception& e) { + component->logger.Log(fmi2Error, "", e.what()); + return fmi2Error; + } } fmi2Status fmi2GetRealOutputDerivatives( fmi2Component c, - const fmi2ValueReference [], - size_t, - const fmi2Integer[], - fmi2Real[]) + const fmi2ValueReference vr[], + size_t nvr, + const fmi2Integer order[], + fmi2Real value[]) { - reinterpret_cast(c)->logger.Log( - fmi2Error, - "cppfmu", - "FMI function not supported: fmiGetRealOutputDerivatives"); - return fmi2Error; + const auto component = reinterpret_cast(c); + try { + component->slave->GetRealOutputDerivatives(vr, nvr, order, value); + return fmi2OK; + } catch (const cppfmu::FatalError& e) { + component->logger.Log(fmi2Fatal, "", e.what()); + return fmi2Fatal; + } catch (const std::exception& e) { + component->logger.Log(fmi2Error, "", e.what()); + return fmi2Error; + } } fmi2Status fmi2DoStep( diff --git a/version.txt b/version.txt index 524cb55..26aaba0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.1.1 +1.2.0 From 9ecf0c1abc21f50ced627c32ea873593edf32f73 Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Wed, 29 Apr 2026 18:12:49 +0200 Subject: [PATCH 2/3] Add derivative API tests for FMI 1.0 and 2.0, remove non-standard FMI 1.0 function Extend cs_test fixture to cover fmi2GetDirectionalDerivative, fmi2SetRealInputDerivatives, and fmi2GetRealOutputDerivatives (FMI 2.0) plus fmiSetRealInputDerivatives and fmiGetRealOutputDerivatives (FMI 1.0). Tests cover both the default 'not supported' error path and the success path with a simple linear derivative model in TestSlave. Remove fmiGetDirectionalDerivative from FMI 1.0 build since it is not part of the FMI 1.0 co-simulation standard. Add cs_test_fmi1 executable target for FMI 1.0 test coverage. --- CMakeLists.txt | 10 ++ fmi_functions.cpp | 25 ----- tests/cs_slave.cpp | 62 ++++++++++++ tests/cs_test.cpp | 230 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 297 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac0b3fa..faf46a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,4 +39,14 @@ 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) +else() + add_executable(cs_test_fmi1 + "tests/cs_test.cpp" + "tests/cs_slave.cpp" + "fmi_functions.cpp" + ) + target_compile_features(cs_test_fmi1 PRIVATE cxx_std_11) + target_link_libraries(cs_test_fmi1 PRIVATE cppfmu) + target_compile_definitions(cs_test_fmi1 PRIVATE CPPFMU_USE_FMI_1_0 "MODEL_IDENTIFIER=") + add_test(NAME "cs_test_fmi1" COMMAND cs_test_fmi1) endif() diff --git a/fmi_functions.cpp b/fmi_functions.cpp index 90aab8f..92f968f 100644 --- a/fmi_functions.cpp +++ b/fmi_functions.cpp @@ -318,31 +318,6 @@ fmiStatus fmiSetString (fmiComponent c, const fmiValueReference vr[], size_t nv } -fmiStatus fmiGetDirectionalDerivative( - fmiComponent c, - const fmiValueReference vUnknown_ref[], - size_t nUnknown, - const fmiValueReference vKnown_ref[], - size_t nKnown, - const fmiReal dvKnown[], - fmiReal dvUnknown[]) -{ - const auto component = reinterpret_cast(c); - try { - component->slave->GetDirectionalDerivative( - vUnknown_ref, nUnknown, - vKnown_ref, nKnown, - dvKnown, dvUnknown); - return fmiOK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmiFatal, "", e.what()); - return fmiFatal; - } catch (const std::exception& e) { - component->logger.Log(fmiError, "", e.what()); - return fmiError; - } -} - fmiStatus fmiSetRealInputDerivatives( fmiComponent c, diff --git a/tests/cs_slave.cpp b/tests/cs_slave.cpp index c249391..45e4c4e 100644 --- a/tests/cs_slave.cpp +++ b/tests/cs_slave.cpp @@ -2,6 +2,7 @@ #include #include +#include class TestSlave : public cppfmu::SlaveInstance @@ -20,6 +21,10 @@ class TestSlave : public cppfmu::SlaveInstance 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"); } @@ -34,12 +39,65 @@ class TestSlave : public cppfmu::SlaveInstance for (std::size_t i = 0; i < nvr; ++i) { if (vr[i] == 0) { value[i] = value_; + } else if (vr[i] == 1) { + value[i] = derivativeSupported_ ? 1.0 : 0.0; + } else if (vr[i] == 2) { + value[i] = seed_; } else { throw std::logic_error("Invalid value reference"); } } } + void GetDirectionalDerivative( + const cppfmu::FMIValueReference vUnknown_ref[], + std::size_t nUnknown, + const cppfmu::FMIValueReference vKnown_ref[], + std::size_t nKnown, + const cppfmu::FMIReal dvKnown[], + cppfmu::FMIReal dvUnknown[]) const override + { + if (!derivativeSupported_) { + SlaveInstance::GetDirectionalDerivative( + vUnknown_ref, nUnknown, + vKnown_ref, nKnown, + dvKnown, dvUnknown); + } + 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 SetRealInputDerivatives( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + const cppfmu::FMIInteger order[], + const cppfmu::FMIReal value[]) override + { + if (!derivativeSupported_) { + SlaveInstance::SetRealInputDerivatives(vr, nvr, order, value); + } + inputDerivatives_.assign(value, value + nvr); + inputOrders_.assign(order, order + nvr); + } + + void GetRealOutputDerivatives( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + const cppfmu::FMIInteger order[], + cppfmu::FMIReal value[]) const override + { + if (!derivativeSupported_) { + SlaveInstance::GetRealOutputDerivatives(vr, nvr, order, value); + } + 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) @@ -96,6 +154,10 @@ class TestSlave : public cppfmu::SlaveInstance private: cppfmu::Memory memory_; cppfmu::FMIReal value_ = 0.0; + bool derivativeSupported_ = false; + cppfmu::FMIReal seed_ = 1.0; + std::vector inputDerivatives_; + std::vector inputOrders_; }; diff --git a/tests/cs_test.cpp b/tests/cs_test.cpp index f7f4aa8..a0f1cd2 100644 --- a/tests/cs_test.cpp +++ b/tests/cs_test.cpp @@ -1,4 +1,11 @@ +#ifdef CPPFMU_USE_FMI_1_0 +#define MODEL_IDENTIFIER +extern "C" { +#include +} +#else #include +#endif #include #include @@ -9,6 +16,27 @@ #include +#ifdef CPPFMU_USE_FMI_1_0 +const double TEST_VALUE = 2.0; +const char* const TEST_INSTANCE_NAME = "MyInstance"; + + +extern "C" void logger( + fmiComponent c, + fmiString instanceName, + fmiStatus status, + fmiString category, + fmiString message, + ...) noexcept +{ + assert(std::string(instanceName) == std::string(TEST_INSTANCE_NAME)); + va_list args; + va_start(args, message); + std::vfprintf(stderr, message, args); + std::fprintf(stderr, "\n"); + va_end(args); +} +#else const double TEST_VALUE = 2.0; const char* const TEST_INSTANCE_NAME = "MyInstance"; @@ -28,6 +56,7 @@ extern "C" void logger( std::fprintf(stderr, "\n"); va_end(args); } +#endif extern "C" void* alloc(std::size_t nobj, std::size_t size) noexcept { @@ -37,7 +66,130 @@ extern "C" void* alloc(std::size_t nobj, std::size_t size) noexcept int main() { - // Instantiation and setup +#ifdef CPPFMU_USE_FMI_1_0 + // Instantiation and setup (FMI 1.0) + fmiCallbackFunctions callbacks = { + &logger, + &alloc, + &std::free, + }; + const auto instance = fmiInstantiateSlave( + TEST_INSTANCE_NAME, + "04b947f3-c057-4860-b59b-eb0bd6fa52be", + "", + "application/x-fmu-sharedlibrary", + 0.0, + fmiFalse, + fmiFalse, + callbacks, + fmiTrue); + assert(instance); + + // Initialization (FMI 1.0) + { + const auto rc = fmiInitializeSlave(instance, 0.0, fmiFalse, 0.0); + assert(rc == fmiOK); + } + + const fmiValueReference validVr = 0; + const fmiReal value1 = 1.0; + { + const auto rc = fmiSetReal(instance, &validVr, 1, &value1); + assert(rc == fmiOK); + } + { + fmiReal val = 0.0; + const auto rc = fmiGetReal(instance, &validVr, 1, &val); + assert(rc == fmiOK); + assert(val == value1); + } + + // Derivative APIs — default "not supported" path (FMI 1.0) + // Note: fmiGetDirectionalDerivative is not part of FMI 1.0 spec + { + const fmiValueReference vr[] = {0}; + fmiInteger order[] = {1}; + fmiReal value[] = {1.0}; + auto rc = fmiSetRealInputDerivatives(instance, vr, 1, order, value); + assert(rc == fmiError); + } + { + const fmiValueReference vr[] = {0}; + fmiInteger order[] = {1}; + fmiReal value[] = {0.0}; + auto rc = fmiGetRealOutputDerivatives(instance, vr, 1, order, value); + assert(rc == fmiError); + } + + // Enable derivative support (vr=1) and set seed (vr=2) (FMI 1.0) + { + const fmiValueReference enableVr[] = {1}; + const fmiReal enableVal = 1.0; + auto rc = fmiSetReal(instance, enableVr, 1, &enableVal); + assert(rc == fmiOK); + } + { + const fmiValueReference seedVr[] = {2}; + const fmiReal seedVal = 2.0; + auto rc = fmiSetReal(instance, seedVr, 1, &seedVal); + assert(rc == fmiOK); + } + + // fmiSetRealInputDerivatives — success (FMI 1.0) + { + const fmiValueReference vr[] = {0}; + fmiInteger order[] = {1}; + fmiReal value[] = {3.14}; + auto rc = fmiSetRealInputDerivatives(instance, vr, 1, order, value); + assert(rc == fmiOK); + } + + // fmiGetRealOutputDerivatives — success, value = seed * order = 2.0 * 1 = 2.0 (FMI 1.0) + { + const fmiValueReference vr[] = {0}; + fmiInteger order[] = {1}; + fmiReal value[] = {0.0}; + auto rc = fmiGetRealOutputDerivatives(instance, vr, 1, order, value); + assert(rc == fmiOK); + assert(value[0] == 2.0); + } + + // Simulation (FMI 1.0) + { + const auto rc = fmiDoStep(instance, 0.0, 0.1, fmiTrue); + assert(rc == fmiOK); + } + const fmiReal value2 = 2.0; + { + const auto rc = fmiSetReal(instance, &validVr, 1, &value2); + assert(rc == fmiOK); + } + { + const auto rc = fmiDoStep(instance, 0.0, 0.1, fmiTrue); + assert(rc == fmiOK); + } + { + fmiReal val = 0.0; + const auto rc = fmiGetReal(instance, &validVr, 1, &val); + assert(rc == fmiOK); + assert(val == value2); + } + { + const fmiValueReference invalidVr = 1; + fmiReal val = -1.0; + const auto rc = fmiGetReal(instance, &invalidVr, 1, &val); + assert(rc == fmiError); + std::fprintf(stderr, "(The last error was expected.)\n"); + assert(val == -1.0); + } + + // Termination (FMI 1.0) + const auto terminateResult = fmiTerminateSlave(instance); + assert(terminateResult == fmiOK); + + fmiFreeSlaveInstance(instance); +#else + // Instantiation and setup (FMI 2.0) const auto callbacks = fmi2CallbackFunctions{ &logger, &alloc, @@ -66,7 +218,7 @@ int main() assert(rc == fmi2OK); } - // Initialization + // Initialization (FMI 2.0) { const auto rc = fmi2EnterInitializationMode(instance); assert(rc == fmi2OK); @@ -90,7 +242,74 @@ int main() assert(rc == fmi2OK); } - // Save state + // Derivative APIs — default "not supported" path (FMI 2.0) + { + const fmi2ValueReference vr[] = {0}; + fmi2Real dvKnown[] = {1.0}; + fmi2Real dvUnknown[] = {0.0}; + auto rc = fmi2GetDirectionalDerivative(instance, vr, 1, vr, 1, dvKnown, dvUnknown); + assert(rc == fmi2Error); + } + { + const fmi2ValueReference vr[] = {0}; + fmi2Integer order[] = {1}; + fmi2Real value[] = {1.0}; + auto rc = fmi2SetRealInputDerivatives(instance, vr, 1, order, value); + assert(rc == fmi2Error); + } + { + const fmi2ValueReference vr[] = {0}; + fmi2Integer order[] = {1}; + fmi2Real value[] = {0.0}; + auto rc = fmi2GetRealOutputDerivatives(instance, vr, 1, order, value); + assert(rc == fmi2Error); + } + + // Enable derivative support (vr=1) and set seed (vr=2) (FMI 2.0) + { + const fmi2ValueReference enableVr[] = {1}; + const fmi2Real enableVal = 1.0; + auto rc = fmi2SetReal(instance, enableVr, 1, &enableVal); + assert(rc == fmi2OK); + } + { + const fmi2ValueReference seedVr[] = {2}; + const fmi2Real seedVal = 2.0; + auto rc = fmi2SetReal(instance, seedVr, 1, &seedVal); + assert(rc == fmi2OK); + } + + // fmi2SetRealInputDerivatives — success (FMI 2.0) + { + const fmi2ValueReference vr[] = {0}; + fmi2Integer order[] = {1}; + fmi2Real value[] = {3.14}; + auto rc = fmi2SetRealInputDerivatives(instance, vr, 1, order, value); + assert(rc == fmi2OK); + } + + // fmi2GetRealOutputDerivatives — success, value = seed * order = 2.0 * 1 = 2.0 (FMI 2.0) + { + const fmi2ValueReference vr[] = {0}; + fmi2Integer order[] = {1}; + fmi2Real value[] = {0.0}; + auto rc = fmi2GetRealOutputDerivatives(instance, vr, 1, order, value); + assert(rc == fmi2OK); + assert(value[0] == 2.0); + } + + // fmi2GetDirectionalDerivative — success, dvUnknown = seed * dvKnown = 2.0 * 5.0 = 10.0 (FMI 2.0) + { + const fmi2ValueReference vUnknown[] = {0}; + const fmi2ValueReference vKnown[] = {0}; + fmi2Real dvKnown[] = {5.0}; + fmi2Real dvUnknown[] = {0.0}; + auto rc = fmi2GetDirectionalDerivative(instance, vUnknown, 1, vKnown, 1, dvKnown, dvUnknown); + assert(rc == fmi2OK); + assert(dvUnknown[0] == 10.0); + } + + // Save state (FMI 2.0) fmi2FMUstate state = nullptr; { const auto rc = fmi2GetFMUstate(instance, &state); @@ -115,7 +334,7 @@ int main() assert(state == nullptr); } - // Simulation + // Simulation (FMI 2.0) { const auto rc = fmi2DoStep(instance, 0.0, 0.1, fmi2False); assert(rc == fmi2OK); @@ -166,10 +385,11 @@ int main() assert(val == -1.0); } - // Termination + // Termination (FMI 2.0) const auto terminateResult = fmi2Terminate(instance); assert(terminateResult == fmi2OK); fmi2FreeInstance(instance); +#endif return 0; } From bac480eb2369f211839f12801263f0dbe25dcb5f Mon Sep 17 00:00:00 2001 From: Joakim Haugen Date: Thu, 30 Apr 2026 18:52:15 +0200 Subject: [PATCH 3/3] Use SetBoolean/GetBoolean for derivative enable flag in test slave Replace the vr=1 Real control variable with a Boolean, which is more semantically natural for an enable/disable flag. This also exercises the SetBoolean and GetBoolean virtuals, which previously had zero test coverage. The vr=1 value reference now throws in SetReal/GetReal, extending the invalid value reference error path coverage. --- tests/cs_slave.cpp | 32 ++++++++++++++++++++++++++++---- tests/cs_test.cpp | 26 ++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/tests/cs_slave.cpp b/tests/cs_slave.cpp index 45e4c4e..8e13b8d 100644 --- a/tests/cs_slave.cpp +++ b/tests/cs_slave.cpp @@ -21,8 +21,6 @@ class TestSlave : public cppfmu::SlaveInstance 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 { @@ -39,8 +37,6 @@ class TestSlave : public cppfmu::SlaveInstance 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 { @@ -49,6 +45,34 @@ class TestSlave : public cppfmu::SlaveInstance } } + void SetBoolean( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + const cppfmu::FMIBoolean value[]) override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 1) { + derivativeSupported_ = (value[i] == cppfmu::FMITrue); + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + + void GetBoolean( + const cppfmu::FMIValueReference vr[], + std::size_t nvr, + cppfmu::FMIBoolean value[]) const override + { + for (std::size_t i = 0; i < nvr; ++i) { + if (vr[i] == 1) { + value[i] = derivativeSupported_ ? cppfmu::FMITrue : cppfmu::FMIFalse; + } else { + throw std::logic_error("Invalid value reference"); + } + } + } + void GetDirectionalDerivative( const cppfmu::FMIValueReference vUnknown_ref[], std::size_t nUnknown, diff --git a/tests/cs_test.cpp b/tests/cs_test.cpp index a0f1cd2..3a11a39 100644 --- a/tests/cs_test.cpp +++ b/tests/cs_test.cpp @@ -124,8 +124,8 @@ int main() // Enable derivative support (vr=1) and set seed (vr=2) (FMI 1.0) { const fmiValueReference enableVr[] = {1}; - const fmiReal enableVal = 1.0; - auto rc = fmiSetReal(instance, enableVr, 1, &enableVal); + const fmiBoolean enableVal = fmiTrue; + auto rc = fmiSetBoolean(instance, enableVr, 1, &enableVal); assert(rc == fmiOK); } { @@ -135,6 +135,15 @@ int main() assert(rc == fmiOK); } + // Verify derivative support flag via GetBoolean + { + const fmiValueReference enableVr[] = {1}; + fmiBoolean val = fmiFalse; + auto rc = fmiGetBoolean(instance, enableVr, 1, &val); + assert(rc == fmiOK); + assert(val == fmiTrue); + } + // fmiSetRealInputDerivatives — success (FMI 1.0) { const fmiValueReference vr[] = {0}; @@ -268,8 +277,8 @@ int main() // Enable derivative support (vr=1) and set seed (vr=2) (FMI 2.0) { const fmi2ValueReference enableVr[] = {1}; - const fmi2Real enableVal = 1.0; - auto rc = fmi2SetReal(instance, enableVr, 1, &enableVal); + const fmi2Boolean enableVal = fmi2True; + auto rc = fmi2SetBoolean(instance, enableVr, 1, &enableVal); assert(rc == fmi2OK); } { @@ -279,6 +288,15 @@ int main() assert(rc == fmi2OK); } + // Verify derivative support flag via GetBoolean + { + const fmi2ValueReference enableVr[] = {1}; + fmi2Boolean val = fmi2False; + auto rc = fmi2GetBoolean(instance, enableVr, 1, &val); + assert(rc == fmi2OK); + assert(val == fmi2True); + } + // fmi2SetRealInputDerivatives — success (FMI 2.0) { const fmi2ValueReference vr[] = {0};