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/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..92f968f 100644 --- a/fmi_functions.cpp +++ b/fmi_functions.cpp @@ -318,33 +318,46 @@ fmiStatus fmiSetString (fmiComponent c, const fmiValueReference vr[], size_t nv } + 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 +921,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/tests/cs_slave.cpp b/tests/cs_slave.cpp index c249391..8e13b8d 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,8 @@ 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] == 2) { + seed_ = value[i]; } else { throw std::logic_error("Invalid value reference"); } @@ -34,12 +37,91 @@ 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] == 2) { + value[i] = seed_; } else { throw std::logic_error("Invalid value reference"); } } } + 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, + 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 +178,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..3a11a39 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,139 @@ 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 fmiBoolean enableVal = fmiTrue; + auto rc = fmiSetBoolean(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); + } + + // 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}; + 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 +227,7 @@ int main() assert(rc == fmi2OK); } - // Initialization + // Initialization (FMI 2.0) { const auto rc = fmi2EnterInitializationMode(instance); assert(rc == fmi2OK); @@ -90,7 +251,83 @@ 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 fmi2Boolean enableVal = fmi2True; + auto rc = fmi2SetBoolean(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); + } + + // 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}; + 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 +352,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 +403,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; } 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