From 4788fea3b9d5fac05495f8d24d2b03770cbbc99a Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Wed, 30 Apr 2025 15:17:52 +0200 Subject: [PATCH 1/8] wip --- pythonfmu/pythonfmu-export/src/CMakeLists.txt | 8 +- .../src/cppfmu/cppfmu_common.hpp | 404 ------------------ .../pythonfmu-export/src/cppfmu/cppfmu_cs.cpp | 148 ------- .../pythonfmu-export/src/cppfmu/cppfmu_cs.hpp | 164 ------- .../pythonfmu-export/src/pythonfmu/Logger.hpp | 45 ++ .../src/pythonfmu/PySlaveInstance.cpp | 205 ++++----- .../src/pythonfmu/PySlaveInstance.hpp | 70 +-- .../src/pythonfmu/SlaveInstance.hpp | 85 ++++ .../{cppfmu => pythonfmu}/fmi_functions.cpp | 355 ++++++++------- .../src/pythonfmu/fmu_except.hpp | 18 + 10 files changed, 468 insertions(+), 1034 deletions(-) delete mode 100644 pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_common.hpp delete mode 100644 pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp delete mode 100644 pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp create mode 100644 pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp create mode 100644 pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp rename pythonfmu/pythonfmu-export/src/{cppfmu => pythonfmu}/fmi_functions.cpp (52%) create mode 100644 pythonfmu/pythonfmu-export/src/pythonfmu/fmu_except.hpp diff --git a/pythonfmu/pythonfmu-export/src/CMakeLists.txt b/pythonfmu/pythonfmu-export/src/CMakeLists.txt index 5c612e1e..34ca1942 100644 --- a/pythonfmu/pythonfmu-export/src/CMakeLists.txt +++ b/pythonfmu/pythonfmu-export/src/CMakeLists.txt @@ -1,19 +1,19 @@ set(headers - cppfmu/cppfmu_cs.hpp - cppfmu/cppfmu_common.hpp fmi/fmi2Functions.h fmi/fmi2FunctionTypes.h fmi/fmi2TypesPlatform.h + pythonfmu/fmu_except.hpp + pythonfmu/Logger.hpp + pythonfmu/PySlaveInstance.hpp pythonfmu/PyState.hpp ) set(sources - cppfmu/cppfmu_cs.cpp - cppfmu/fmi_functions.cpp + pythonfmu/fmi_functions.cpp pythonfmu/PySlaveInstance.cpp ) diff --git a/pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_common.hpp b/pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_common.hpp deleted file mode 100644 index 18dd4048..00000000 --- a/pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_common.hpp +++ /dev/null @@ -1,404 +0,0 @@ -/* Copyright 2016-2019, 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_COMMON_HPP -#define CPPFMU_COMMON_HPP - -#include // std::find() -#include // std::size_t -#include // std::function -#include // std::shared_ptr, std::unique_ptr -#include // std::bad_alloc -#include // std::runtime_error -#include // std::basic_string, std::char_traits -#include // std::forward -#include - -extern "C" -{ -#ifdef CPPFMU_USE_FMI_1_0 -# include "fmiFunctions.h" -#else -# include "fmi/fmi2Functions.h" -#endif -} - - -// CPPFMU_NOEXCEPT evaluates to 'noexcept' on compilers that support it. -#if (__cplusplus >= 201103L) || (defined(_MSC_VER) && _MSC_VER >= 1900) -# define CPPFMU_NOEXCEPT noexcept -#else -# define CPPFMU_NOEXCEPT -#endif - - -namespace cppfmu -{ - -// Aliases for FMI types and enums -#ifdef CPPFMU_USE_FMI_1_0 - typedef fmiReal FMIReal; - typedef fmiInteger FMIInteger; - typedef fmiBoolean FMIBoolean; - typedef fmiString FMIString; - typedef fmiCallbackFunctions FMICallbackFunctions; - typedef fmiCallbackAllocateMemory FMICallbackAllocateMemory; - typedef fmiCallbackFreeMemory FMICallbackFreeMemory; - typedef fmiCallbackLogger FMICallbackLogger; - typedef fmiComponent FMIComponent; - typedef fmiComponent FMIComponentEnvironment; - typedef fmiStatus FMIStatus; - typedef fmiValueReference FMIValueReference; - - const FMIBoolean FMIFalse = fmiFalse; - const FMIBoolean FMITrue = fmiTrue; - - const FMIStatus FMIOK = fmiOK; - const FMIStatus FMIWarning = fmiWarning; - const FMIStatus FMIDiscard = fmiDiscard; - const FMIStatus FMIError = fmiError; - const FMIStatus FMIFatal = fmiFatal; - const FMIStatus FMIPending = fmiPending; -#else - typedef fmi2Real FMIReal; - typedef fmi2Integer FMIInteger; - typedef fmi2Boolean FMIBoolean; - typedef fmi2String FMIString; - typedef fmi2CallbackFunctions FMICallbackFunctions; - typedef fmi2CallbackAllocateMemory FMICallbackAllocateMemory; - typedef fmi2CallbackFreeMemory FMICallbackFreeMemory; - typedef fmi2CallbackLogger FMICallbackLogger; - typedef fmi2Component FMIComponent; - typedef fmi2ComponentEnvironment FMIComponentEnvironment; - typedef fmi2Status FMIStatus; - typedef fmi2ValueReference FMIValueReference; - - const FMIBoolean FMIFalse = fmi2False; - const FMIBoolean FMITrue = fmi2True; - - const FMIStatus FMIOK = fmi2OK; - const FMIStatus FMIWarning = fmi2Warning; - const FMIStatus FMIDiscard = fmi2Discard; - const FMIStatus FMIError = fmi2Error; - const FMIStatus FMIFatal = fmi2Fatal; - const FMIStatus FMIPending = fmi2Pending; -#endif - - -// ============================================================================ -// ERROR HANDLING -// ============================================================================ - - -/* Exception class that signals "fatal error", i.e. an error which means that - * not only is the current model instance invalid, but all other instances of - * the same model too. - */ -class FatalError : public std::runtime_error -{ -public: - FatalError(const char* msg) CPPFMU_NOEXCEPT : std::runtime_error{msg} { } -}; - - -// ============================================================================ -// MEMORY MANAGEMENT -// ============================================================================ - - -/* A wrapper class for the FMI memory allocation and deallocation functions. - * Alloc() and Free() simply forward to the functions provided by the - * simulation environment. - */ -class Memory -{ -public: - explicit Memory(const FMICallbackFunctions& callbackFunctions) - : m_alloc{callbackFunctions.allocateMemory} - , m_free{callbackFunctions.freeMemory} - { - } - - // Allocates memory for 'nObj' objects of size 'size'. - void* Alloc(std::size_t nObj, std::size_t size) CPPFMU_NOEXCEPT - { - return m_alloc(nObj, size); - } - - // Frees the memory pointed to by 'ptr'. - void Free(void* ptr) CPPFMU_NOEXCEPT - { - m_free(ptr); - } - - bool operator==(const Memory& rhs) const CPPFMU_NOEXCEPT - { - return m_alloc == rhs.m_alloc && m_free == rhs.m_free; - } - - bool operator!=(const Memory& rhs) const CPPFMU_NOEXCEPT - { - return !operator==(rhs); - } - -private: - FMICallbackAllocateMemory m_alloc; - FMICallbackFreeMemory m_free; -}; - - -/* A class that satisfies the Allocator concept, and which can therefore be - * used to manage memory for the standard C++ containers. - * - * For information about the various member functions, we refer to reference - * material for the Allocator concept, e.g.: - * http://en.cppreference.com/w/cpp/concept/Allocator - */ -template -class Allocator -{ -public: - using value_type = T; - - explicit Allocator(const Memory& memory) : m_memory{memory} { } - - template - Allocator(const Allocator& other) CPPFMU_NOEXCEPT - : m_memory{other.m_memory} - { - } - - T* allocate(std::size_t n) - { - if (n == 0) return nullptr; - if (auto m = m_memory.Alloc(n, sizeof(T))) { - return reinterpret_cast(m); - } else { - throw std::bad_alloc(); - } - } - - void deallocate(T* p, std::size_t n) CPPFMU_NOEXCEPT - { - if (n > 0) { - m_memory.Free(p); - } - } - - bool operator==(const Allocator& rhs) const CPPFMU_NOEXCEPT - { - return m_memory == rhs.m_memory; - } - - bool operator!=(const Allocator& rhs) const CPPFMU_NOEXCEPT - { - return !operator==(rhs); - } - - // ------------------------------------------------------------------------- - // None of the following are required by C++11, yet they are, variously, - // required by GCC and MSVC. - - template - struct rebind { using other = Allocator; }; - -#if defined(__GNUC__) && (__GNUC__ < 5) - using pointer = T*; - using const_pointer = const T*; - using reference = T&; - using const_reference = const T&; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" - Allocator() : m_memory{FMICallbackFunctions{}} { } -#pragma GCC diagnostic pop -#endif - -#ifdef _MSC_VER - template - void construct(U* p, Args&&... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - template - void destroy(U* p) - { - p->~U(); - } -#endif - - // ------------------------------------------------------------------------- - -private: - template - friend class Allocator; - - Memory m_memory; -}; - - -// An alias for a string type that uses cppfmu::Allocator to manage memory. -using String = std::basic_string, Allocator>; - - -// Returns a String whose contents are equal to those of 'string'. -inline String CopyString(const Memory& memory, FMIString string) -{ - return String{string, Allocator{memory}}; -} - - -/* Allocates memory for a single object of type T and runs its constructor, - * in the style of the built-in 'new' operator. Any arguments in 'args' - * are forwarded to the constructor. - */ -template -T* New(const Memory& memory, Args&&... args) -{ - auto alloc = Allocator{memory}; - const auto ptr = std::allocator_traits::allocate(alloc, 1); - try { - std::allocator_traits::construct( - alloc, - ptr, - std::forward(args)...); - } catch (...) { - std::allocator_traits::deallocate(alloc, ptr, 1); - throw; - } - return ptr; -} - - -/* Destroys and deallocates memory for an object of type T, in the style of - * the built-in 'delete' operator. - */ -template -void Delete(const Memory& memory, T* obj) CPPFMU_NOEXCEPT -{ - auto alloc = Allocator{memory}; - std::allocator_traits::destroy(alloc, obj); - std::allocator_traits::deallocate(alloc, obj, 1); -} - - -/* 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(). - */ -template -UniquePtr AllocateUnique(const Memory& memory, Args&&... args) -{ - return UniquePtr{ - New(memory, std::forward(args)...), - [memory] (void* ptr) { Delete(memory, reinterpret_cast(ptr)); }}; -} - - -// ============================================================================ -// 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 model code. All messages are - * forwarded to the logging facilities provided by the simulation environment. - */ -class Logger -{ -public: - struct Settings - { - Settings(const Memory& memory) - : loggedCategories(Allocator{memory}) - { } - - bool debugLoggingEnabled = false; - std::vector> loggedCategories; - }; - - Logger( - FMIComponentEnvironment component, - String instanceName, - FMICallbackFunctions callbackFunctions, - std::shared_ptr settings) - : m_component{component} - , m_instanceName(std::move(instanceName)) - , m_fmiLogger{callbackFunctions.logger} - , m_settings{settings} - { - } - - // Logs a message. - template - void Log( - FMIStatus status, - FMIString category, - FMIString message, - Args&&... args) CPPFMU_NOEXCEPT - { - if (m_settings->loggedCategories.empty() || - detail::CanFind(m_settings->loggedCategories, category)) { - m_fmiLogger( - m_component, - m_instanceName.c_str(), - status, - category, - message, - std::forward(args)...); - } - } - - /* Logs a debug message (if debug logging is enabled by the simulation - * environment). - */ - template - void DebugLog( - FMIStatus status, - FMIString category, - FMIString message, - Args&&... args) CPPFMU_NOEXCEPT - { - if (m_settings->debugLoggingEnabled) { - Log( - status, - category, - message, - std::forward(args)...); - } - } - -private: - const FMIComponentEnvironment m_component; - const String m_instanceName; - const FMICallbackLogger m_fmiLogger; - std::shared_ptr m_settings; -}; - - -} // namespace cppfmu -#endif // header guard diff --git a/pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp b/pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp deleted file mode 100644 index ff2ba4c2..00000000 --- a/pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* Copyright 2016-2019, 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/cppfmu_cs.hpp" - -#include - - -namespace cppfmu -{ - -// ============================================================================= -// SlaveInstance -// ============================================================================= - - -void SlaveInstance::SetupExperiment( - FMIBoolean /*toleranceDefined*/, - FMIReal /*tolerance*/, - FMIReal /*tStart*/, - FMIBoolean /*stopTimeDefined*/, - FMIReal /*tStop*/) -{ - // Do nothing -} - - -void SlaveInstance::EnterInitializationMode() -{ - // Do nothing -} - - -void SlaveInstance::ExitInitializationMode() -{ - // Do nothing -} - - -void SlaveInstance::Terminate() -{ - // Do nothing -} - - -void SlaveInstance::Reset() -{ - // Do nothing -} - - -void SlaveInstance::SetReal( - const FMIValueReference /*vr*/[], - std::size_t nvr, - const FMIReal /*value*/[]) -{ - if (nvr != 0) { - throw std::logic_error("Attempted to set nonexistent variable"); - } -} - - -void SlaveInstance::SetInteger( - const FMIValueReference /*vr*/[], - std::size_t nvr, - const FMIInteger /*value*/[]) -{ - if (nvr != 0) { - throw std::logic_error("Attempted to set nonexistent variable"); - } -} - - -void SlaveInstance::SetBoolean( - const FMIValueReference /*vr*/[], - std::size_t nvr, - const FMIBoolean /*value*/[]) -{ - if (nvr != 0) { - throw std::logic_error("Attempted to set nonexistent variable"); - } -} - - -void SlaveInstance::SetString( - const FMIValueReference /*vr*/[], - std::size_t nvr, - const FMIString /*value*/[]) -{ - if (nvr != 0) { - throw std::logic_error("Attempted to set nonexistent variable"); - } -} - - -void SlaveInstance::GetReal( - const FMIValueReference /*vr*/[], - std::size_t nvr, - FMIReal /*value*/[]) const -{ - if (nvr != 0) { - throw std::logic_error("Attempted to get nonexistent variable"); - } -} - - -void SlaveInstance::GetInteger( - const FMIValueReference /*vr*/[], - std::size_t nvr, - FMIInteger /*value*/[]) const -{ - if (nvr != 0) { - throw std::logic_error("Attempted to get nonexistent variable"); - } -} - - -void SlaveInstance::GetBoolean( - const FMIValueReference /*vr*/[], - std::size_t nvr, - FMIBoolean /*value*/[]) const -{ - if (nvr != 0) { - throw std::logic_error("Attempted to set nonexistent variable"); - } -} - - -void SlaveInstance::GetString( - const FMIValueReference /*vr*/[], - std::size_t nvr, - FMIString /*value*/[]) const -{ - if (nvr != 0) { - throw std::logic_error("Attempted to set nonexistent variable"); - } -} - - -SlaveInstance::~SlaveInstance() CPPFMU_NOEXCEPT -{ - // Do nothing -} - - -} // namespace cppfmu diff --git a/pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp b/pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp deleted file mode 100644 index 1e0ff4cd..00000000 --- a/pythonfmu/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp +++ /dev/null @@ -1,164 +0,0 @@ -/* Copyright 2016-2019, 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_HPP -#define CPPFMU_CS_HPP - -#include "cppfmu_common.hpp" - -#include - -namespace cppfmu -{ - -/* ============================================================================ - * CO-SIMULATION INTERFACE - * ============================================================================ - */ - -/* A base class for 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 2.0 (and, with - * some adaptations, FMI 1.0), so the documentation here is intentionally - * sparse. We refer to the FMI specifications for detailed information. - */ -class SlaveInstance -{ -public: - /* Called from fmi2SetupExperiment() (FMI 2.0) or fmiInitializeSlave() - * (FMI 1.0). - * Does nothing by default. - */ - virtual void SetupExperiment( - FMIBoolean toleranceDefined, - FMIReal tolerance, - FMIReal tStart, - FMIBoolean stopTimeDefined, - FMIReal tStop); - - /* Called from fmi2EnterInitializationMode() (FMI 2.0) or - * fmiInitializeSlave() (FMI 1.0). - * Does nothing by default. - */ - virtual void EnterInitializationMode(); - - /* Called from fmi2ExitInitializationMode() (FMI 2.0) or - * fmiInitializeSlave() (FMI 1.0). - * Does nothing by default. - */ - virtual void ExitInitializationMode(); - - /* Called from fmi2Terminate()/fmiTerminateSlave(). - * Does nothing by default. - */ - virtual void Terminate(); - - /* Called from fmi2Reset()/fmiResetSlave(). - * Does nothing by default. - */ - virtual void Reset(); - - /* Called from fmi2SetXxx()/fmiSetXxx(). - * Throws std::logic_error by default. - */ - virtual void SetReal( - const FMIValueReference vr[], - std::size_t nvr, - const FMIReal value[]); - virtual void SetInteger( - const FMIValueReference vr[], - std::size_t nvr, - const FMIInteger value[]); - virtual void SetBoolean( - const FMIValueReference vr[], - std::size_t nvr, - const FMIBoolean value[]); - virtual void SetString( - const FMIValueReference vr[], - std::size_t nvr, - const FMIString value[]); - - /* Called from fmi2GetXxx()/fmiGetXxx(). - * Throws std::logic_error by default. - */ - virtual void GetReal( - const FMIValueReference vr[], - std::size_t nvr, - FMIReal value[]) const; - virtual void GetInteger( - const FMIValueReference vr[], - std::size_t nvr, - FMIInteger value[]) const; - virtual void GetBoolean( - const FMIValueReference vr[], - std::size_t nvr, - FMIBoolean value[]) const; - virtual void GetString( - const FMIValueReference vr[], - std::size_t nvr, - FMIString value[]) const; - - // Called from fmi2DoStep()/fmiDoStep(). Must be implemented in model code. - virtual bool DoStep( - FMIReal currentCommunicationPoint, - FMIReal communicationStepSize, - FMIBoolean newStep, - FMIReal& endOfStep) = 0; - - virtual void GetFMUstate(fmi2FMUstate& state) = 0; - virtual void SetFMUstate(const fmi2FMUstate& state) = 0; - virtual void FreeFMUstate(fmi2FMUstate& state) = 0; - - virtual size_t SerializedFMUstateSize(const fmi2FMUstate& state) = 0; - virtual void SerializeFMUstate(const fmi2FMUstate& state, fmi2Byte bytes[], size_t size) = 0; - virtual void DeSerializeFMUstate(const fmi2Byte bytes[], size_t size, fmi2FMUstate& state) = 0; - - // The instance is destroyed in fmi2FreeInstance()/fmiFreeSlaveInstance(). - virtual ~SlaveInstance() 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::AllocateUnique() to - * create the slave instance. - * - * Most of its parameters correspond to those of fmi2Instantiate() and - * fmiInstantiateSlave(), except that 'functions' and 'loggingOn' have been - * replaced with more convenient types: - * - * memory = An object which the model code can use for memory management, - * typically in conjunction with cppfmu::Allocator, - * cppfmu::AllocateUnique(), etc. Allocation and deallocation - * requests get forwarded to the simulation environment. - * - * logger = An object which the model code can use to log messages (e.g. - * warnings or debug info). 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 fmuGUID, - cppfmu::FMIString fmuResourceLocation, - cppfmu::FMIString mimeType, - cppfmu::FMIReal timeout, - cppfmu::FMIBoolean visible, - cppfmu::FMIBoolean interactive, - cppfmu::Memory memory, - const cppfmu::Logger& logger); - - -#endif // header guard diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp b/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp new file mode 100644 index 00000000..7840231d --- /dev/null +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp @@ -0,0 +1,45 @@ + +#ifndef PYTHONFMU_LOGGER_HPP +#define PYTHONFMU_LOGGER_HPP + +#include + +#include + +namespace pythonfmu { + + class PyLogger { + + public: + explicit PyLogger(std::string instanceName) + : instanceName_(std::move(instanceName)) {} + + void setDebugLogging(bool flag) { + debugLogging_ = flag; + } + + // Logs a message. + void log(fmi2Status s, const std::string &message) { + log(s, "", message); + } + + void log(fmi2Status s, const std::string& category, const std::string &message) { + if (debugLogging_) { + debugLog(s, category, message); + } + } + + virtual ~PyLogger() = default; + + private: + bool debugLogging_{false}; + + protected: + std::string instanceName_; + + virtual void debugLog(fmi2Status s, const std::string& category, const std::string &message) = 0; + }; + +}// namespace pythonfmu + +#endif//PYTHONFMU_LOGGER_HPP diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp index c0bd1904..441e8cda 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp @@ -1,23 +1,24 @@ -#include "pythonfmu/IPyState.hpp" #include "pythonfmu/PySlaveInstance.hpp" +#include "pythonfmu/IPyState.hpp" +#include "pythonfmu/Logger.hpp" #include "pythonfmu/PyState.hpp" -#include "cppfmu/cppfmu_cs.hpp" - +#include #include #include -#include -#include #include +#include #include #include +#include #include -namespace pythonfmu -{ +using namespace pythonfmu; -inline std::string getline(const std::string& fileName) +namespace +{ +std::string getline(const std::filesystem::path& fileName) { std::string line; std::ifstream infile(fileName); @@ -25,16 +26,17 @@ inline std::string getline(const std::string& fileName) return line; } -PyObject* findClass(const std::string& resources, const std::string& moduleName) { +PyObject* findClass(const std::string& resources, const std::string& moduleName) +{ // Initialize the Python interpreter std::string filename = resources + "/" + moduleName + ".py"; - std::string deepestFile = ""; + std::string deepestFile; int deepestChain = 0; // Read and execute the Python file std::ifstream file; file.open(filename); - + if (!file.is_open()) { return nullptr; } @@ -48,7 +50,7 @@ PyObject* findClass(const std::string& resources, const std::string& moduleName) // Compile python code so classes are added to the namespace PyObject* pyModule = PyImport_ImportModule(moduleName.c_str()); - + if (pyModule == nullptr) { return nullptr; } @@ -56,7 +58,7 @@ PyObject* findClass(const std::string& resources, const std::string& moduleName) PyObject* pLocals = PyDict_New(); PyObject* pCode = Py_CompileString(fileContents.str().c_str(), moduleName.c_str(), Py_file_input); - if (pCode != NULL) { + if (pCode != nullptr) { PyObject* pResult = PyEval_EvalCode(pCode, pGlobals, pLocals); Py_XDECREF(pResult); } else { @@ -71,7 +73,7 @@ PyObject* findClass(const std::string& resources, const std::string& moduleName) } fileContents.clear(); - PyObject* key, * value; + PyObject *key, *value; Py_ssize_t pos = 0; while (PyDict_Next(pLocals, &pos, &key, &value)) { @@ -82,17 +84,17 @@ PyObject* findClass(const std::string& resources, const std::string& moduleName) PyObject* pMroAttribute = PyObject_GetAttrString(value, "__mro__"); - if (pMroAttribute != NULL && PySequence_Check(pMroAttribute)) { - std::regex pattern (" deepestChain && match[1] == "Fmi2Slave") { @@ -117,19 +119,17 @@ PyObject* findClass(const std::string& resources, const std::string& moduleName) return pyClass; } -inline void py_safe_run(const std::function& f) +void py_safe_run(const std::function& f) { PyGILState_STATE gil_state = PyGILState_Ensure(); f(gil_state); PyGILState_Release(gil_state); } -PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, const bool visible, std::shared_ptr pyState) - : pyState_{ std::move(pyState) } - , instanceName_(std::move(instanceName)) - , resources_(std::move(resources)) - , logger_(logger) - , visible_(visible) +} // namespace + +PySlaveInstance::PySlaveInstance(const fmu_data& data) + : data_(data) { py_safe_run([this](PyGILState_STATE gilState) { // Append resources path to python sys path @@ -142,16 +142,16 @@ PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources if (sys_path == nullptr) { handle_py_exception("[ctor] PyObject_GetAttrString", gilState); } - int success = PyList_Insert(sys_path, 0, PyUnicode_FromString(resources_.c_str())); + int success = PyList_Insert(sys_path, 0, PyUnicode_FromString(resourceLocation().c_str())); Py_DECREF(sys_path); if (success != 0) { handle_py_exception("[ctor] PyList_Insert", gilState); } - std::string moduleName = getline(resources_ + "/slavemodule.txt"); + std::string moduleName = getline(resourceLocation() + "/slavemodule.txt"); - pClass_ = findClass(resources_, moduleName); + pClass_ = findClass(resourceLocation(), moduleName); if (pClass_ == nullptr) { handle_py_exception("[ctor] findClass", gilState); } @@ -169,17 +169,16 @@ void PySlaveInstance::clearLogBuffer() const PyObject* categoryField = Py_BuildValue("s", "category"); PyObject* statusField = Py_BuildValue("s", "status"); - if ( pMessages_ != NULL && PyList_Check(pMessages_) ) { - auto size = PyList_Size(pMessages_); + if (pMessages_ != nullptr && PyList_Check(pMessages_)) { + const auto size = PyList_Size(pMessages_); for (auto i = 0; i < size; i++) { PyObject* msg = PyList_GetItem(pMessages_, i); + + const auto msgAttr = PyObject_GetAttr(msg, msgField); + const auto categoryAttr = PyObject_GetAttr(msg, categoryField); + const auto statusAttr = PyObject_GetAttr(msg, statusField); - auto debugAttr = PyObject_GetAttr(msg, debugField); - auto msgAttr = PyObject_GetAttr(msg, msgField); - auto categoryAttr = PyObject_GetAttr(msg, categoryField); - auto statusAttr = PyObject_GetAttr(msg, statusField); - - auto statusValue = static_cast(PyLong_AsLong(statusAttr)); + const auto statusValue = static_cast(PyLong_AsLong(statusAttr)); PyObject* msgValue = PyUnicode_AsEncodedString(msgAttr, "utf-8", nullptr); char* msgStr = PyBytes_AsString(msgValue); @@ -192,11 +191,8 @@ void PySlaveInstance::clearLogBuffer() const logStrBuffer.emplace_back(categoryValue); } - if (PyObject_IsTrue(debugAttr)) { - const_cast(logger_).DebugLog(statusValue, categoryStr, msgStr); - } else { - const_cast(logger_).Log(statusValue, categoryStr, msgStr); - } + + log(statusValue, categoryStr, msgStr); } PyList_SetSlice(pMessages_, 0, size, nullptr); } @@ -213,10 +209,10 @@ void PySlaveInstance::initialize(PyGILState_STATE gilState) PyObject* args = PyTuple_New(0); PyObject* kwargs = Py_BuildValue("{ss,ss,sn,si}", - "instance_name", instanceName_.c_str(), - "resources", resources_.c_str(), - "logger", &logger_, - "visible", visible_); + "instance_name", data_.instanceName.c_str(), + "resources", data_.resourceLocation.c_str(), + "logger", data_.fmiLogger, + "visible", data_.visible); pInstance_ = PyObject_Call(pClass_, args, kwargs); Py_DECREF(args); Py_DECREF(kwargs); @@ -226,18 +222,6 @@ void PySlaveInstance::initialize(PyGILState_STATE gilState) pMessages_ = PyObject_CallMethod(pInstance_, "_get_log_queue", nullptr); } -void PySlaveInstance::SetupExperiment(cppfmu::FMIBoolean, cppfmu::FMIReal, cppfmu::FMIReal startTime, cppfmu::FMIBoolean, cppfmu::FMIReal) -{ - py_safe_run([this, startTime](PyGILState_STATE gilState) { - auto f = PyObject_CallMethod(pInstance_, "setup_experiment", "(d)", startTime); - if (f == nullptr) { - handle_py_exception("[setupExperiment] PyObject_CallMethod", gilState); - } - Py_DECREF(f); - clearLogBuffer(); - }); -} - void PySlaveInstance::EnterInitializationMode() { py_safe_run([this](PyGILState_STATE gilState) { @@ -262,7 +246,7 @@ void PySlaveInstance::ExitInitializationMode() }); } -bool PySlaveInstance::DoStep(cppfmu::FMIReal currentTime, cppfmu::FMIReal stepSize, cppfmu::FMIBoolean, cppfmu::FMIReal& endOfStep) +bool PySlaveInstance::DoStep(fmi2Real currentTime, fmi2Real stepSize, fmi2Boolean, fmi2Real& endOfStep) { bool status; py_safe_run([this, &status, currentTime, stepSize](PyGILState_STATE gilState) { @@ -297,7 +281,7 @@ void PySlaveInstance::Terminate() }); } -void PySlaveInstance::SetReal(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIReal* values) +void PySlaveInstance::SetReal(const cppfmu::FMIValueReference* vr, std::size_t nvr, const fmi2Real* values) { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); @@ -339,7 +323,7 @@ void PySlaveInstance::SetInteger(const cppfmu::FMIValueReference* vr, std::size_ }); } -void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIBoolean* values) +void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const fmi2Boolean* values) { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); @@ -381,7 +365,7 @@ void PySlaveInstance::SetString(const cppfmu::FMIValueReference* vr, std::size_t }); } -void PySlaveInstance::GetReal(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIReal* values) const +void PySlaveInstance::GetReal(const cppfmu::FMIValueReference* vr, std::size_t nvr, fmi2Real* values) const { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); @@ -426,7 +410,7 @@ void PySlaveInstance::GetInteger(const cppfmu::FMIValueReference* vr, std::size_ }); } -void PySlaveInstance::GetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIBoolean* values) const +void PySlaveInstance::GetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, fmi2Boolean* values) const { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); @@ -599,28 +583,25 @@ PySlaveInstance::~PySlaveInstance() }); } -} // namespace pythonfmu - -namespace { - std::mutex pyStateMutex{}; - std::shared_ptr pyState{}; -} - -cppfmu::UniquePtr CppfmuInstantiateSlave( - cppfmu::FMIString instanceName, - cppfmu::FMIString, - cppfmu::FMIString fmuResourceLocation, - cppfmu::FMIString, - cppfmu::FMIReal, - cppfmu::FMIBoolean visible, - cppfmu::FMIBoolean, - cppfmu::Memory memory, - const cppfmu::Logger& logger) +namespace +{ +std::mutex pyStateMutex{}; +std::shared_ptr pyState{}; + +} // namespace + +fmi2Component fmi2Instantiate(fmi2String instanceName, + fmi2Type fmuType, + fmi2String fmuGUID, + fmi2String fmuResourceLocation, + const fmi2CallbackFunctions* functions, + fmi2Boolean /*visible*/, + fmi2Boolean loggingOn) { // Convert URI %20 to space auto resources = std::regex_replace(std::string(fmuResourceLocation), std::regex("%20"), " "); - auto find = resources.find("file://"); + const auto find = resources.find("file://"); if (find != std::string::npos) { #ifdef _MSC_VER @@ -633,8 +614,8 @@ cppfmu::UniquePtr CppfmuInstantiateSlave( { auto const ensurePyStateAlive = [&]() { auto const lock = std::lock_guard{pyStateMutex}; - if (nullptr == pyState) pyState = std::make_shared(); - }; + if (nullptr == pyState) pyState = std::make_shared(); + }; ensurePyStateAlive(); return cppfmu::AllocateUnique( @@ -644,43 +625,43 @@ cppfmu::UniquePtr CppfmuInstantiateSlave( extern "C" { - // The PyState instance owns it's own thread for constructing and destroying the Py* from the same thread. - // Creation of an std::thread increments ref counter of a shared library. So, when the client unloads the library - // the library won't be freed, as std::thread is alive, and the std::thread itself waits for de-initialization request. - // Thus, use DllMain on Windows and __attribute__((destructor)) on Linux for signaling to the PyState about de-initialization. - void finalizePythonInterpreter() - { - pyState = nullptr; - } +// The PyState instance owns it's own thread for constructing and destroying the Py* from the same thread. +// Creation of an std::thread increments ref counter of a shared library. So, when the client unloads the library +// the library won't be freed, as std::thread is alive, and the std::thread itself waits for de-initialization request. +// Thus, use DllMain on Windows and __attribute__((destructor)) on Linux for signaling to the PyState about de-initialization. +void finalizePythonInterpreter() +{ + pyState = nullptr; +} } namespace { #ifdef _WIN32 -#include +# include - BOOL APIENTRY DllMain(HMODULE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved) - { - switch (ul_reason_for_call) { - case DLL_PROCESS_ATTACH: - break; - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - case DLL_PROCESS_DETACH: - finalizePythonInterpreter(); - break; - } - return TRUE; +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + finalizePythonInterpreter(); + break; + } + return TRUE; } #elif defined(__linux__) - __attribute__((destructor)) void onLibraryUnload() - { - finalizePythonInterpreter(); +__attribute__((destructor)) void onLibraryUnload() +{ + finalizePythonInterpreter(); } #else -#error port the code +# error port the code #endif -} +} // namespace diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp index 5c79207e..3fb6f594 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp @@ -1,41 +1,51 @@ -#ifndef PYTHONFMU_SLAVEINSTANCE_HPP -#define PYTHONFMU_SLAVEINSTANCE_HPP +#ifndef PYTHONFMU_PYSLAVEINSTANCE_HPP +#define PYTHONFMU_PYSLAVEINSTANCE_HPP -#include "cppfmu/cppfmu_cs.hpp" #include "pythonfmu/IPyState.hpp" +#include "pythonfmu/Logger.hpp" +#include "pythonfmu/SlaveInstance.hpp" #include +#include #include #include namespace pythonfmu { -class PySlaveInstance : public cppfmu::SlaveInstance +struct fmu_data +{ + PyLogger* fmiLogger{nullptr}; + bool visible{false}; + std::string instanceName; + std::string resourceLocation; + std::shared_ptr pyState; +}; + +class PySlaveInstance : public SlaveInstance { public: - PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, bool visible, std::shared_ptr pyState); + PySlaveInstance(const fmu_data& data); void initialize(PyGILState_STATE gilState); - void SetupExperiment(cppfmu::FMIBoolean toleranceDefined, cppfmu::FMIReal tolerance, cppfmu::FMIReal tStart, cppfmu::FMIBoolean stopTimeDefined, cppfmu::FMIReal tStop) override; void EnterInitializationMode() override; void ExitInitializationMode() override; void Terminate() override; void Reset() override; - bool DoStep(cppfmu::FMIReal currentCommunicationPoint, cppfmu::FMIReal communicationStepSize, cppfmu::FMIBoolean newStep, cppfmu::FMIReal& endOfStep) override; + bool DoStep(fmi2Real currentCommunicationPoint, fmi2Real communicationStepSize, fmi2Boolean newStep, fmi2Real& endOfStep) override; - void SetReal(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIReal* value) override; - void SetInteger(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInteger* value) override; - void SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIBoolean* value) override; - void SetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString const* value) override; + void SetReal(const fmi2ValueReference* vr, std::size_t nvr, const fmi2Real* value) override; + void SetInteger(const fmi2ValueReference* vr, std::size_t nvr, const fmi2Integer* value) override; + void SetBoolean(const fmi2ValueReference* vr, std::size_t nvr, const fmi2Boolean* value) override; + void SetString(const fmi2ValueReference* vr, std::size_t nvr, fmi2String const* value) override; - void GetReal(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIReal* value) const override; - void GetInteger(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInteger* value) const override; - void GetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIBoolean* value) const override; - void GetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString* value) const override; + void GetReal(const fmi2ValueReference* vr, std::size_t nvr, fmi2Real* value) const override; + void GetInteger(const fmi2ValueReference* vr, std::size_t nvr, fmi2Integer* value) const override; + void GetBoolean(const fmi2ValueReference* vr, std::size_t nvr, fmi2Boolean* value) const override; + void GetString(const fmi2ValueReference* vr, std::size_t nvr, fmi2String* value) const override; void GetFMUstate(fmi2FMUstate& state) override; void SetFMUstate(const fmi2FMUstate& state) override; @@ -49,23 +59,35 @@ class PySlaveInstance : public cppfmu::SlaveInstance ~PySlaveInstance() override; +protected: + fmu_data data_; private: - std::shared_ptr pyState_; + PyObject* pClass_; PyObject* pInstance_{}; PyObject* pMessages_{}; - const bool visible_; - const std::string instanceName_; - const std::string resources_; - const cppfmu::Logger& logger_; - mutable std::vector strBuffer; mutable std::vector logStrBuffer; void handle_py_exception(const std::string& what, PyGILState_STATE gilState) const; - inline void clearStrBuffer() const + std::string resourceLocation() const + { + return data_.resourceLocation; + } + + void log(fmi2Status s, const std::string& message) const + { + data_.fmiLogger->log(s, message); + } + + void log(fmi2Status s, const std::string& category, const std::string& message) const + { + data_.fmiLogger->log(s, category, message); + } + + void clearStrBuffer() const { if (!strBuffer.empty()) { for (auto obj : strBuffer) { @@ -75,7 +97,7 @@ class PySlaveInstance : public cppfmu::SlaveInstance } } - inline void clearLogStrBuffer() const + void clearLogStrBuffer() const { if (!logStrBuffer.empty()) { for (auto obj : logStrBuffer) { @@ -85,7 +107,7 @@ class PySlaveInstance : public cppfmu::SlaveInstance } } - inline void cleanPyObject() const + void cleanPyObject() const { clearLogBuffer(); clearLogStrBuffer(); diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp b/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp new file mode 100644 index 00000000..33ba12a7 --- /dev/null +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp @@ -0,0 +1,85 @@ + +#ifndef PYTHONFMU_SLAVEINSTANCE_HPP +#define PYTHONFMU_SLAVEINSTANCE_HPP + +#include +#include + +namespace pythonfmu +{ + +class SlaveInstance +{ +public: + void EnterInitializationMode(double start, std::optional stop, std::optional tolerance) + { + EnterInitializationMode(); + } + + virtual void EnterInitializationMode(); + + virtual void ExitInitializationMode(); + + virtual void Terminate(); + + virtual void Reset(); + + virtual void SetReal( + const fmi2ValueReference vr[], + std::size_t nvr, + const fmi2Real value[]); + virtual void SetInteger( + const fmi2ValueReference vr[], + std::size_t nvr, + const fmi2Integer value[]); + virtual void SetBoolean( + const fmi2ValueReference vr[], + std::size_t nvr, + const fmi2Boolean value[]); + virtual void SetString( + const fmi2ValueReference vr[], + std::size_t nvr, + const fmi2String value[]); + + /* Called from fmi2GetXxx()/fmiGetXxx(). + * Throws std::logic_error by default. + */ + virtual void GetReal( + const fmi2ValueReference vr[], + std::size_t nvr, + fmi2Real value[]) const; + virtual void GetInteger( + const fmi2ValueReference vr[], + std::size_t nvr, + fmi2Integer value[]) const; + virtual void GetBoolean( + const fmi2ValueReference vr[], + std::size_t nvr, + fmi2Boolean value[]) const; + virtual void GetString( + const fmi2ValueReference vr[], + std::size_t nvr, + fmi2String value[]) const; + + // Called from fmi2DoStep()/fmiDoStep(). Must be implemented in model code. + virtual bool DoStep( + fmi2Real currentCommunicationPoint, + fmi2Real communicationStepSize, + fmi2Boolean newStep, + fmi2Real& endOfStep) = 0; + + virtual void GetFMUstate(fmi2FMUstate& state) = 0; + virtual void SetFMUstate(const fmi2FMUstate& state) = 0; + virtual void FreeFMUstate(fmi2FMUstate& state) = 0; + + virtual size_t SerializedFMUstateSize(const fmi2FMUstate& state) = 0; + virtual void SerializeFMUstate(const fmi2FMUstate& state, fmi2Byte bytes[], size_t size) = 0; + virtual void DeSerializeFMUstate(const fmi2Byte bytes[], size_t size, fmi2FMUstate& state) = 0; + + // The instance is destroyed in fmi2FreeInstance()/fmiFreeSlaveInstance(). + virtual ~SlaveInstance(); +}; + +} // namespace pythonfmu + +#endif // PYTHONFMU_SLAVEINSTANCE_HPP diff --git a/pythonfmu/pythonfmu-export/src/cppfmu/fmi_functions.cpp b/pythonfmu/pythonfmu-export/src/pythonfmu/fmi_functions.cpp similarity index 52% rename from pythonfmu/pythonfmu-export/src/cppfmu/fmi_functions.cpp rename to pythonfmu/pythonfmu-export/src/pythonfmu/fmi_functions.cpp index f04d1f5a..5cc7d3b2 100644 --- a/pythonfmu/pythonfmu-export/src/cppfmu/fmi_functions.cpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/fmi_functions.cpp @@ -1,40 +1,58 @@ -/* Copyright 2016-2019, 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/cppfmu_cs.hpp" + +#include "Logger.hpp" +#include "PySlaveInstance.hpp" +#include "fmi/fmi2Functions.h" +#include "fmu_except.hpp" #include #include - +#include +#include +#include namespace { -// A struct that holds all the data for one model instance. -struct Component -{ - Component( - cppfmu::FMIString instanceName, - cppfmu::FMICallbackFunctions callbackFunctions, - cppfmu::FMIBoolean loggingOn) - : memory{callbackFunctions} - , loggerSettings{std::make_shared(memory)} - , logger{callbackFunctions.componentEnvironment, cppfmu::CopyString(memory, instanceName), callbackFunctions, loggerSettings} - , lastSuccessfulTime{std::numeric_limits::quiet_NaN()} + +class fmi2Logger : public pythonfmu::PyLogger +{ + +public: + explicit fmi2Logger(const std::string& instanceName, const fmi2CallbackFunctions* f) + : PyLogger(instanceName) + , f_(f) + { } + +protected: + void debugLog(fmi2Status s, const std::string& category, const std::string& message) override { - loggerSettings->debugLoggingEnabled = (loggingOn == cppfmu::FMITrue); + f_->logger(f_->componentEnvironment, instanceName_.c_str(), + s, category.empty() ? nullptr: category.c_str(), message.c_str()); } - // General - cppfmu::Memory memory; - std::shared_ptr loggerSettings; - cppfmu::Logger logger; +private: + const fmi2CallbackFunctions* f_; +}; - // Co-simulation - cppfmu::UniquePtr slave; - cppfmu::FMIReal lastSuccessfulTime; +// A struct that holds all the data for one model instance. +struct Fmi2Component +{ + + Fmi2Component(std::unique_ptr slave, std::unique_ptr logger) + : lastSuccessfulTime{std::numeric_limits::quiet_NaN()} + , slave(std::move(slave)) + , logger(std::move(logger)) + { } + + double lastSuccessfulTime{0}; + + std::unique_ptr slave; + std::unique_ptr logger; + + double start{0}; + std::optional stop; + std::optional tolerance; }; + } // namespace @@ -67,42 +85,39 @@ fmi2Component fmi2Instantiate( fmi2Boolean visible, fmi2Boolean loggingOn) { - try { - if (fmuType != fmi2CoSimulation) { - throw std::logic_error("Unsupported FMU instance type requested (only co-simulation is supported)"); - } - auto component = cppfmu::AllocateUnique(cppfmu::Memory{*functions}, - instanceName, - *functions, - loggingOn); - component->slave = CppfmuInstantiateSlave( - instanceName, - fmuGUID, - fmuResourceLocation, - "application/x-fmu-sharedlibrary", - 0.0, - visible, - cppfmu::FMIFalse, - component->memory, - component->logger); - return component.release(); - } catch (const cppfmu::FatalError& e) { - functions->logger(functions->componentEnvironment, instanceName, fmi2Fatal, "", e.what()); - return nullptr; - } catch (const std::exception& e) { - functions->logger(functions->componentEnvironment, instanceName, fmi2Error, "", e.what()); - return nullptr; - } +// try { +// if (fmuType != fmi2CoSimulation) { +// throw std::logic_error("Unsupported FMU instance type requested (only co-simulation is supported)"); +// } +// auto component = std::make_unique(cppfmu::Memory{*functions}, +// instanceName, +// *functions, +// loggingOn); +// component->slave = CppfmuInstantiateSlave( +// instanceName, +// fmuGUID, +// fmuResourceLocation, +// "application/x-fmu-sharedlibrary", +// 0.0, +// visible, +// false, +// component->memory, +// component->logger); +// return component.release(); +// } catch (const pythonfmu::fatal_error& e) { +// functions->logger(functions->componentEnvironment, instanceName, fmi2Fatal, "", e.what()); +// return nullptr; +// } catch (const std::exception& e) { +// functions->logger(functions->componentEnvironment, instanceName, fmi2Error, "", e.what()); +// return nullptr; +// } } void fmi2FreeInstance(fmi2Component c) { - const auto component = reinterpret_cast(c); - // The Component object was allocated using cppfmu::AllocateUnique(), - // which uses cppfmu::New() internally, so we use cppfmu::Delete() to - // release it again. - cppfmu::Delete(component->memory, component); + const auto component = reinterpret_cast(c); + delete component; } @@ -112,16 +127,16 @@ fmi2Status fmi2SetDebugLogging( size_t nCategories, const fmi2String categories[]) { - const auto component = reinterpret_cast(c); - - std::vector> newCategories( - cppfmu::Allocator(component->memory)); - for (size_t i = 0; i < nCategories; ++i) { - newCategories.push_back(cppfmu::CopyString(component->memory, categories[i])); - } + const auto component = reinterpret_cast(c); - component->loggerSettings->debugLoggingEnabled = (loggingOn == fmi2True); - component->loggerSettings->loggedCategories.swap(newCategories); +// std::vector> newCategories( +// cppfmu::Allocator(component->memory)); +// for (size_t i = 0; i < nCategories; ++i) { +// newCategories.push_back(cppfmu::CopyString(component->memory, categories[i])); +// } +// +// component->loggerSettings->debugLoggingEnabled = (loggingOn == fmi2True); +// component->loggerSettings->loggedCategories.swap(newCategories); return fmi2OK; } @@ -134,36 +149,28 @@ fmi2Status fmi2SetupExperiment( fmi2Boolean stopTimeDefined, fmi2Real stopTime) { - const auto component = reinterpret_cast(c); - try { - component->slave->SetupExperiment( - toleranceDefined, - tolerance, - startTime, - stopTimeDefined, - stopTime); + const auto component = reinterpret_cast(c); + component->start = startTime; + component->stop = stopTimeDefined ? std::optional(stopTime) : std::nullopt; + component->tolerance = toleranceDefined ? std::optional(tolerance) : std::nullopt; + 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 fmi2EnterInitializationMode(fmi2Component c) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { - component->slave->EnterInitializationMode(); + component->slave->EnterInitializationMode(component->start, + component->stop, + component->tolerance); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -171,15 +178,15 @@ fmi2Status fmi2EnterInitializationMode(fmi2Component c) fmi2Status fmi2ExitInitializationMode(fmi2Component c) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->ExitInitializationMode(); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -187,15 +194,15 @@ fmi2Status fmi2ExitInitializationMode(fmi2Component c) fmi2Status fmi2Terminate(fmi2Component c) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->Terminate(); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -203,15 +210,15 @@ fmi2Status fmi2Terminate(fmi2Component c) fmi2Status fmi2Reset(fmi2Component c) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->Reset(); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -223,15 +230,15 @@ fmi2Status fmi2GetReal( size_t nvr, fmi2Real value[]) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->GetReal(vr, nvr, value); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -242,15 +249,15 @@ fmi2Status fmi2GetInteger( size_t nvr, fmi2Integer value[]) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->GetInteger(vr, nvr, value); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -261,15 +268,15 @@ fmi2Status fmi2GetBoolean( size_t nvr, fmi2Boolean value[]) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->GetBoolean(vr, nvr, value); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -280,15 +287,15 @@ fmi2Status fmi2GetString( size_t nvr, fmi2String value[]) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->GetString(vr, nvr, value); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -300,15 +307,15 @@ fmi2Status fmi2SetReal( size_t nvr, const fmi2Real value[]) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->SetReal(vr, nvr, value); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -319,15 +326,15 @@ fmi2Status fmi2SetInteger( size_t nvr, const fmi2Integer value[]) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->SetInteger(vr, nvr, value); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -338,15 +345,15 @@ fmi2Status fmi2SetBoolean( size_t nvr, const fmi2Boolean value[]) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->SetBoolean(vr, nvr, value); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -357,15 +364,15 @@ fmi2Status fmi2SetString( size_t nvr, const fmi2String value[]) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->SetString(vr, nvr, value); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -375,15 +382,15 @@ fmi2Status fmi2GetFMUstate( fmi2Component c, fmi2FMUstate* state) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->GetFMUstate(*state); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -392,15 +399,15 @@ fmi2Status fmi2SetFMUstate( fmi2Component c, fmi2FMUstate state) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->SetFMUstate(state); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -409,15 +416,15 @@ fmi2Status fmi2FreeFMUstate( fmi2Component c, fmi2FMUstate* state) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->FreeFMUstate(*state); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -427,15 +434,15 @@ fmi2Status fmi2SerializedFMUstateSize( fmi2FMUstate state, size_t* size) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { *size = component->slave->SerializedFMUstateSize(state); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -446,15 +453,15 @@ fmi2Status fmi2SerializeFMUstate( fmi2Byte bytes[], size_t size) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->SerializeFMUstate(state, bytes, size); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -465,15 +472,15 @@ fmi2Status fmi2DeSerializeFMUstate( size_t size, fmi2FMUstate* state) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { component->slave->DeSerializeFMUstate(bytes, size, *state); return fmi2OK; - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } @@ -488,7 +495,7 @@ fmi2Status fmi2GetDirectionalDerivative( const fmi2Real[], fmi2Real[]) { - reinterpret_cast(c)->logger.Log( + reinterpret_cast(c)->logger->log( fmi2Error, "cppfmu", "FMI function not supported: fmi2GetDirectionalDerivative"); @@ -502,7 +509,7 @@ fmi2Status fmi2SetRealInputDerivatives( const fmi2Integer[], const fmi2Real[]) { - reinterpret_cast(c)->logger.Log( + reinterpret_cast(c)->logger->log( fmi2Error, "cppfmu", "FMI function not supported: fmi2SetRealInputDerivatives"); @@ -516,7 +523,7 @@ fmi2Status fmi2GetRealOutputDerivatives( const fmi2Integer[], fmi2Real[]) { - reinterpret_cast(c)->logger.Log( + reinterpret_cast(c)->logger->log( fmi2Error, "cppfmu", "FMI function not supported: fmiGetRealOutputDerivatives"); @@ -529,7 +536,7 @@ fmi2Status fmi2DoStep( fmi2Real communicationStepSize, fmi2Boolean /*noSetFMUStatePriorToCurrentPoint*/) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); try { double endTime = currentCommunicationPoint; const auto ok = component->slave->DoStep( @@ -545,20 +552,19 @@ fmi2Status fmi2DoStep( component->lastSuccessfulTime = endTime; return fmi2Discard; } - } catch (const cppfmu::FatalError& e) { - component->logger.Log(fmi2Fatal, "", e.what()); + } catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; } catch (const std::exception& e) { - component->logger.Log(fmi2Error, "", e.what()); + component->logger->log(fmi2Error, e.what()); return fmi2Error; } } fmi2Status fmi2CancelStep(fmi2Component c) { - reinterpret_cast(c)->logger.Log( + reinterpret_cast(c)->logger->log( fmi2Error, - "cppfmu", "FMI function not supported: fmi2CancelStep"); return fmi2Error; } @@ -570,9 +576,8 @@ fmi2Status fmi2GetStatus( const fmi2StatusKind, fmi2Status*) { - reinterpret_cast(c)->logger.Log( + reinterpret_cast(c)->logger->log( fmi2Error, - "cppfmu", "FMI function not supported: fmi2GetStatus"); return fmi2Error; } @@ -582,14 +587,13 @@ fmi2Status fmi2GetRealStatus( const fmi2StatusKind s, fmi2Real* value) { - const auto component = reinterpret_cast(c); + const auto component = reinterpret_cast(c); if (s == fmi2LastSuccessfulTime) { *value = component->lastSuccessfulTime; return fmi2OK; } else { - component->logger.Log( + component->logger->log( fmi2Error, - "cppfmu", "Invalid status inquiry for fmi2GetRealStatus"); return fmi2Error; } @@ -600,9 +604,8 @@ fmi2Status fmi2GetIntegerStatus( const fmi2StatusKind, fmi2Integer*) { - reinterpret_cast(c)->logger.Log( + reinterpret_cast(c)->logger->log( fmi2Error, - "cppfmu", "FMI function not supported: fmi2GetIntegerStatus"); return fmi2Error; } @@ -612,10 +615,8 @@ fmi2Status fmi2GetBooleanStatus( const fmi2StatusKind, fmi2Boolean*) { - reinterpret_cast(c)->logger.Log( - fmi2Error, - "cppfmu", - "FMI function not supported: fmi2GetBooleanStatus"); + reinterpret_cast(c)->logger->log( + fmi2Error,"FMI function not supported: fmi2GetBooleanStatus"); return fmi2Error; } @@ -624,10 +625,8 @@ fmi2Status fmi2GetStringStatus( const fmi2StatusKind, fmi2String*) { - reinterpret_cast(c)->logger.Log( - fmi2Error, - "cppfmu", - "FMI function not supported: fmi2GetStringStatus"); + reinterpret_cast(c)->logger->log( + fmi2Error,"FMI function not supported: fmi2GetStringStatus"); return fmi2Error; } } diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/fmu_except.hpp b/pythonfmu/pythonfmu-export/src/pythonfmu/fmu_except.hpp new file mode 100644 index 00000000..cd76667e --- /dev/null +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/fmu_except.hpp @@ -0,0 +1,18 @@ + +#ifndef PYTHONFMU_FMU_EXCEPT_HPP +#define PYTHONFMU_FMU_EXCEPT_HPP + +#include +#include + +namespace pythonfmu { + + class fatal_error final : public std::runtime_error { + public: + explicit fatal_error(const std::string &msg) : std::runtime_error(msg) {} + }; + + +}// namespace pythonfmu + +#endif//PYTHONFMU_FMU_EXCEPT_HPP From 7c5c4f8a4b101c850d7a4aa787c07cc844d17401 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Wed, 30 Apr 2025 21:47:24 +0200 Subject: [PATCH 2/8] working version --- pythonfmu/fmi2slave.py | 5 +- pythonfmu/pythonfmu-export/src/CMakeLists.txt | 18 +- .../src/pythonfmu/PySlaveInstance.cpp | 894 +++++++++--------- .../src/pythonfmu/PySlaveInstance.hpp | 123 --- .../src/pythonfmu/SlaveInstance.hpp | 80 +- .../pythonfmu/{fmi_functions.cpp => fmi2.cpp} | 173 ++-- 6 files changed, 617 insertions(+), 676 deletions(-) delete mode 100644 pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp rename pythonfmu/pythonfmu-export/src/pythonfmu/{fmi_functions.cpp => fmi2.cpp} (74%) diff --git a/pythonfmu/fmi2slave.py b/pythonfmu/fmi2slave.py index 1929a3df..27ab2172 100644 --- a/pythonfmu/fmi2slave.py +++ b/pythonfmu/fmi2slave.py @@ -176,10 +176,7 @@ def register_variable(self, var: ScalarVariable, nested: bool = True): if var.setter is None and hasattr(owner, var.local_name) and var.variability != Fmi2Variability.constant: var.setter = lambda v: setattr(owner, var.local_name, v) - def setup_experiment(self, start_time: float): - pass - - def enter_initialization_mode(self): + def enter_initialization_mode(self, start_time: float): pass def exit_initialization_mode(self): diff --git a/pythonfmu/pythonfmu-export/src/CMakeLists.txt b/pythonfmu/pythonfmu-export/src/CMakeLists.txt index 34ca1942..98612dd7 100644 --- a/pythonfmu/pythonfmu-export/src/CMakeLists.txt +++ b/pythonfmu/pythonfmu-export/src/CMakeLists.txt @@ -1,20 +1,20 @@ set(headers - fmi/fmi2Functions.h - fmi/fmi2FunctionTypes.h - fmi/fmi2TypesPlatform.h + "fmi/fmi2Functions.h" + "fmi/fmi2FunctionTypes.h" + "fmi/fmi2TypesPlatform.h" - pythonfmu/fmu_except.hpp - pythonfmu/Logger.hpp + "pythonfmu/fmu_except.hpp" + "pythonfmu/Logger.hpp" - pythonfmu/PySlaveInstance.hpp - pythonfmu/PyState.hpp + "pythonfmu/SlaveInstance.hpp" + "pythonfmu/PyState.hpp" ) set(sources - pythonfmu/fmi_functions.cpp - pythonfmu/PySlaveInstance.cpp + "pythonfmu/fmi2.cpp" + "pythonfmu/PySlaveInstance.cpp" ) add_library(pythonfmu-export ${sources} ${headers}) diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp index 441e8cda..90d7c255 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp @@ -1,14 +1,14 @@ -#include "pythonfmu/PySlaveInstance.hpp" -#include "pythonfmu/IPyState.hpp" +#include "fmu_except.hpp" + #include "pythonfmu/Logger.hpp" #include "pythonfmu/PyState.hpp" +#include "pythonfmu/SlaveInstance.hpp" #include #include #include #include -#include #include #include #include @@ -128,489 +128,530 @@ void py_safe_run(const std::function& f) } // namespace -PySlaveInstance::PySlaveInstance(const fmu_data& data) - : data_(data) +class PySlaveInstance : public SlaveInstance { - py_safe_run([this](PyGILState_STATE gilState) { - // Append resources path to python sys path - PyObject* sys_module = PyImport_ImportModule("sys"); - if (sys_module == nullptr) { - handle_py_exception("[ctor] PyImport_ImportModule", gilState); - } - PyObject* sys_path = PyObject_GetAttrString(sys_module, "path"); - Py_DECREF(sys_module); - if (sys_path == nullptr) { - handle_py_exception("[ctor] PyObject_GetAttrString", gilState); - } - int success = PyList_Insert(sys_path, 0, PyUnicode_FromString(resourceLocation().c_str())); +public: + PySlaveInstance(fmu_data data) + : data_(std::move(data)) + { + py_safe_run([this](PyGILState_STATE gilState) { + // Append resources path to python sys path + PyObject* sys_module = PyImport_ImportModule("sys"); + if (sys_module == nullptr) { + handle_py_exception("[ctor] PyImport_ImportModule", gilState); + } + PyObject* sys_path = PyObject_GetAttrString(sys_module, "path"); + Py_DECREF(sys_module); + if (sys_path == nullptr) { + handle_py_exception("[ctor] PyObject_GetAttrString", gilState); + } + int success = PyList_Insert(sys_path, 0, PyUnicode_FromString(resourceLocation().c_str())); - Py_DECREF(sys_path); - if (success != 0) { - handle_py_exception("[ctor] PyList_Insert", gilState); - } + Py_DECREF(sys_path); + if (success != 0) { + handle_py_exception("[ctor] PyList_Insert", gilState); + } + + std::string moduleName = getline(resourceLocation() + "/slavemodule.txt"); + + pClass_ = findClass(resourceLocation(), moduleName); + if (pClass_ == nullptr) { + handle_py_exception("[ctor] findClass", gilState); + } + + initialize(gilState); + }); + } + + void clearLogBuffer() const + { + clearLogStrBuffer(); + + PyObject* debugField = Py_BuildValue("s", "debug"); + PyObject* msgField = Py_BuildValue("s", "msg"); + PyObject* categoryField = Py_BuildValue("s", "category"); + PyObject* statusField = Py_BuildValue("s", "status"); + + if (pMessages_ != nullptr && PyList_Check(pMessages_)) { + const auto size = PyList_Size(pMessages_); + for (auto i = 0; i < size; i++) { + PyObject* msg = PyList_GetItem(pMessages_, i); + + const auto msgAttr = PyObject_GetAttr(msg, msgField); + const auto categoryAttr = PyObject_GetAttr(msg, categoryField); + const auto statusAttr = PyObject_GetAttr(msg, statusField); + + const auto statusValue = static_cast(PyLong_AsLong(statusAttr)); + + PyObject* msgValue = PyUnicode_AsEncodedString(msgAttr, "utf-8", nullptr); + char* msgStr = PyBytes_AsString(msgValue); + logStrBuffer.emplace_back(msgValue); + + const char* categoryStr = ""; + if (categoryAttr != Py_None) { + PyObject* categoryValue = PyUnicode_AsEncodedString(categoryAttr, "utf-8", nullptr); + categoryStr = PyBytes_AsString(categoryValue); + logStrBuffer.emplace_back(categoryValue); + } - std::string moduleName = getline(resourceLocation() + "/slavemodule.txt"); - pClass_ = findClass(resourceLocation(), moduleName); - if (pClass_ == nullptr) { - handle_py_exception("[ctor] findClass", gilState); + log(statusValue, categoryStr, msgStr); + } + PyList_SetSlice(pMessages_, 0, size, nullptr); } + Py_DECREF(debugField); + Py_DECREF(msgField); + Py_DECREF(categoryField); + Py_DECREF(statusField); + } - initialize(gilState); - }); -} + void initialize(PyGILState_STATE gilState) + { + Py_XDECREF(pInstance_); + Py_XDECREF(pMessages_); + + PyObject* args = PyTuple_New(0); + PyObject* kwargs = Py_BuildValue("{ss,ss,sn,si}", + "instance_name", data_.instanceName.c_str(), + "resources", data_.resourceLocation.c_str(), + "logger", data_.fmiLogger, + "visible", data_.visible); + pInstance_ = PyObject_Call(pClass_, args, kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); + if (pInstance_ == nullptr) { + handle_py_exception("[initialize] PyObject_Call", gilState); + } + pMessages_ = PyObject_CallMethod(pInstance_, "_get_log_queue", nullptr); + } -void PySlaveInstance::clearLogBuffer() const -{ - clearLogStrBuffer(); + void EnterInitializationMode() override + { + py_safe_run([this](PyGILState_STATE gilState) { + auto f = PyObject_CallMethod(pInstance_, "enter_initialization_mode", "(d)", time_); + if (f == nullptr) { + handle_py_exception("[enterInitializationMode] PyObject_CallMethod", gilState); + } + Py_DECREF(f); + clearLogBuffer(); + }); + } - PyObject* debugField = Py_BuildValue("s", "debug"); - PyObject* msgField = Py_BuildValue("s", "msg"); - PyObject* categoryField = Py_BuildValue("s", "category"); - PyObject* statusField = Py_BuildValue("s", "status"); + void ExitInitializationMode() override + { + py_safe_run([this](PyGILState_STATE gilState) { + auto f = PyObject_CallMethod(pInstance_, "exit_initialization_mode", nullptr); + if (f == nullptr) { + handle_py_exception("[exitInitializationMode] PyObject_CallMethod", gilState); + } + Py_DECREF(f); + clearLogBuffer(); + }); + } - if (pMessages_ != nullptr && PyList_Check(pMessages_)) { - const auto size = PyList_Size(pMessages_); - for (auto i = 0; i < size; i++) { - PyObject* msg = PyList_GetItem(pMessages_, i); - - const auto msgAttr = PyObject_GetAttr(msg, msgField); - const auto categoryAttr = PyObject_GetAttr(msg, categoryField); - const auto statusAttr = PyObject_GetAttr(msg, statusField); + bool Step(double currentTime, double stepSize) override + { + bool status; + py_safe_run([this, &status, currentTime, stepSize](PyGILState_STATE gilState) { + auto f = PyObject_CallMethod(pInstance_, "do_step", "(dd)", currentTime, stepSize); + if (f == nullptr) { + handle_py_exception("[doStep] PyObject_CallMethod", gilState); + } + status = static_cast(PyObject_IsTrue(f)); + Py_DECREF(f); + clearLogBuffer(); + }); - const auto statusValue = static_cast(PyLong_AsLong(statusAttr)); + return status; + } - PyObject* msgValue = PyUnicode_AsEncodedString(msgAttr, "utf-8", nullptr); - char* msgStr = PyBytes_AsString(msgValue); - logStrBuffer.emplace_back(msgValue); + void Reset() override + { + py_safe_run([this](PyGILState_STATE gilState) { + initialize(gilState); + }); + } - const char* categoryStr = ""; - if (categoryAttr != Py_None) { - PyObject* categoryValue = PyUnicode_AsEncodedString(categoryAttr, "utf-8", nullptr); - categoryStr = PyBytes_AsString(categoryValue); - logStrBuffer.emplace_back(categoryValue); + void Terminate() override + { + py_safe_run([this](PyGILState_STATE gilState) { + auto f = PyObject_CallMethod(pInstance_, "terminate", nullptr); + if (f == nullptr) { + handle_py_exception("[terminate] PyObject_CallMethod", gilState); } + Py_DECREF(f); + clearLogBuffer(); + }); + } + void SetReal(const fmi2ValueReference* vr, std::size_t nvr, const fmi2Real* values) override + { + py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + PyObject* vrs = PyList_New(nvr); + PyObject* refs = PyList_New(nvr); + for (int i = 0; i < nvr; i++) { + PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + PyList_SetItem(refs, i, Py_BuildValue("d", values[i])); + } - log(statusValue, categoryStr, msgStr); - } - PyList_SetSlice(pMessages_, 0, size, nullptr); + auto f = PyObject_CallMethod(pInstance_, "set_real", "(OO)", vrs, refs); + Py_DECREF(vrs); + Py_DECREF(refs); + if (f == nullptr) { + handle_py_exception("[setReal] PyObject_CallMethod", gilState); + } + Py_DECREF(f); + clearLogBuffer(); + }); } - Py_DECREF(debugField); - Py_DECREF(msgField); - Py_DECREF(categoryField); - Py_DECREF(statusField); -} -void PySlaveInstance::initialize(PyGILState_STATE gilState) -{ - Py_XDECREF(pInstance_); - Py_XDECREF(pMessages_); - - PyObject* args = PyTuple_New(0); - PyObject* kwargs = Py_BuildValue("{ss,ss,sn,si}", - "instance_name", data_.instanceName.c_str(), - "resources", data_.resourceLocation.c_str(), - "logger", data_.fmiLogger, - "visible", data_.visible); - pInstance_ = PyObject_Call(pClass_, args, kwargs); - Py_DECREF(args); - Py_DECREF(kwargs); - if (pInstance_ == nullptr) { - handle_py_exception("[initialize] PyObject_Call", gilState); - } - pMessages_ = PyObject_CallMethod(pInstance_, "_get_log_queue", nullptr); -} + void SetInteger(const fmi2ValueReference* vr, std::size_t nvr, const fmi2Integer* values) override + { + py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + PyObject* vrs = PyList_New(nvr); + PyObject* refs = PyList_New(nvr); + for (int i = 0; i < nvr; i++) { + PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + PyList_SetItem(refs, i, Py_BuildValue("i", values[i])); + } -void PySlaveInstance::EnterInitializationMode() -{ - py_safe_run([this](PyGILState_STATE gilState) { - auto f = PyObject_CallMethod(pInstance_, "enter_initialization_mode", nullptr); - if (f == nullptr) { - handle_py_exception("[enterInitializationMode] PyObject_CallMethod", gilState); - } - Py_DECREF(f); - clearLogBuffer(); - }); -} + auto f = PyObject_CallMethod(pInstance_, "set_integer", "(OO)", vrs, refs); + Py_DECREF(vrs); + Py_DECREF(refs); + if (f == nullptr) { + handle_py_exception("[setInteger] PyObject_CallMethod", gilState); + } + Py_DECREF(f); + clearLogBuffer(); + }); + } -void PySlaveInstance::ExitInitializationMode() -{ - py_safe_run([this](PyGILState_STATE gilState) { - auto f = PyObject_CallMethod(pInstance_, "exit_initialization_mode", nullptr); - if (f == nullptr) { - handle_py_exception("[exitInitializationMode] PyObject_CallMethod", gilState); - } - Py_DECREF(f); - clearLogBuffer(); - }); -} + void SetBoolean(const fmi2ValueReference* vr, std::size_t nvr, const fmi2Boolean* values) override + { + py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + PyObject* vrs = PyList_New(nvr); + PyObject* refs = PyList_New(nvr); + for (int i = 0; i < nvr; i++) { + PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + PyList_SetItem(refs, i, PyBool_FromLong(values[i])); + } -bool PySlaveInstance::DoStep(fmi2Real currentTime, fmi2Real stepSize, fmi2Boolean, fmi2Real& endOfStep) -{ - bool status; - py_safe_run([this, &status, currentTime, stepSize](PyGILState_STATE gilState) { - auto f = PyObject_CallMethod(pInstance_, "do_step", "(dd)", currentTime, stepSize); - if (f == nullptr) { - handle_py_exception("[doStep] PyObject_CallMethod", gilState); - } - status = static_cast(PyObject_IsTrue(f)); - Py_DECREF(f); - clearLogBuffer(); - }); + auto f = PyObject_CallMethod(pInstance_, "set_boolean", "(OO)", vrs, refs); + Py_DECREF(vrs); + Py_DECREF(refs); + if (f == nullptr) { + handle_py_exception("[setBoolean] PyObject_CallMethod", gilState); + } + Py_DECREF(f); + clearLogBuffer(); + }); + } - return status; -} + void SetString(const fmi2ValueReference* vr, std::size_t nvr, fmi2String const* values) override + { + py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + PyObject* vrs = PyList_New(nvr); + PyObject* refs = PyList_New(nvr); + for (int i = 0; i < nvr; i++) { + PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + PyList_SetItem(refs, i, Py_BuildValue("s", values[i])); + } -void PySlaveInstance::Reset() -{ - py_safe_run([this](PyGILState_STATE gilState) { - initialize(gilState); - }); -} + auto f = PyObject_CallMethod(pInstance_, "set_string", "(OO)", vrs, refs); + Py_DECREF(vrs); + Py_DECREF(refs); + if (f == nullptr) { + handle_py_exception("[setString] PyObject_CallMethod", gilState); + } + Py_DECREF(f); + clearLogBuffer(); + }); + } -void PySlaveInstance::Terminate() -{ - py_safe_run([this](PyGILState_STATE gilState) { - auto f = PyObject_CallMethod(pInstance_, "terminate", nullptr); - if (f == nullptr) { - handle_py_exception("[terminate] PyObject_CallMethod", gilState); - } - Py_DECREF(f); - clearLogBuffer(); - }); -} + void GetReal(const fmi2ValueReference* vr, std::size_t nvr, fmi2Real* values) const override + { + py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + PyObject* vrs = PyList_New(nvr); + for (int i = 0; i < nvr; i++) { + PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + } -void PySlaveInstance::SetReal(const cppfmu::FMIValueReference* vr, std::size_t nvr, const fmi2Real* values) -{ - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { - PyObject* vrs = PyList_New(nvr); - PyObject* refs = PyList_New(nvr); - for (int i = 0; i < nvr; i++) { - PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); - PyList_SetItem(refs, i, Py_BuildValue("d", values[i])); - } + auto refs = PyObject_CallMethod(pInstance_, "get_real", "O", vrs); + Py_DECREF(vrs); + if (refs == nullptr) { + handle_py_exception("[getReal] PyObject_CallMethod", gilState); + } - auto f = PyObject_CallMethod(pInstance_, "set_real", "(OO)", vrs, refs); - Py_DECREF(vrs); - Py_DECREF(refs); - if (f == nullptr) { - handle_py_exception("[setReal] PyObject_CallMethod", gilState); - } - Py_DECREF(f); - clearLogBuffer(); - }); -} + for (int i = 0; i < nvr; i++) { + PyObject* value = PyList_GetItem(refs, i); + values[i] = PyFloat_AsDouble(value); + } + Py_DECREF(refs); + clearLogBuffer(); + }); + } -void PySlaveInstance::SetInteger(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInteger* values) -{ - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { - PyObject* vrs = PyList_New(nvr); - PyObject* refs = PyList_New(nvr); - for (int i = 0; i < nvr; i++) { - PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); - PyList_SetItem(refs, i, Py_BuildValue("i", values[i])); - } + void GetInteger(const fmi2ValueReference* vr, std::size_t nvr, fmi2Integer* values) const override + { + py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + PyObject* vrs = PyList_New(nvr); + for (int i = 0; i < nvr; i++) { + PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + } + auto refs = PyObject_CallMethod(pInstance_, "get_integer", "O", vrs); + Py_DECREF(vrs); + if (refs == nullptr) { + handle_py_exception("[getInteger] PyObject_CallMethod", gilState); + } - auto f = PyObject_CallMethod(pInstance_, "set_integer", "(OO)", vrs, refs); - Py_DECREF(vrs); - Py_DECREF(refs); - if (f == nullptr) { - handle_py_exception("[setInteger] PyObject_CallMethod", gilState); - } - Py_DECREF(f); - clearLogBuffer(); - }); -} + for (int i = 0; i < nvr; i++) { + PyObject* value = PyList_GetItem(refs, i); + values[i] = static_cast(PyLong_AsLong(value)); + } + Py_DECREF(refs); + clearLogBuffer(); + }); + } -void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const fmi2Boolean* values) -{ - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { - PyObject* vrs = PyList_New(nvr); - PyObject* refs = PyList_New(nvr); - for (int i = 0; i < nvr; i++) { - PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); - PyList_SetItem(refs, i, PyBool_FromLong(values[i])); - } + void GetBoolean(const fmi2ValueReference* vr, std::size_t nvr, fmi2Boolean* values) const override + { + py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + PyObject* vrs = PyList_New(nvr); + for (int i = 0; i < nvr; i++) { + PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + } + auto refs = PyObject_CallMethod(pInstance_, "get_boolean", "O", vrs); + Py_DECREF(vrs); + if (refs == nullptr) { + handle_py_exception("[getBoolean] PyObject_CallMethod", gilState); + } - auto f = PyObject_CallMethod(pInstance_, "set_boolean", "(OO)", vrs, refs); - Py_DECREF(vrs); - Py_DECREF(refs); - if (f == nullptr) { - handle_py_exception("[setBoolean] PyObject_CallMethod", gilState); - } - Py_DECREF(f); - clearLogBuffer(); - }); -} + for (int i = 0; i < nvr; i++) { + PyObject* value = PyList_GetItem(refs, i); + values[i] = PyObject_IsTrue(value); + } + Py_DECREF(refs); + clearLogBuffer(); + }); + } -void PySlaveInstance::SetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString const* values) -{ - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { - PyObject* vrs = PyList_New(nvr); - PyObject* refs = PyList_New(nvr); - for (int i = 0; i < nvr; i++) { - PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); - PyList_SetItem(refs, i, Py_BuildValue("s", values[i])); - } + void GetString(const fmi2ValueReference* vr, std::size_t nvr, fmi2String* values) const override + { + py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + clearStrBuffer(); + PyObject* vrs = PyList_New(nvr); + for (int i = 0; i < nvr; i++) { + PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + } + auto refs = PyObject_CallMethod(pInstance_, "get_string", "O", vrs); + Py_DECREF(vrs); + if (refs == nullptr) { + handle_py_exception("[getString] PyObject_CallMethod", gilState); + } - auto f = PyObject_CallMethod(pInstance_, "set_string", "(OO)", vrs, refs); - Py_DECREF(vrs); - Py_DECREF(refs); - if (f == nullptr) { - handle_py_exception("[setString] PyObject_CallMethod", gilState); - } - Py_DECREF(f); - clearLogBuffer(); - }); -} + for (int i = 0; i < nvr; i++) { + PyObject* value = PyUnicode_AsEncodedString(PyList_GetItem(refs, i), "utf-8", nullptr); + values[i] = PyBytes_AsString(value); + strBuffer.emplace_back(value); + } + Py_DECREF(refs); + clearLogBuffer(); + }); + } -void PySlaveInstance::GetReal(const cppfmu::FMIValueReference* vr, std::size_t nvr, fmi2Real* values) const -{ - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { - PyObject* vrs = PyList_New(nvr); - for (int i = 0; i < nvr; i++) { - PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); - } + void GetFMUstate(fmi2FMUstate& state) override + { + py_safe_run([this, &state](PyGILState_STATE gilState) { + auto f = PyObject_CallMethod(pInstance_, "_get_fmu_state", nullptr); + if (f == nullptr) { + handle_py_exception("[_get_fmu_state] PyObject_CallMethod", gilState); + } + state = reinterpret_cast(f); + clearLogBuffer(); + }); + } - auto refs = PyObject_CallMethod(pInstance_, "get_real", "O", vrs); - Py_DECREF(vrs); - if (refs == nullptr) { - handle_py_exception("[getReal] PyObject_CallMethod", gilState); - } + void SetFMUstate(const fmi2FMUstate& state) override + { + py_safe_run([this, &state](PyGILState_STATE gilState) { + auto pyState = reinterpret_cast(state); + auto f = PyObject_CallMethod(pInstance_, "_set_fmu_state", "(O)", pyState); + if (f == nullptr) { + handle_py_exception("[_set_fmu_state] PyObject_CallMethod", gilState); + } + clearLogBuffer(); + }); + } - for (int i = 0; i < nvr; i++) { - PyObject* value = PyList_GetItem(refs, i); - values[i] = PyFloat_AsDouble(value); - } - Py_DECREF(refs); - clearLogBuffer(); - }); -} + void FreeFMUstate(fmi2FMUstate& state) override + { + py_safe_run([this, &state](PyGILState_STATE gilState) { + auto f = reinterpret_cast(state); + Py_XDECREF(f); + }); + } -void PySlaveInstance::GetInteger(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInteger* values) const -{ - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { - PyObject* vrs = PyList_New(nvr); - for (int i = 0; i < nvr; i++) { - PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); - } - auto refs = PyObject_CallMethod(pInstance_, "get_integer", "O", vrs); - Py_DECREF(vrs); - if (refs == nullptr) { - handle_py_exception("[getInteger] PyObject_CallMethod", gilState); - } + size_t SerializedFMUstateSize(const fmi2FMUstate& state) override + { + size_t size; + py_safe_run([this, &state, &size](PyGILState_STATE gilState) { + auto pyState = reinterpret_cast(state); + PyObject* pyStateBytes = PyObject_CallMethod(pClass_, "_fmu_state_to_bytes", "(O)", pyState); + if (pyStateBytes == nullptr) { + handle_py_exception("[SerializedFMUstateSize] PyObject_CallMethod", gilState); + } + size = PyBytes_Size(pyStateBytes); + Py_DECREF(pyStateBytes); + clearLogBuffer(); + }); + return size; + } - for (int i = 0; i < nvr; i++) { - PyObject* value = PyList_GetItem(refs, i); - values[i] = static_cast(PyLong_AsLong(value)); - } - Py_DECREF(refs); - clearLogBuffer(); - }); -} + void SerializeFMUstate(const fmi2FMUstate& state, fmi2Byte* bytes, size_t size) override + { + py_safe_run([this, &state, &bytes, size](PyGILState_STATE gilState) { + auto pyState = reinterpret_cast(state); + PyObject* pyStateBytes = PyObject_CallMethod(pClass_, "_fmu_state_to_bytes", "(O)", pyState); + if (pyStateBytes == nullptr) { + handle_py_exception("[SerializeFMUstate] PyObject_CallMethod", gilState); + } + char* c = PyBytes_AsString(pyStateBytes); + if (c == nullptr) { + handle_py_exception("[SerializeFMUstate] PyBytes_AsString", gilState); + } + for (int i = 0; i < size; i++) { + bytes[i] = c[i]; + } + Py_DECREF(pyStateBytes); + clearLogBuffer(); + }); + } -void PySlaveInstance::GetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, fmi2Boolean* values) const -{ - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { - PyObject* vrs = PyList_New(nvr); - for (int i = 0; i < nvr; i++) { - PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); - } - auto refs = PyObject_CallMethod(pInstance_, "get_boolean", "O", vrs); - Py_DECREF(vrs); - if (refs == nullptr) { - handle_py_exception("[getBoolean] PyObject_CallMethod", gilState); - } + void DeSerializeFMUstate(const fmi2Byte bytes[], size_t size, fmi2FMUstate& state) + { + py_safe_run([this, &bytes, size, &state](PyGILState_STATE gilState) { + PyObject* pyStateBytes = PyBytes_FromStringAndSize(bytes, size); + if (pyStateBytes == nullptr) { + handle_py_exception("[DeSerializeFMUstate] PyBytes_FromStringAndSize", gilState); + } + PyObject* pyState = PyObject_CallMethod(pClass_, "_fmu_state_from_bytes", "(O)", pyStateBytes); + if (pyState == nullptr) { + handle_py_exception("[DeSerializeFMUstate] PyObject_CallMethod", gilState); + } + state = reinterpret_cast(pyState); + Py_DECREF(pyStateBytes); + clearLogBuffer(); + }); + } - for (int i = 0; i < nvr; i++) { - PyObject* value = PyList_GetItem(refs, i); - values[i] = PyObject_IsTrue(value); - } - Py_DECREF(refs); - clearLogBuffer(); - }); -} + ~PySlaveInstance() override + { + py_safe_run([this](PyGILState_STATE) { + cleanPyObject(); + }); + } -void PySlaveInstance::GetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString* values) const -{ - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { - clearStrBuffer(); - PyObject* vrs = PyList_New(nvr); - for (int i = 0; i < nvr; i++) { - PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); - } - auto refs = PyObject_CallMethod(pInstance_, "get_string", "O", vrs); - Py_DECREF(vrs); - if (refs == nullptr) { - handle_py_exception("[getString] PyObject_CallMethod", gilState); - } +private: + fmu_data data_; - for (int i = 0; i < nvr; i++) { - PyObject* value = PyUnicode_AsEncodedString(PyList_GetItem(refs, i), "utf-8", nullptr); - values[i] = PyBytes_AsString(value); - strBuffer.emplace_back(value); - } - Py_DECREF(refs); - clearLogBuffer(); - }); -} + PyObject* pClass_; + PyObject* pInstance_{}; + PyObject* pMessages_{}; -void PySlaveInstance::GetFMUstate(fmi2FMUstate& state) -{ - py_safe_run([this, &state](PyGILState_STATE gilState) { - auto f = PyObject_CallMethod(pInstance_, "_get_fmu_state", nullptr); - if (f == nullptr) { - handle_py_exception("[_get_fmu_state] PyObject_CallMethod", gilState); - } - state = reinterpret_cast(f); - clearLogBuffer(); - }); -} + mutable std::vector strBuffer; + mutable std::vector logStrBuffer; -void PySlaveInstance::SetFMUstate(const fmi2FMUstate& state) -{ - py_safe_run([this, &state](PyGILState_STATE gilState) { - auto pyState = reinterpret_cast(state); - auto f = PyObject_CallMethod(pInstance_, "_set_fmu_state", "(O)", pyState); - if (f == nullptr) { - handle_py_exception("[_set_fmu_state] PyObject_CallMethod", gilState); - } - clearLogBuffer(); - }); -} + std::string resourceLocation() const + { + return data_.resourceLocation; + } -void PySlaveInstance::FreeFMUstate(fmi2FMUstate& state) -{ - py_safe_run([this, &state](PyGILState_STATE gilState) { - auto f = reinterpret_cast(state); - Py_XDECREF(f); - }); -} + void log(fmi2Status s, const std::string& message) const + { + data_.fmiLogger->log(s, message); + } -size_t PySlaveInstance::SerializedFMUstateSize(const fmi2FMUstate& state) -{ - size_t size; - py_safe_run([this, &state, &size](PyGILState_STATE gilState) { - auto pyState = reinterpret_cast(state); - PyObject* pyStateBytes = PyObject_CallMethod(pClass_, "_fmu_state_to_bytes", "(O)", pyState); - if (pyStateBytes == nullptr) { - handle_py_exception("[SerializedFMUstateSize] PyObject_CallMethod", gilState); - } - size = PyBytes_Size(pyStateBytes); - Py_DECREF(pyStateBytes); - clearLogBuffer(); - }); - return size; -} + void log(fmi2Status s, const std::string& category, const std::string& message) const + { + data_.fmiLogger->log(s, category, message); + } -void PySlaveInstance::SerializeFMUstate(const fmi2FMUstate& state, fmi2Byte* bytes, size_t size) -{ - py_safe_run([this, &state, &bytes, size](PyGILState_STATE gilState) { - auto pyState = reinterpret_cast(state); - PyObject* pyStateBytes = PyObject_CallMethod(pClass_, "_fmu_state_to_bytes", "(O)", pyState); - if (pyStateBytes == nullptr) { - handle_py_exception("[SerializeFMUstate] PyObject_CallMethod", gilState); - } - char* c = PyBytes_AsString(pyStateBytes); - if (c == nullptr) { - handle_py_exception("[SerializeFMUstate] PyBytes_AsString", gilState); - } - for (int i = 0; i < size; i++) { - bytes[i] = c[i]; + void clearStrBuffer() const + { + if (!strBuffer.empty()) { + for (auto obj : strBuffer) { + Py_DECREF(obj); + } + strBuffer.clear(); } - Py_DECREF(pyStateBytes); - clearLogBuffer(); - }); -} + } -void PySlaveInstance::DeSerializeFMUstate(const fmi2Byte bytes[], size_t size, fmi2FMUstate& state) -{ - py_safe_run([this, &bytes, size, &state](PyGILState_STATE gilState) { - PyObject* pyStateBytes = PyBytes_FromStringAndSize(bytes, size); - if (pyStateBytes == nullptr) { - handle_py_exception("[DeSerializeFMUstate] PyBytes_FromStringAndSize", gilState); - } - PyObject* pyState = PyObject_CallMethod(pClass_, "_fmu_state_from_bytes", "(O)", pyStateBytes); - if (pyState == nullptr) { - handle_py_exception("[DeSerializeFMUstate] PyObject_CallMethod", gilState); + void clearLogStrBuffer() const + { + if (!logStrBuffer.empty()) { + for (auto obj : logStrBuffer) { + Py_DECREF(obj); + } + logStrBuffer.clear(); } - state = reinterpret_cast(pyState); - Py_DECREF(pyStateBytes); + } + + void cleanPyObject() const + { clearLogBuffer(); - }); -} + clearLogStrBuffer(); + clearStrBuffer(); + Py_XDECREF(pClass_); + Py_XDECREF(pInstance_); + Py_XDECREF(pMessages_); + } -void PySlaveInstance::handle_py_exception(const std::string& what, PyGILState_STATE gilState) const -{ - auto err = PyErr_Occurred(); - if (err != nullptr) { - cleanPyObject(); - - PyObject *pExcType, *pExcValue, *pExcTraceback; - PyErr_Fetch(&pExcType, &pExcValue, &pExcTraceback); - - std::ostringstream oss; - oss << "Fatal py exception encountered: "; - oss << what << "\n"; - if (pExcValue != nullptr) { - PyObject* pRepr = PyObject_Repr(pExcValue); - PyObject* pyStr = PyUnicode_AsEncodedString(pRepr, "utf-8", nullptr); - oss << PyBytes_AsString(pyStr); - Py_DECREF(pyStr); - Py_DECREF(pRepr); - } else { - oss << "unknown error"; - } - PyErr_Clear(); + void handle_py_exception(const std::string& what, PyGILState_STATE gilState) const + { + const auto err = PyErr_Occurred(); + if (err != nullptr) { + cleanPyObject(); + + PyObject *pExcType, *pExcValue, *pExcTraceback; + PyErr_Fetch(&pExcType, &pExcValue, &pExcTraceback); + + std::ostringstream oss; + oss << "Fatal py exception encountered: "; + oss << what << "\n"; + if (pExcValue != nullptr) { + PyObject* pRepr = PyObject_Repr(pExcValue); + PyObject* pyStr = PyUnicode_AsEncodedString(pRepr, "utf-8", nullptr); + oss << PyBytes_AsString(pyStr); + Py_DECREF(pyStr); + Py_DECREF(pRepr); + } else { + oss << "unknown error"; + } - Py_XDECREF(pExcType); - Py_XDECREF(pExcValue); - Py_XDECREF(pExcTraceback); + PyErr_Clear(); - PyGILState_Release(gilState); + Py_XDECREF(pExcType); + Py_XDECREF(pExcValue); + Py_XDECREF(pExcTraceback); - auto msg = oss.str(); - throw cppfmu::FatalError(msg.c_str()); - } -} + PyGILState_Release(gilState); -PySlaveInstance::~PySlaveInstance() -{ - py_safe_run([this](PyGILState_STATE gilState) { - cleanPyObject(); - }); -} + throw fatal_error(oss.str()); + } + } +}; namespace { + std::mutex pyStateMutex{}; -std::shared_ptr pyState{}; +std::shared_ptr pyState{}; } // namespace -fmi2Component fmi2Instantiate(fmi2String instanceName, - fmi2Type fmuType, - fmi2String fmuGUID, - fmi2String fmuResourceLocation, - const fmi2CallbackFunctions* functions, - fmi2Boolean /*visible*/, - fmi2Boolean loggingOn) +std::unique_ptr pythonfmu::createInstance(fmu_data data) { - - // Convert URI %20 to space - auto resources = std::regex_replace(std::string(fmuResourceLocation), std::regex("%20"), " "); - const auto find = resources.find("file://"); - - if (find != std::string::npos) { -#ifdef _MSC_VER - resources.replace(find, 8, ""); -#else - resources.replace(find, 7, ""); -#endif - } - { auto const ensurePyStateAlive = [&]() { auto const lock = std::lock_guard{pyStateMutex}; @@ -618,11 +659,13 @@ fmi2Component fmi2Instantiate(fmi2String instanceName, }; ensurePyStateAlive(); - return cppfmu::AllocateUnique( - memory, instanceName, resources, logger, visible, pyState); + auto c = std::make_unique(data); + data.pyState = pyState; + return c; } } + extern "C" { // The PyState instance owns it's own thread for constructing and destroying the Py* from the same thread. @@ -637,6 +680,7 @@ void finalizePythonInterpreter() namespace { + #ifdef _WIN32 # include diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp deleted file mode 100644 index 3fb6f594..00000000 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp +++ /dev/null @@ -1,123 +0,0 @@ - -#ifndef PYTHONFMU_PYSLAVEINSTANCE_HPP -#define PYTHONFMU_PYSLAVEINSTANCE_HPP - -#include "pythonfmu/IPyState.hpp" -#include "pythonfmu/Logger.hpp" -#include "pythonfmu/SlaveInstance.hpp" - -#include -#include -#include -#include - -namespace pythonfmu -{ - -struct fmu_data -{ - PyLogger* fmiLogger{nullptr}; - bool visible{false}; - std::string instanceName; - std::string resourceLocation; - std::shared_ptr pyState; -}; - -class PySlaveInstance : public SlaveInstance -{ - -public: - PySlaveInstance(const fmu_data& data); - - void initialize(PyGILState_STATE gilState); - - void EnterInitializationMode() override; - void ExitInitializationMode() override; - void Terminate() override; - void Reset() override; - bool DoStep(fmi2Real currentCommunicationPoint, fmi2Real communicationStepSize, fmi2Boolean newStep, fmi2Real& endOfStep) override; - - void SetReal(const fmi2ValueReference* vr, std::size_t nvr, const fmi2Real* value) override; - void SetInteger(const fmi2ValueReference* vr, std::size_t nvr, const fmi2Integer* value) override; - void SetBoolean(const fmi2ValueReference* vr, std::size_t nvr, const fmi2Boolean* value) override; - void SetString(const fmi2ValueReference* vr, std::size_t nvr, fmi2String const* value) override; - - void GetReal(const fmi2ValueReference* vr, std::size_t nvr, fmi2Real* value) const override; - void GetInteger(const fmi2ValueReference* vr, std::size_t nvr, fmi2Integer* value) const override; - void GetBoolean(const fmi2ValueReference* vr, std::size_t nvr, fmi2Boolean* value) const override; - void GetString(const fmi2ValueReference* vr, std::size_t nvr, fmi2String* value) const override; - - void GetFMUstate(fmi2FMUstate& state) override; - void SetFMUstate(const fmi2FMUstate& state) override; - void FreeFMUstate(fmi2FMUstate& state) override; - - size_t SerializedFMUstateSize(const fmi2FMUstate& state) override; - void SerializeFMUstate(const fmi2FMUstate& state, fmi2Byte bytes[], size_t size) override; - void DeSerializeFMUstate(const fmi2Byte bytes[], size_t size, fmi2FMUstate& state) override; - - void clearLogBuffer() const; - - ~PySlaveInstance() override; - -protected: - fmu_data data_; -private: - - PyObject* pClass_; - PyObject* pInstance_{}; - PyObject* pMessages_{}; - - mutable std::vector strBuffer; - mutable std::vector logStrBuffer; - - void handle_py_exception(const std::string& what, PyGILState_STATE gilState) const; - - std::string resourceLocation() const - { - return data_.resourceLocation; - } - - void log(fmi2Status s, const std::string& message) const - { - data_.fmiLogger->log(s, message); - } - - void log(fmi2Status s, const std::string& category, const std::string& message) const - { - data_.fmiLogger->log(s, category, message); - } - - void clearStrBuffer() const - { - if (!strBuffer.empty()) { - for (auto obj : strBuffer) { - Py_DECREF(obj); - } - strBuffer.clear(); - } - } - - void clearLogStrBuffer() const - { - if (!logStrBuffer.empty()) { - for (auto obj : logStrBuffer) { - Py_DECREF(obj); - } - logStrBuffer.clear(); - } - } - - void cleanPyObject() const - { - clearLogBuffer(); - clearLogStrBuffer(); - clearStrBuffer(); - Py_XDECREF(pClass_); - Py_XDECREF(pInstance_); - Py_XDECREF(pMessages_); - } -}; - -} // namespace pythonfmu - -#endif diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp b/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp index 33ba12a7..db5a0f28 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp @@ -2,71 +2,90 @@ #ifndef PYTHONFMU_SLAVEINSTANCE_HPP #define PYTHONFMU_SLAVEINSTANCE_HPP -#include +#include "IPyState.hpp" +#include "Logger.hpp" + +#include #include namespace pythonfmu { +struct fmu_data +{ + PyLogger* fmiLogger{nullptr}; + bool visible{false}; + std::string instanceName; + std::string resourceLocation; + std::shared_ptr pyState; +}; + class SlaveInstance { public: void EnterInitializationMode(double start, std::optional stop, std::optional tolerance) { + time_ = start; + stop_ = stop; + tolerance_ = tolerance; + EnterInitializationMode(); } - virtual void EnterInitializationMode(); + virtual void EnterInitializationMode() = 0; - virtual void ExitInitializationMode(); + virtual void ExitInitializationMode() = 0; - virtual void Terminate(); + virtual void Terminate() = 0; - virtual void Reset(); + virtual void Reset() = 0; virtual void SetReal( - const fmi2ValueReference vr[], + const unsigned int vr[], std::size_t nvr, - const fmi2Real value[]); + const fmi2Real value[]) = 0; virtual void SetInteger( - const fmi2ValueReference vr[], + const unsigned int vr[], std::size_t nvr, - const fmi2Integer value[]); + const int value[]) = 0; virtual void SetBoolean( - const fmi2ValueReference vr[], + const unsigned int vr[], std::size_t nvr, - const fmi2Boolean value[]); + const int value[]) = 0; virtual void SetString( - const fmi2ValueReference vr[], + const unsigned int vr[], std::size_t nvr, - const fmi2String value[]); + const char* const value[]) = 0; /* Called from fmi2GetXxx()/fmiGetXxx(). * Throws std::logic_error by default. */ virtual void GetReal( - const fmi2ValueReference vr[], + const unsigned int vr[], std::size_t nvr, - fmi2Real value[]) const; + fmi2Real value[]) const = 0; virtual void GetInteger( - const fmi2ValueReference vr[], + const unsigned int vr[], std::size_t nvr, - fmi2Integer value[]) const; + int value[]) const = 0; virtual void GetBoolean( - const fmi2ValueReference vr[], + const unsigned int vr[], std::size_t nvr, - fmi2Boolean value[]) const; + int value[]) const = 0; virtual void GetString( - const fmi2ValueReference vr[], + const unsigned int vr[], std::size_t nvr, - fmi2String value[]) const; + const char* value[]) const = 0; // Called from fmi2DoStep()/fmiDoStep(). Must be implemented in model code. - virtual bool DoStep( - fmi2Real currentCommunicationPoint, - fmi2Real communicationStepSize, - fmi2Boolean newStep, - fmi2Real& endOfStep) = 0; + bool DoStep( + double currentCommunicationPoint, + double communicationStepSize) + { + return Step(currentCommunicationPoint, communicationStepSize); + } + + virtual bool Step(double currentTime, double dt) = 0; virtual void GetFMUstate(fmi2FMUstate& state) = 0; virtual void SetFMUstate(const fmi2FMUstate& state) = 0; @@ -77,9 +96,16 @@ class SlaveInstance virtual void DeSerializeFMUstate(const fmi2Byte bytes[], size_t size, fmi2FMUstate& state) = 0; // The instance is destroyed in fmi2FreeInstance()/fmiFreeSlaveInstance(). - virtual ~SlaveInstance(); + virtual ~SlaveInstance() = default; + +protected: + double time_{0}; + std::optional stop_; + std::optional tolerance_; }; +std::unique_ptr createInstance(fmu_data data); + } // namespace pythonfmu #endif // PYTHONFMU_SLAVEINSTANCE_HPP diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/fmi_functions.cpp b/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp similarity index 74% rename from pythonfmu/pythonfmu-export/src/pythonfmu/fmi_functions.cpp rename to pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp index 5cc7d3b2..affb3af6 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/fmi_functions.cpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp @@ -1,6 +1,6 @@ #include "Logger.hpp" -#include "PySlaveInstance.hpp" +#include "SlaveInstance.hpp" #include "fmi/fmi2Functions.h" #include "fmu_except.hpp" @@ -8,16 +8,16 @@ #include #include #include -#include +#include namespace { -class fmi2Logger : public pythonfmu::PyLogger +class Fmi2Logger : public pythonfmu::PyLogger { public: - explicit fmi2Logger(const std::string& instanceName, const fmi2CallbackFunctions* f) + explicit Fmi2Logger(const std::string& instanceName, const fmi2CallbackFunctions* f) : PyLogger(instanceName) , f_(f) { } @@ -26,7 +26,7 @@ class fmi2Logger : public pythonfmu::PyLogger void debugLog(fmi2Status s, const std::string& category, const std::string& message) override { f_->logger(f_->componentEnvironment, instanceName_.c_str(), - s, category.empty() ? nullptr: category.c_str(), message.c_str()); + s, category.empty() ? nullptr : category.c_str(), message.c_str()); } private: @@ -37,16 +37,16 @@ class fmi2Logger : public pythonfmu::PyLogger struct Fmi2Component { - Fmi2Component(std::unique_ptr slave, std::unique_ptr logger) + Fmi2Component(std::unique_ptr slave, std::unique_ptr logger) : lastSuccessfulTime{std::numeric_limits::quiet_NaN()} - , slave(std::move(slave)) - , logger(std::move(logger)) + , slave(std::move(slave)) + , logger(std::move(logger)) { } double lastSuccessfulTime{0}; - std::unique_ptr slave; - std::unique_ptr logger; + std::unique_ptr slave; + std::unique_ptr logger; double start{0}; std::optional stop; @@ -76,8 +76,7 @@ const char* fmi2GetVersion() } -fmi2Component fmi2Instantiate( - fmi2String instanceName, +fmi2Component fmi2Instantiate(fmi2String instanceName, fmi2Type fmuType, fmi2String fmuGUID, fmi2String fmuResourceLocation, @@ -85,39 +84,46 @@ fmi2Component fmi2Instantiate( fmi2Boolean visible, fmi2Boolean loggingOn) { -// try { -// if (fmuType != fmi2CoSimulation) { -// throw std::logic_error("Unsupported FMU instance type requested (only co-simulation is supported)"); -// } -// auto component = std::make_unique(cppfmu::Memory{*functions}, -// instanceName, -// *functions, -// loggingOn); -// component->slave = CppfmuInstantiateSlave( -// instanceName, -// fmuGUID, -// fmuResourceLocation, -// "application/x-fmu-sharedlibrary", -// 0.0, -// visible, -// false, -// component->memory, -// component->logger); -// return component.release(); -// } catch (const pythonfmu::fatal_error& e) { -// functions->logger(functions->componentEnvironment, instanceName, fmi2Fatal, "", e.what()); -// return nullptr; -// } catch (const std::exception& e) { -// functions->logger(functions->componentEnvironment, instanceName, fmi2Error, "", e.what()); -// return nullptr; -// } + + // Convert URI %20 to space + auto resources = std::regex_replace(std::string(fmuResourceLocation), std::regex("%20"), " "); + const auto find = resources.find("file://"); + + if (find != std::string::npos) { +#ifdef _MSC_VER + resources.replace(find, 8, ""); +#else + resources.replace(find, 7, ""); +#endif + } + + auto logger = std::make_unique(instanceName, functions); + logger->setDebugLogging(loggingOn); + + try { + + auto instance = pythonfmu::createInstance( + {logger.get(), + visible == fmi2True, + instanceName, + resources, + nullptr}); + + auto component = std::make_unique(std::move(instance), std::move(logger)); + return component.release(); + } catch (const std::exception& e) { + logger->log(fmi2Fatal, e.what()); + return nullptr; + } } void fmi2FreeInstance(fmi2Component c) { - const auto component = reinterpret_cast(c); - delete component; + if (c) { + const auto component = static_cast(c); + delete component; + } } @@ -127,16 +133,9 @@ fmi2Status fmi2SetDebugLogging( size_t nCategories, const fmi2String categories[]) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); + component->logger->setDebugLogging(loggingOn); -// std::vector> newCategories( -// cppfmu::Allocator(component->memory)); -// for (size_t i = 0; i < nCategories; ++i) { -// newCategories.push_back(cppfmu::CopyString(component->memory, categories[i])); -// } -// -// component->loggerSettings->debugLoggingEnabled = (loggingOn == fmi2True); -// component->loggerSettings->loggedCategories.swap(newCategories); return fmi2OK; } @@ -149,18 +148,18 @@ fmi2Status fmi2SetupExperiment( fmi2Boolean stopTimeDefined, fmi2Real stopTime) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); component->start = startTime; component->stop = stopTimeDefined ? std::optional(stopTime) : std::nullopt; component->tolerance = toleranceDefined ? std::optional(tolerance) : std::nullopt; - - return fmi2OK; + + return fmi2OK; } fmi2Status fmi2EnterInitializationMode(fmi2Component c) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->EnterInitializationMode(component->start, component->stop, @@ -178,7 +177,7 @@ fmi2Status fmi2EnterInitializationMode(fmi2Component c) fmi2Status fmi2ExitInitializationMode(fmi2Component c) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->ExitInitializationMode(); return fmi2OK; @@ -194,7 +193,7 @@ fmi2Status fmi2ExitInitializationMode(fmi2Component c) fmi2Status fmi2Terminate(fmi2Component c) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->Terminate(); return fmi2OK; @@ -210,7 +209,7 @@ fmi2Status fmi2Terminate(fmi2Component c) fmi2Status fmi2Reset(fmi2Component c) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->Reset(); return fmi2OK; @@ -230,7 +229,7 @@ fmi2Status fmi2GetReal( size_t nvr, fmi2Real value[]) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->GetReal(vr, nvr, value); return fmi2OK; @@ -249,7 +248,7 @@ fmi2Status fmi2GetInteger( size_t nvr, fmi2Integer value[]) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->GetInteger(vr, nvr, value); return fmi2OK; @@ -268,7 +267,7 @@ fmi2Status fmi2GetBoolean( size_t nvr, fmi2Boolean value[]) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->GetBoolean(vr, nvr, value); return fmi2OK; @@ -287,7 +286,7 @@ fmi2Status fmi2GetString( size_t nvr, fmi2String value[]) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->GetString(vr, nvr, value); return fmi2OK; @@ -307,7 +306,7 @@ fmi2Status fmi2SetReal( size_t nvr, const fmi2Real value[]) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->SetReal(vr, nvr, value); return fmi2OK; @@ -326,7 +325,7 @@ fmi2Status fmi2SetInteger( size_t nvr, const fmi2Integer value[]) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->SetInteger(vr, nvr, value); return fmi2OK; @@ -345,7 +344,7 @@ fmi2Status fmi2SetBoolean( size_t nvr, const fmi2Boolean value[]) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->SetBoolean(vr, nvr, value); return fmi2OK; @@ -364,7 +363,7 @@ fmi2Status fmi2SetString( size_t nvr, const fmi2String value[]) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->SetString(vr, nvr, value); return fmi2OK; @@ -382,7 +381,7 @@ fmi2Status fmi2GetFMUstate( fmi2Component c, fmi2FMUstate* state) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->GetFMUstate(*state); return fmi2OK; @@ -399,7 +398,7 @@ fmi2Status fmi2SetFMUstate( fmi2Component c, fmi2FMUstate state) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->SetFMUstate(state); return fmi2OK; @@ -416,7 +415,7 @@ fmi2Status fmi2FreeFMUstate( fmi2Component c, fmi2FMUstate* state) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->FreeFMUstate(*state); return fmi2OK; @@ -434,7 +433,7 @@ fmi2Status fmi2SerializedFMUstateSize( fmi2FMUstate state, size_t* size) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { *size = component->slave->SerializedFMUstateSize(state); return fmi2OK; @@ -453,7 +452,7 @@ fmi2Status fmi2SerializeFMUstate( fmi2Byte bytes[], size_t size) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->SerializeFMUstate(state, bytes, size); return fmi2OK; @@ -472,7 +471,7 @@ fmi2Status fmi2DeSerializeFMUstate( size_t size, fmi2FMUstate* state) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { component->slave->DeSerializeFMUstate(bytes, size, *state); return fmi2OK; @@ -495,7 +494,7 @@ fmi2Status fmi2GetDirectionalDerivative( const fmi2Real[], fmi2Real[]) { - reinterpret_cast(c)->logger->log( + static_cast(c)->logger->log( fmi2Error, "cppfmu", "FMI function not supported: fmi2GetDirectionalDerivative"); @@ -509,7 +508,7 @@ fmi2Status fmi2SetRealInputDerivatives( const fmi2Integer[], const fmi2Real[]) { - reinterpret_cast(c)->logger->log( + static_cast(c)->logger->log( fmi2Error, "cppfmu", "FMI function not supported: fmi2SetRealInputDerivatives"); @@ -523,7 +522,7 @@ fmi2Status fmi2GetRealOutputDerivatives( const fmi2Integer[], fmi2Real[]) { - reinterpret_cast(c)->logger->log( + static_cast(c)->logger->log( fmi2Error, "cppfmu", "FMI function not supported: fmiGetRealOutputDerivatives"); @@ -536,22 +535,20 @@ fmi2Status fmi2DoStep( fmi2Real communicationStepSize, fmi2Boolean /*noSetFMUStatePriorToCurrentPoint*/) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); try { double endTime = currentCommunicationPoint; const auto ok = component->slave->DoStep( currentCommunicationPoint, - communicationStepSize, - fmi2True, - endTime); + communicationStepSize); if (ok) { component->lastSuccessfulTime = currentCommunicationPoint + communicationStepSize; return fmi2OK; - } else { - component->lastSuccessfulTime = endTime; - return fmi2Discard; } + + component->lastSuccessfulTime = endTime; + return fmi2Discard; } catch (const pythonfmu::fatal_error& e) { component->logger->log(fmi2Fatal, e.what()); return fmi2Fatal; @@ -563,7 +560,7 @@ fmi2Status fmi2DoStep( fmi2Status fmi2CancelStep(fmi2Component c) { - reinterpret_cast(c)->logger->log( + static_cast(c)->logger->log( fmi2Error, "FMI function not supported: fmi2CancelStep"); return fmi2Error; @@ -576,7 +573,7 @@ fmi2Status fmi2GetStatus( const fmi2StatusKind, fmi2Status*) { - reinterpret_cast(c)->logger->log( + static_cast(c)->logger->log( fmi2Error, "FMI function not supported: fmi2GetStatus"); return fmi2Error; @@ -587,7 +584,7 @@ fmi2Status fmi2GetRealStatus( const fmi2StatusKind s, fmi2Real* value) { - const auto component = reinterpret_cast(c); + const auto component = static_cast(c); if (s == fmi2LastSuccessfulTime) { *value = component->lastSuccessfulTime; return fmi2OK; @@ -604,7 +601,7 @@ fmi2Status fmi2GetIntegerStatus( const fmi2StatusKind, fmi2Integer*) { - reinterpret_cast(c)->logger->log( + static_cast(c)->logger->log( fmi2Error, "FMI function not supported: fmi2GetIntegerStatus"); return fmi2Error; @@ -615,8 +612,8 @@ fmi2Status fmi2GetBooleanStatus( const fmi2StatusKind, fmi2Boolean*) { - reinterpret_cast(c)->logger->log( - fmi2Error,"FMI function not supported: fmi2GetBooleanStatus"); + static_cast(c)->logger->log( + fmi2Error, "FMI function not supported: fmi2GetBooleanStatus"); return fmi2Error; } @@ -625,8 +622,8 @@ fmi2Status fmi2GetStringStatus( const fmi2StatusKind, fmi2String*) { - reinterpret_cast(c)->logger->log( - fmi2Error,"FMI function not supported: fmi2GetStringStatus"); + static_cast(c)->logger->log( + fmi2Error, "FMI function not supported: fmi2GetStringStatus"); return fmi2Error; } } From bec480dd8090ac71ace6061ddf8e64d98705b89f Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Thu, 1 May 2025 00:55:37 +0200 Subject: [PATCH 3/8] bump version --- pythonfmu/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonfmu/_version.py b/pythonfmu/_version.py index f6104e0c..49e0fc1e 100644 --- a/pythonfmu/_version.py +++ b/pythonfmu/_version.py @@ -1 +1 @@ -__version__ = "0.6.7" +__version__ = "0.7.0" From 4ae8062148155052d2ba70ab7fad739555a51e50 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Thu, 1 May 2025 00:55:52 +0200 Subject: [PATCH 4/8] cleanup ref to debug --- pythonfmu/fmi2slave.py | 6 ++---- pythonfmu/logmsg.py | 5 ++--- .../pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pythonfmu/fmi2slave.py b/pythonfmu/fmi2slave.py index 27ab2172..00c47229 100644 --- a/pythonfmu/fmi2slave.py +++ b/pythonfmu/fmi2slave.py @@ -308,8 +308,7 @@ def log( self, msg: str, status: Fmi2Status = Fmi2Status.ok, - category: Optional[str] = None, - debug: bool = False + category: Optional[str] = None ): """Log a message to the FMU logger. @@ -317,11 +316,10 @@ def log( msg (str) : Log message status (Fmi2Status) : Optional, message status (default ok) category (str or None) : Optional, message category (default derived from status) - debug (bool) : Optional, is this a debug message (default False) """ if category is None: category = f"logStatus{status.name.capitalize()}" if category not in self.log_categories: category = "logAll" - log_msg = LogMsg(status, category, msg, debug) + log_msg = LogMsg(status, category, msg) self.log_queue.append(log_msg) diff --git a/pythonfmu/logmsg.py b/pythonfmu/logmsg.py index ad28489b..05f87e2f 100644 --- a/pythonfmu/logmsg.py +++ b/pythonfmu/logmsg.py @@ -1,14 +1,13 @@ class LogMsg: - def __init__(self, status: int, category: str, msg: str, debug: bool): + def __init__(self, status: int, category: str, msg: str): self.status = status self.category = category self.msg = msg - self.debug = debug def __str__(self) -> str: - return "LogMsg(status={}, category={}, msg={}, debug={}".format(self.status, self.category, self.msg, self.debug) + return "LogMsg(status={}, category={}, msg={}".format(self.status, self.category, self.msg) diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp index 90d7c255..2fa9026e 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp @@ -167,7 +167,6 @@ class PySlaveInstance : public SlaveInstance { clearLogStrBuffer(); - PyObject* debugField = Py_BuildValue("s", "debug"); PyObject* msgField = Py_BuildValue("s", "msg"); PyObject* categoryField = Py_BuildValue("s", "category"); PyObject* statusField = Py_BuildValue("s", "status"); @@ -199,7 +198,7 @@ class PySlaveInstance : public SlaveInstance } PyList_SetSlice(pMessages_, 0, size, nullptr); } - Py_DECREF(debugField); + Py_DECREF(msgField); Py_DECREF(categoryField); Py_DECREF(statusField); From 069e3636b4f543da0f65413d479212642e642a07 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sat, 3 May 2025 23:17:14 +0200 Subject: [PATCH 5/8] Add support for log categories in debug logging Also fixes and enables old logging tests --- .../pythonfmu-export/src/pythonfmu/Logger.hpp | 10 ++- .../pythonfmu-export/src/pythonfmu/fmi2.cpp | 12 ++- pythonfmu/tests/test_logger.py | 82 ++++++++++--------- 3 files changed, 62 insertions(+), 42 deletions(-) diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp b/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp index 7840231d..43822ddc 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp @@ -3,8 +3,8 @@ #define PYTHONFMU_LOGGER_HPP #include - #include +#include namespace pythonfmu { @@ -14,8 +14,9 @@ namespace pythonfmu { explicit PyLogger(std::string instanceName) : instanceName_(std::move(instanceName)) {} - void setDebugLogging(bool flag) { + void setDebugLogging(bool flag, const std::vector& categories = {}) { debugLogging_ = flag; + categories_ = categories; } // Logs a message. @@ -25,7 +26,9 @@ namespace pythonfmu { void log(fmi2Status s, const std::string& category, const std::string &message) { if (debugLogging_) { - debugLog(s, category, message); + if (categories_.empty() || std::find(categories_.begin(), categories_.end(), category) != categories_.end()) { + debugLog(s, category, message); + } } } @@ -36,6 +39,7 @@ namespace pythonfmu { protected: std::string instanceName_; + std::vector categories_; virtual void debugLog(fmi2Status s, const std::string& category, const std::string &message) = 0; }; diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp b/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp index affb3af6..a4e2bde9 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp @@ -134,7 +134,17 @@ fmi2Status fmi2SetDebugLogging( const fmi2String categories[]) { const auto component = static_cast(c); - component->logger->setDebugLogging(loggingOn); + + std::vector categoriesVec; + if (nCategories > 0) { + // Convert categories to std::vector + categoriesVec.reserve(nCategories); + for (size_t i = 0; i < nCategories; ++i) { + categoriesVec.emplace_back(categories[i]); + } + } + + component->logger->setDebugLogging(loggingOn, categoriesVec); return fmi2OK; } diff --git a/pythonfmu/tests/test_logger.py b/pythonfmu/tests/test_logger.py index 72ea0ae1..0d105986 100644 --- a/pythonfmu/tests/test_logger.py +++ b/pythonfmu/tests/test_logger.py @@ -1,7 +1,7 @@ import sys import itertools import pytest -from unittest.mock import call, MagicMock +from unittest.mock import call, MagicMock, ANY from pythonfmu.builder import FmuBuilder from pythonfmu.enums import Fmi2Status @@ -14,7 +14,7 @@ not FmuBuilder.has_binary(), reason="No binary available for the current platform." ) -if True: +if False: pytest.skip("This test needs to be manually enabled", allow_module_level=True) @@ -27,15 +27,15 @@ def test_logger(tmp_path, debug_logging): log_calls = [ ( - f"{status.name.upper()} - {debug} - {message}", - status, - category, + f"{status.name.upper()} - {debug} - {message}", + status, + category, debug ) for debug, status in itertools.product([True, False], Fmi2Status) ] fmu_calls = "\n".join([ - ' self.log("{}", Fmi2Status.{}, "{}", {})'.format(c[0], c[1].name, c[2], c[3]) for c in log_calls + ' self.log("{}", Fmi2Status.{}, "{}")'.format(c[0], c[1].name, c[2]) for c in log_calls ]) slave_code = f"""from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Status, Fmi2Causality, Integer, Real, Boolean, String @@ -74,18 +74,21 @@ def do_step(self, current_time, step_size): debug_logging=debug_logging ) - expected_calls = [ - call( - logger.call_args[0][0], # Don't test the first argument - bytes(name, encoding="utf-8"), - int(c[1]), - bytes(c[2], encoding="utf-8"), - bytes(c[0], encoding="utf-8") - ) for c in filter(lambda c: debug_logging or not c[3], log_calls) - ] - - assert logger.call_count == len(Fmi2Status) * (1 + int(debug_logging)) - logger.assert_has_calls(expected_calls) + if debug_logging: + expected_calls = [ + call( + ANY, # Don't test the first argument + bytes(name, encoding="utf-8"), + int(c[1]), + bytes(c[2], encoding="utf-8"), + bytes(c[0], encoding="utf-8") + ) for c in filter(lambda c: debug_logging or not c[3], log_calls) + ] + + assert logger.call_count == len(Fmi2Status) * (1 + int(debug_logging)) + logger.assert_has_calls(expected_calls) + else: + assert logger.call_count == 0 @pytest.mark.integration @@ -97,14 +100,14 @@ def test_log_categories(tmp_path, debug_logging, categories): log_calls = [ ( - f"{status.name.upper()} - {debug} - {message}", + f"{status.name.upper()} - {debug} - {message}", status, debug ) for debug, status in itertools.product([True, False], Fmi2Status) ] fmu_calls = "\n".join([ - ' self.log("{}", Fmi2Status.{}, None, {})'.format(c[0], c[1].name, c[2]) for c in log_calls + ' self.log("{}", Fmi2Status.{}, None)'.format(c[0], c[1].name) for c in log_calls ]) slave_code = f"""from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Status, Fmi2Causality, Integer, Real, Boolean, String @@ -160,21 +163,24 @@ def do_step(self, current_time, step_size): # Clean the model model.terminate() - expected_calls = [] - for c in filter(lambda c: debug_logging or not c[2], log_calls): - category = f"logStatus{c[1].name.capitalize()}" - if category not in Fmi2Slave.log_categories: - category = "logAll" - if len(categories) == 0 or category in categories: - expected_calls.append(call( - logger.call_args[0][0], # Don't test the first argument - b'instance1', - int(c[1]), - bytes(category, encoding="utf-8"), - bytes(c[0], encoding="utf-8") - )) - - n_calls = len(Fmi2Status) if len(categories) == 0 else len(categories) - - assert logger.call_count == n_calls * (1 + int(debug_logging)) - logger.assert_has_calls(expected_calls) + if debug_logging: + expected_calls = [] + for c in filter(lambda c: debug_logging or not c[2], log_calls): + category = f"logStatus{c[1].name.capitalize()}" + if category not in Fmi2Slave.log_categories: + category = "logAll" + if len(categories) == 0 or category in categories: + expected_calls.append(call( + logger.call_args[0][0], # Don't test the first argument + b'instance1', + int(c[1]), + bytes(category, encoding="utf-8"), + bytes(c[0], encoding="utf-8") + )) + + n_calls = len(Fmi2Status) if len(categories) == 0 else len(categories) + + assert logger.call_count == n_calls * (1 + int(debug_logging)) + logger.assert_has_calls(expected_calls) + else: + assert logger.call_count == 0 From 2cd2cb6172a2cee338971117167a73f5366c335f Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sat, 3 May 2025 23:18:30 +0200 Subject: [PATCH 6/8] add --- .../pythonfmu-export/src/pythonfmu/Logger.hpp | 76 ++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp b/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp index 43822ddc..0054d0b2 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/Logger.hpp @@ -2,48 +2,56 @@ #ifndef PYTHONFMU_LOGGER_HPP #define PYTHONFMU_LOGGER_HPP -#include +#include "fmi/fmi2Functions.h" + +#include #include #include -namespace pythonfmu { - - class PyLogger { - - public: - explicit PyLogger(std::string instanceName) - : instanceName_(std::move(instanceName)) {} - - void setDebugLogging(bool flag, const std::vector& categories = {}) { - debugLogging_ = flag; - categories_ = categories; - } - - // Logs a message. - void log(fmi2Status s, const std::string &message) { - log(s, "", message); - } - - void log(fmi2Status s, const std::string& category, const std::string &message) { - if (debugLogging_) { - if (categories_.empty() || std::find(categories_.begin(), categories_.end(), category) != categories_.end()) { - debugLog(s, category, message); - } +namespace pythonfmu +{ + +class PyLogger +{ + +public: + explicit PyLogger(std::string instanceName) + : instanceName_(std::move(instanceName)) + { } + + void setDebugLogging(bool flag, const std::vector& categories = {}) + { + debugLogging_ = flag; + categories_ = categories; + } + + // Logs a message. + void log(fmi2Status s, const std::string& message) + { + log(s, "", message); + } + + void log(fmi2Status s, const std::string& category, const std::string& message) + { + if (debugLogging_) { + if (categories_.empty() || std::find(categories_.begin(), categories_.end(), category) != categories_.end()) { + debugLog(s, category, message); } } + } - virtual ~PyLogger() = default; + virtual ~PyLogger() = default; - private: - bool debugLogging_{false}; +private: + bool debugLogging_{false}; - protected: - std::string instanceName_; - std::vector categories_; +protected: + std::string instanceName_; + std::vector categories_; - virtual void debugLog(fmi2Status s, const std::string& category, const std::string &message) = 0; - }; + virtual void debugLog(fmi2Status s, const std::string& category, const std::string& message) = 0; +}; -}// namespace pythonfmu +} // namespace pythonfmu -#endif//PYTHONFMU_LOGGER_HPP +#endif // PYTHONFMU_LOGGER_HPP From c66bc4e32b1a54bea8381de95a421b551e701a4e Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Tue, 6 May 2025 22:00:57 +0200 Subject: [PATCH 7/8] undo breaking changes --- pythonfmu/fmi2slave.py | 18 ++++++++++----- .../src/pythonfmu/PySlaveInstance.cpp | 18 ++++++++++++--- .../src/pythonfmu/SlaveInstance.hpp | 19 ++-------------- .../pythonfmu-export/src/pythonfmu/fmi2.cpp | 22 ++++++++++++------- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/pythonfmu/fmi2slave.py b/pythonfmu/fmi2slave.py index 00c47229..4b3c36df 100644 --- a/pythonfmu/fmi2slave.py +++ b/pythonfmu/fmi2slave.py @@ -176,7 +176,10 @@ def register_variable(self, var: ScalarVariable, nested: bool = True): if var.setter is None and hasattr(owner, var.local_name) and var.variability != Fmi2Variability.constant: var.setter = lambda v: setattr(owner, var.local_name, v) - def enter_initialization_mode(self, start_time: float): + def setup_experiment(self, start_time: float): + pass + + def enter_initialization_mode(self): pass def exit_initialization_mode(self): @@ -305,10 +308,11 @@ def _get_log_queue(self): return self.log_queue def log( - self, - msg: str, - status: Fmi2Status = Fmi2Status.ok, - category: Optional[str] = None + self, + msg: str, + status: Fmi2Status = Fmi2Status.ok, + category: Optional[str] = None, + debug=None ): """Log a message to the FMU logger. @@ -316,7 +320,11 @@ def log( msg (str) : Log message status (Fmi2Status) : Optional, message status (default ok) category (str or None) : Optional, message category (default derived from status) + debug (bool) : Deprecated (has no effect) """ + if debug is not None: + print(f"WARNING: 'debug' argument is deprecated and has no effect.") + if category is None: category = f"logStatus{status.name.capitalize()}" if category not in self.log_categories: diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp index 2fa9026e..06045eca 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp @@ -131,7 +131,7 @@ void py_safe_run(const std::function& f) class PySlaveInstance : public SlaveInstance { public: - PySlaveInstance(fmu_data data) + explicit PySlaveInstance(fmu_data data) : data_(std::move(data)) { py_safe_run([this](PyGILState_STATE gilState) { @@ -224,10 +224,22 @@ class PySlaveInstance : public SlaveInstance pMessages_ = PyObject_CallMethod(pInstance_, "_get_log_queue", nullptr); } + void SetupExperiment(double startTime, std::optional stop, std::optional tolerance) override + { + py_safe_run([this, startTime](PyGILState_STATE gilState) { + auto f = PyObject_CallMethod(pInstance_, "setup_experiment", "(d)", startTime); + if (f == nullptr) { + handle_py_exception("[setupExperiment] PyObject_CallMethod", gilState); + } + Py_DECREF(f); + clearLogBuffer(); + }); + } + void EnterInitializationMode() override { py_safe_run([this](PyGILState_STATE gilState) { - auto f = PyObject_CallMethod(pInstance_, "enter_initialization_mode", "(d)", time_); + auto f = PyObject_CallMethod(pInstance_, "enter_initialization_mode", nullptr); if (f == nullptr) { handle_py_exception("[enterInitializationMode] PyObject_CallMethod", gilState); } @@ -526,7 +538,7 @@ class PySlaveInstance : public SlaveInstance }); } - void DeSerializeFMUstate(const fmi2Byte bytes[], size_t size, fmi2FMUstate& state) + void DeSerializeFMUstate(const fmi2Byte bytes[], size_t size, fmi2FMUstate& state) override { py_safe_run([this, &bytes, size, &state](PyGILState_STATE gilState) { PyObject* pyStateBytes = PyBytes_FromStringAndSize(bytes, size); diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp b/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp index db5a0f28..4416c4e2 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/SlaveInstance.hpp @@ -23,14 +23,8 @@ struct fmu_data class SlaveInstance { public: - void EnterInitializationMode(double start, std::optional stop, std::optional tolerance) - { - time_ = start; - stop_ = stop; - tolerance_ = tolerance; - EnterInitializationMode(); - } + virtual void SetupExperiment(double start, std::optional stop, std::optional tolerance) = 0; virtual void EnterInitializationMode() = 0; @@ -57,9 +51,6 @@ class SlaveInstance std::size_t nvr, const char* const value[]) = 0; - /* Called from fmi2GetXxx()/fmiGetXxx(). - * Throws std::logic_error by default. - */ virtual void GetReal( const unsigned int vr[], std::size_t nvr, @@ -77,7 +68,6 @@ class SlaveInstance std::size_t nvr, const char* value[]) const = 0; - // Called from fmi2DoStep()/fmiDoStep(). Must be implemented in model code. bool DoStep( double currentCommunicationPoint, double communicationStepSize) @@ -95,13 +85,8 @@ class SlaveInstance virtual void SerializeFMUstate(const fmi2FMUstate& state, fmi2Byte bytes[], size_t size) = 0; virtual void DeSerializeFMUstate(const fmi2Byte bytes[], size_t size, fmi2FMUstate& state) = 0; - // The instance is destroyed in fmi2FreeInstance()/fmiFreeSlaveInstance(). virtual ~SlaveInstance() = default; - -protected: - double time_{0}; - std::optional stop_; - std::optional tolerance_; + ; }; std::unique_ptr createInstance(fmu_data data); diff --git a/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp b/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp index a4e2bde9..e99c98cb 100644 --- a/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp +++ b/pythonfmu/pythonfmu-export/src/pythonfmu/fmi2.cpp @@ -159,11 +159,19 @@ fmi2Status fmi2SetupExperiment( fmi2Real stopTime) { const auto component = static_cast(c); - component->start = startTime; - component->stop = stopTimeDefined ? std::optional(stopTime) : std::nullopt; - component->tolerance = toleranceDefined ? std::optional(tolerance) : std::nullopt; - - return fmi2OK; + try { + component->slave->SetupExperiment( + startTime, + stopTimeDefined ? std::optional(stopTime) : std::nullopt, + toleranceDefined ? std::optional(tolerance) : std::nullopt); + return fmi2OK; + }catch (const pythonfmu::fatal_error& e) { + component->logger->log(fmi2Fatal, e.what()); + return fmi2Fatal; + } catch (const std::exception& e) { + component->logger->log(fmi2Error, e.what()); + return fmi2Error; + } } @@ -171,9 +179,7 @@ fmi2Status fmi2EnterInitializationMode(fmi2Component c) { const auto component = static_cast(c); try { - component->slave->EnterInitializationMode(component->start, - component->stop, - component->tolerance); + component->slave->EnterInitializationMode(); return fmi2OK; } catch (const pythonfmu::fatal_error& e) { component->logger->log(fmi2Fatal, e.what()); From 88d348d292e995fa8e124d7b03915645bb800713 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Tue, 6 May 2025 22:52:14 +0200 Subject: [PATCH 8/8] alter version [skip ci] --- pythonfmu/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonfmu/_version.py b/pythonfmu/_version.py index 49e0fc1e..afd45a48 100644 --- a/pythonfmu/_version.py +++ b/pythonfmu/_version.py @@ -1 +1 @@ -__version__ = "0.7.0" +__version__ = "0.6.8"