diff --git a/.gitignore b/.gitignore index 612b06f0..69df0ad6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,17 @@ *.csv *.html +# binaries +*.a +*.dll +*.dylib +*.exe +*.lib +*.so + # source archives *.tar.gz +*.zip # Affinity Designer documents *.afdesign @@ -18,6 +27,7 @@ build/ cvode-*/ dist/ MANIFEST +rpclib-*/ *.egg-info # test artifacts diff --git a/OverrideMSVCFlags.cmake b/OverrideMSVCFlags.cmake index 124349dc..c976b2c9 100644 --- a/OverrideMSVCFlags.cmake +++ b/OverrideMSVCFlags.cmake @@ -2,7 +2,14 @@ if (MSVC) # link statically against the Visual C runtime set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING INTERNAL FORCE) + set(CMAKE_C_FLAGS_DEBUG "/MTd /Zi /Ob0 /Od /RTC1") + message("Updated CMAKE_C_FLAGS_DEBUG: ${CMAKE_C_FLAGS_DEBUG}") + set(CMAKE_C_FLAGS_RELEASE "/MT /O2 /Ob2 /DNDEBUG") - message("Updated CMAKE_C_FLAGS_RELEASE:") - message("${CMAKE_C_FLAGS_RELEASE}") + message("Updated CMAKE_C_FLAGS_RELEASE: ${CMAKE_C_FLAGS_RELEASE}") +endif () + +if (UNIX AND NOT APPLE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fpic") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpic") endif () diff --git a/README.md b/README.md index c533f199..ec59a2cf 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Several options are available: - Install with conda: `conda install -c conda-forge fmpy` - Install with from PyPI: `python -m pip install fmpy[complete]` -- Install the latest development version directly from GitHub: `python -m pip install https://github.com/CATIA-Systems/FMPy/archive/develop.zip` +- [Install a development build](docs/faq.md) If you don't have Python on your machine you can install [Anaconda Python](https://www.anaconda.com/download/). diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5b2237cb..1b7842e0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,6 +1,6 @@ jobs: -- job: +- job: linux displayName: 'Ubuntu 16.04' strategy: matrix: @@ -24,6 +24,11 @@ jobs: conda install --yes --quiet --name myEnvironment -c conda-forge python=$PYTHON_VERSION cmake dask lark-parser lxml matplotlib numpy pyqt pyqtgraph pytest-cov requests $PYTHON_LIBRARIES displayName: Install Anaconda packages + - bash: | + source activate myEnvironment + python build_cvode.py + displayName: Build CVode binaries + - bash: | source activate myEnvironment python setup.py bdist_wheel --universal @@ -52,7 +57,7 @@ jobs: path: dist artifact: 'linux-python-$(python.version)' -- job: +- job: macosx displayName: 'macOS 10.15' strategy: matrix: @@ -83,6 +88,11 @@ jobs: conda install --yes --quiet --name myEnvironment -c conda-forge python=$PYTHON_VERSION dask lark-parser lxml matplotlib numpy pyqt pyqtgraph pytest-cov requests $PYTHON_LIBRARIES displayName: Install Anaconda packages + - bash: | + source activate myEnvironment + python build_cvode.py + displayName: Build CVode binaries + - bash: | source activate myEnvironment python setup.py bdist_wheel --universal @@ -111,7 +121,7 @@ jobs: path: dist artifact: 'macosx-python-$(python.version)' -- job: +- job: windows displayName: 'Windows 2016' strategy: matrix: @@ -130,12 +140,22 @@ jobs: - script: conda create --yes --quiet --name myEnvironment displayName: Create Anaconda environment - + - script: | call activate myEnvironment conda install --yes --quiet --name myEnvironment -c conda-forge python=%PYTHON_VERSION% cmake dask lark-parser lxml matplotlib numpy pyqt pyqtgraph pytest-cov pywin32 requests displayName: Install Anaconda packages + - script: | + call activate myEnvironment + python build_cvode.py + displayName: Build CVode binaries + + - script: | + call activate myEnvironment + python build_remoting.py + displayName: Build Remoting binaries + - script: | call activate myEnvironment python setup.py bdist_wheel --universal @@ -166,3 +186,45 @@ jobs: codeCoverageTool: Cobertura summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml' reportDirectory: '$(System.DefaultWorkingDirectory)/**/htmlcov' + +- job: merge + dependsOn: + - linux + - macosx + - windows + displayName: 'Merge Python Wheels' + pool: + vmImage: 'ubuntu-16.04' + + steps: + + - bash: mkdir temp + displayName: Merge Python Wheels + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: linux-python-3.6 + downloadPath: linux + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: macosx-python-3.6 + downloadPath: macosx + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: windows-python-3.6 + downloadPath: windows + + - bash: | + unzip -o linux/*.whl -d merged + unzip -o macosx/*.whl -d merged + unzip -o windows/*.whl -d merged + cd merged + zip -r FMPy-x.x.x-py2.py3-none-any.whl . + displayName: Merge FMUs + + - task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: merged/FMPy-x.x.x-py2.py3-none-any.whl + artifactName: merged diff --git a/build_cvode.py b/build_cvode.py new file mode 100644 index 00000000..0b6f6dd0 --- /dev/null +++ b/build_cvode.py @@ -0,0 +1,102 @@ +from fmpy import platform_tuple, sharedLibraryExtension +from fmpy.util import download_file +import tarfile +import os +import shutil +from subprocess import check_call + + +if os.name == 'nt': + generator = 'Visual Studio 15 2017 Win64' + sl_prefix = '' + sl_suffix = sharedLibraryExtension +else: + generator = 'Unix Makefiles' + sl_prefix = 'lib' + sl_suffix = sharedLibraryExtension + +sundials_binary_dir = os.path.join('fmpy', 'sundials', platform_tuple) + +# clean up +for build_dir in ['cswrapper/build', 'cvode-5.3.0', sundials_binary_dir, 'fmpy/logging/build']: + if os.path.isdir(build_dir): + shutil.rmtree(build_dir) + +url = 'https://computing.llnl.gov/projects/sundials/download/cvode-5.3.0.tar.gz' +checksum = 'd7ff8e77bb2a59cf8143de30f05a2651c2b4d29b586f8003f9187bf9e5a7da6e' + +filename = os.path.basename(url) + +download_file(url, checksum) + +# response = requests.get(url) +# +# with open(filename, 'wb') as f: +# f.write(response.content) + +with tarfile.open(filename, "r:gz") as tar: + tar.extractall() + +os.mkdir('cvode-5.3.0/static') + +# build CVode as static library +check_call([ + 'cmake', + '-DEXAMPLES_ENABLE_C=OFF', + '-DBUILD_SHARED_LIBS=OFF', + '-DCMAKE_INSTALL_PREFIX=cvode-5.3.0/static/install', + '-DCMAKE_USER_MAKE_RULES_OVERRIDE=../OverrideMSVCFlags.cmake', + '-G', generator, + '-S', 'cvode-5.3.0', + '-B', 'cvode-5.3.0/static' +]) + +check_call(['cmake', '--build', 'cvode-5.3.0/static', '--target', 'install', '--config', 'Release']) + +# build CVode as dynamic library +check_call([ + 'cmake', + '-DEXAMPLES_ENABLE_C=OFF', + '-DBUILD_STATIC_LIBS=OFF', + '-DCMAKE_INSTALL_PREFIX=cvode-5.3.0/dynamic/install', + '-DCMAKE_USER_MAKE_RULES_OVERRIDE=../OverrideMSVCFlags.cmake', + '-G', generator, + '-S', 'cvode-5.3.0', + '-B', 'cvode-5.3.0/dynamic' +]) + +check_call(['cmake', '--build', 'cvode-5.3.0/dynamic', '--target', 'install', '--config', 'Release']) + +os.mkdir(sundials_binary_dir) + +os.path.join('cvode-5.3.0', 'dynamic', 'install', 'sundials_cvode' + sharedLibraryExtension) + +for name in ['sundials_cvode', 'sundials_nvecserial', 'sundials_sunlinsoldense', 'sundials_sunmatrixdense']: + src = os.path.join('cvode-5.3.0', 'dynamic', 'install', 'lib', sl_prefix + name + sl_suffix) + dst = os.path.join(sundials_binary_dir, name + sl_suffix) + shutil.copy(src, dst) + +# build cswrapper +os.mkdir('cswrapper/build') + +check_call([ + 'cmake', + '-DCVODE_INSTALL_DIR=../cvode-5.3.0/static/install', + '-G', generator, + '-S', 'cswrapper', + '-B', 'cswrapper/build' +]) + +check_call(['cmake', '--build', 'cswrapper/build', '--config', 'Release']) + +# build logging callback +os.mkdir('fmpy/logging/build') + +check_call([ + 'cmake', + '-G', generator, + '-S', 'fmpy/logging', + '-B', 'fmpy/logging/build' +]) + +check_call(['cmake', '--build', 'fmpy/logging/build', '--config', 'Release']) diff --git a/build_remoting.py b/build_remoting.py new file mode 100644 index 00000000..cc6ee7b6 --- /dev/null +++ b/build_remoting.py @@ -0,0 +1,79 @@ +from fmpy import sharedLibraryExtension, extract +from fmpy.util import download_file +import os +import shutil +from subprocess import check_call + + +# clean up +for p in ['rpclib-2.2.1', 'remoting/client/build', 'remoting/server/build']: + if os.path.exists(p): + shutil.rmtree(p) + +for f in ['fmpy/remoting/client.dll', 'fmpy/remoting/server.exe']: + if os.path.exists(f): + os.remove(f) + +rpclib_url = 'https://github.com/rpclib/rpclib/archive/v2.2.1.zip' +rpclib_checksum = '70f10b59f0eb303ccee4a9dda32e6ed898783be9a539d32b43e6fcb4430dce0c' +rpclib_filename = os.path.basename(rpclib_url) + +download_file(rpclib_url, rpclib_checksum) + +extract(rpclib_filename, '.') + +# root = os.path.dirname(__file__) + +# build RPCLIB for win32 +check_call([ + 'cmake', + '-DCMAKE_INSTALL_PREFIX=rpclib-2.2.1/win32/install', + '-DRPCLIB_MSVC_STATIC_RUNTIME=ON', + '-G', 'Visual Studio 15 2017', + '-S', 'rpclib-2.2.1', + '-B', 'rpclib-2.2.1/win32' +]) + +check_call(['cmake', '--build', 'rpclib-2.2.1/win32', '--target', 'install', '--config', 'Release']) + +# build RPCLIB for win64 +check_call([ + 'cmake', + '-DCMAKE_INSTALL_PREFIX=rpclib-2.2.1/win64/install', + '-DRPCLIB_MSVC_STATIC_RUNTIME=ON', + '-G', 'Visual Studio 15 2017 Win64', + '-S', 'rpclib-2.2.1', + '-B', 'rpclib-2.2.1/win64' +]) + +check_call(['cmake', '--build', 'rpclib-2.2.1/win64', '--target', 'install', '--config', 'Release']) + +print('####' + str([ + 'cmake', + '-DRPCLIB=' + os.path.abspath('rpclib-2.2.1/win32/install').replace('\\', '/'), + '-G', 'Visual Studio 15 2017', + '-S', 'remoting/server', + '-B', 'remoting/server/build' +])) + +# build server.exe +check_call([ + 'cmake', + '-DRPCLIB=' + os.path.abspath('rpclib-2.2.1/win32/install').replace('\\', '/'), + '-G', 'Visual Studio 15 2017', + '-S', 'remoting/server', + '-B', 'remoting/server/build' +]) + +check_call(['cmake', '--build', 'remoting/server/build', '--config', 'Release']) + +# build client.exe +check_call([ + 'cmake', + '-DRPCLIB=' + os.path.abspath('rpclib-2.2.1/win64/install').replace('\\', '/'), + '-G', 'Visual Studio 15 2017 Win64', + '-S', 'remoting/client', + '-B', 'remoting/client/build' +]) + +check_call(['cmake', '--build', 'remoting/client/build', '--config', 'Release']) diff --git a/cswrapper/CMakeLists.txt b/cswrapper/CMakeLists.txt new file mode 100644 index 00000000..888100a8 --- /dev/null +++ b/cswrapper/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required (VERSION 3.2) + +set(CVODE_INSTALL_DIR "../cvode-5.3.0/build/install" CACHE STRING "CVode installation directory") + +project (cswrapper) + +if (MSVC) + # link statically against the the Visual C runtime + string(REPLACE "/MD" "/MT" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string(REPLACE "/MDd" "/MTd" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") + + # disable compiler warnings + add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE) +endif () + +add_library(cswrapper SHARED + ../fmpy/c-code/fmi2Functions.h + ../fmpy/c-code/fmi2FunctionTypes.h + ../fmpy/c-code/fmi2TypesPlatform.h + cswrapper.c +) + +SET_TARGET_PROPERTIES(cswrapper PROPERTIES PREFIX "") + +target_include_directories(cswrapper PUBLIC + .. + ../fmpy/c-code + ${CVODE_INSTALL_DIR}/include +) + +if (WIN32) + file(GLOB SUNDIALS_LIBS ${CVODE_INSTALL_DIR}/lib/*.lib) +else() + file(GLOB SUNDIALS_LIBS ${CVODE_INSTALL_DIR}/lib/*.a) +endif() + +target_link_libraries(cswrapper + ${SUNDIALS_LIBS} + ${CMAKE_DL_LIBS} +) + +add_custom_command(TARGET cswrapper POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${CMAKE_CURRENT_SOURCE_DIR}/../fmpy/cswrapper" +) diff --git a/cswrapper/cswrapper.c b/cswrapper/cswrapper.c new file mode 100644 index 00000000..d9888a80 --- /dev/null +++ b/cswrapper/cswrapper.c @@ -0,0 +1,626 @@ +#if defined(_WIN32) +#include +#elif defined(__APPLE__) +#include +#include +#else +#define _GNU_SOURCE +#include +#include +#endif + +#include +#include +#include +#include /* for fabs() */ + +#include /* prototypes for CVODE fcts., consts. */ +#include /* access to serial N_Vector */ +#include /* access to dense SUNMatrix */ +#include /* access to dense SUNLinearSolver */ +#include /* defs. of realtype, sunindextype */ + +#include "fmi2Functions.h" + + +#define EPSILON 1e-14 +#define RTOL RCONST(1.0e-4) /* scalar relative tolerance */ + +#if defined(_WIN32) +#define SHARED_LIBRARY_EXTENSION ".dll" +#elif defined(__APPLE__) +#define SHARED_LIBRARY_EXTENSION ".dylib" +#else +#define SHARED_LIBRARY_EXTENSION ".so" +#endif + +typedef struct { + +#if defined(_WIN32) + HMODULE libraryHandle; +#else + void *libraryHandle; +#endif + + fmi2Component c; + fmi2EventInfo eventInfo; + fmi2CallbackLogger logger; + const char *instanceName; + + size_t nx; + size_t nz; + + void *cvode_mem; + N_Vector x; + N_Vector abstol; + SUNMatrix A; + SUNLinearSolver LS; + + /*************************************************** + Common Functions + ****************************************************/ + fmi2GetTypesPlatformTYPE *fmi2GetTypesPlatform; + fmi2GetVersionTYPE *fmi2GetVersion; + fmi2SetDebugLoggingTYPE *fmi2SetDebugLogging; + fmi2InstantiateTYPE *fmi2Instantiate; + fmi2FreeInstanceTYPE *fmi2FreeInstance; + fmi2SetupExperimentTYPE *fmi2SetupExperiment; + fmi2EnterInitializationModeTYPE *fmi2EnterInitializationMode; + fmi2ExitInitializationModeTYPE *fmi2ExitInitializationMode; + fmi2TerminateTYPE *fmi2Terminate; + fmi2ResetTYPE *fmi2Reset; + fmi2GetRealTYPE *fmi2GetReal; + fmi2GetIntegerTYPE *fmi2GetInteger; + fmi2GetBooleanTYPE *fmi2GetBoolean; + fmi2GetStringTYPE *fmi2GetString; + fmi2SetRealTYPE *fmi2SetReal; + fmi2SetIntegerTYPE *fmi2SetInteger; + fmi2SetBooleanTYPE *fmi2SetBoolean; + fmi2SetStringTYPE *fmi2SetString; + fmi2GetFMUstateTYPE *fmi2GetFMUstate; + fmi2SetFMUstateTYPE *fmi2SetFMUstate; + fmi2FreeFMUstateTYPE *fmi2FreeFMUstate; + fmi2SerializedFMUstateSizeTYPE *fmi2SerializedFMUstateSize; + fmi2SerializeFMUstateTYPE *fmi2SerializeFMUstate; + fmi2DeSerializeFMUstateTYPE *fmi2DeSerializeFMUstate; + fmi2GetDirectionalDerivativeTYPE *fmi2GetDirectionalDerivative; + + + /*************************************************** + Functions for FMI2 for Model Exchange + ****************************************************/ + fmi2EnterEventModeTYPE *fmi2EnterEventMode; + fmi2NewDiscreteStatesTYPE *fmi2NewDiscreteStates; + fmi2EnterContinuousTimeModeTYPE *fmi2EnterContinuousTimeMode; + fmi2CompletedIntegratorStepTYPE *fmi2CompletedIntegratorStep; + fmi2SetTimeTYPE *fmi2SetTime; + fmi2SetContinuousStatesTYPE *fmi2SetContinuousStates; + fmi2GetDerivativesTYPE *fmi2GetDerivatives; + fmi2GetEventIndicatorsTYPE *fmi2GetEventIndicators; + fmi2GetContinuousStatesTYPE *fmi2GetContinuousStates; + fmi2GetNominalsOfContinuousStatesTYPE *fmi2GetNominalsOfContinuousStates; + +} Model; + +static int f(realtype t, N_Vector y, N_Vector ydot, void *user_data) { + + Model *m = (Model *)user_data; + + if (m->nx > 0) { + fmi2Status status; + status = m->fmi2SetTime(m->c, t); + status = m->fmi2GetContinuousStates(m->c, NV_DATA_S(y), NV_LENGTH_S(y)); + status = m->fmi2GetDerivatives(m->c, NV_DATA_S(ydot), NV_LENGTH_S(ydot)); + } + + return 0; + +} + +static int g(realtype t, N_Vector y, realtype *gout, void *user_data) { + + Model *m = (Model *)user_data; + + fmi2Status status = m->fmi2SetTime(m->c, t); + + if (m->nx > 0) { + status = m->fmi2SetContinuousStates(m->c, NV_DATA_S(y), NV_LENGTH_S(y)); + } + + status = m->fmi2GetEventIndicators(m->c, gout, m->nz); + + return 0; +} + +static void ehfun(int error_code, const char *module, const char *function, char *msg, void *user_data) { + + Model *m = (Model *)user_data; + + m->logger(m, m->instanceName, fmi2Error, "logError", "CVode error(code %d) in module %s, function %s: %s.", error_code, module, function, msg); +} + + +/*************************************************** +Types for Common Functions +****************************************************/ + +/* Inquire version numbers of header files and setting logging status */ +const char* fmi2GetTypesPlatform(void) { return fmi2TypesPlatform; } + +const char* fmi2GetVersion(void) { return fmi2Version; } + +fmi2Status fmi2SetDebugLogging(fmi2Component c, fmi2Boolean loggingOn, size_t nCategories, const fmi2String categories[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2SetDebugLogging(c, loggingOn, nCategories, categories); +} + + +/* Creation and destruction of FMU instances and setting debug status */ +#ifdef _WIN32 +#define GET(f) m->f = (f ## TYPE *)GetProcAddress(m->libraryHandle, #f); if (!m->f) { return NULL; } +#else +#define GET(f) m->f = (f ## TYPE *)dlsym(m->libraryHandle, #f); if (!m->f) { return NULL; } +#endif + +#define ASSERT_CV_SUCCESS(f) if (f != CV_SUCCESS) { return NULL; } +#define ASSERT_NOT_NULL(v) if (!v) { return NULL; } + +/* Creation and destruction of FMU instances and setting debug status */ +fmi2Component fmi2Instantiate(fmi2String instanceName, + fmi2Type fmuType, + fmi2String fmuGUID, + fmi2String fmuResourceLocation, + const fmi2CallbackFunctions* functions, + fmi2Boolean visible, + fmi2Boolean loggingOn) { + + if (!functions || !functions->logger) { + return NULL; + } + + if (fmuType != fmi2CoSimulation) { + functions->logger(NULL, instanceName, fmi2Error, "logError", "Argument fmuType must be fmi2CoSimulation."); + return NULL; + } + + Model *m = calloc(1, sizeof(Model)); + + m->logger = functions->logger; + m->instanceName = strdup(instanceName); + +#ifdef _WIN32 + char path[MAX_PATH]; + HMODULE hm = NULL; + + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)&fmi2Instantiate, &hm) == 0) + { + int ret = GetLastError(); + //fprintf(stderr, "GetModuleHandle failed, error = %d\n", ret); + // Return or however you want to handle an error. + } + + if (GetModuleFileName(hm, path, sizeof(path)) == 0) + { + int ret = GetLastError(); + //fprintf(stderr, "GetModuleFileName failed, error = %d\n", ret); + // Return or however you want to handle an error. + } + + char* name = strdup(path); +#else + Dl_info info; + + if (!dladdr(fmi2Instantiate, &info)) { + if (functions && functions->logger) { + functions->logger(NULL, instanceName, fmi2Error, "logError", "Failed to get shared library info."); + } + return NULL; + } + + char *name = strdup(info.dli_fname); +#endif + + size_t len = strlen(name); + + // remove the file extension + char *sle = SHARED_LIBRARY_EXTENSION; + size_t slelen = strlen(sle); + + name[len-slelen] = '\0'; + + // number of event indicators as a string + char *n = strrchr(name, '_'); + + m->nz = atoi(n+1); + + len = strlen(name); + name[len-strlen(n)] = '\0'; + + // number of continuous states as a string + n = strrchr(name, '_'); + + m->nx = atoi(n+1); + + len = strlen(name); + name[len-strlen(n)] = '\0'; + + // re-append the file extension + strcat(name, SHARED_LIBRARY_EXTENSION); + +#ifdef _WIN32 + m->libraryHandle = LoadLibrary(name); +#else + m->libraryHandle = dlopen(name, RTLD_LAZY); +#endif + + ASSERT_NOT_NULL(m->libraryHandle) + + GET(fmi2GetTypesPlatform) + GET(fmi2GetVersion) + GET(fmi2SetDebugLogging) + GET(fmi2Instantiate) + GET(fmi2FreeInstance) + GET(fmi2SetupExperiment) + GET(fmi2EnterInitializationMode) + GET(fmi2ExitInitializationMode) + GET(fmi2Terminate) + GET(fmi2Reset) + GET(fmi2GetReal) + GET(fmi2GetInteger) + GET(fmi2GetBoolean) + GET(fmi2GetString) + GET(fmi2SetReal) + GET(fmi2SetInteger) + GET(fmi2SetBoolean) + GET(fmi2SetString) + GET(fmi2GetFMUstate) + GET(fmi2SetFMUstate) + GET(fmi2FreeFMUstate) + GET(fmi2SerializedFMUstateSize) + GET(fmi2SerializeFMUstate) + GET(fmi2DeSerializeFMUstate) + GET(fmi2GetDirectionalDerivative) + + GET(fmi2EnterEventMode) + GET(fmi2NewDiscreteStates) + GET(fmi2EnterContinuousTimeMode) + GET(fmi2CompletedIntegratorStep) + GET(fmi2SetTime) + GET(fmi2SetContinuousStates) + GET(fmi2GetDerivatives) + GET(fmi2GetEventIndicators) + GET(fmi2GetContinuousStates) + GET(fmi2GetNominalsOfContinuousStates) + + m->c = m->fmi2Instantiate(instanceName, fmi2ModelExchange, fmuGUID, fmuResourceLocation, functions, visible, loggingOn); + ASSERT_NOT_NULL(m->c) + + if (m->nx > 0) { + m->x = N_VNew_Serial(m->nx); + m->abstol = N_VNew_Serial(m->nx); + for (size_t i = 0; i < m->nx; i++) { + NV_DATA_S(m->abstol)[i] = RTOL; + } + m->A = SUNDenseMatrix(m->nx, m->nx); + } else { + m->x = N_VNew_Serial(1); + m->abstol = N_VNew_Serial(1); + NV_DATA_S(m->abstol)[0] = RTOL; + m->A = SUNDenseMatrix(1, 1); + } + + m->cvode_mem = CVodeCreate(CV_BDF); + + int flag; + + flag = CVodeInit(m->cvode_mem, f, 0, m->x); + ASSERT_CV_SUCCESS(flag) + + flag = CVodeSVtolerances(m->cvode_mem, RTOL, m->abstol); + ASSERT_CV_SUCCESS(flag) + + if (m->nz > 0) { + flag = CVodeRootInit(m->cvode_mem, (int)m->nz, g); + ASSERT_CV_SUCCESS(flag) + } + + m->LS = SUNLinSol_Dense(m->x, m->A); + + flag = CVodeSetLinearSolver(m->cvode_mem, m->LS, m->A); + ASSERT_CV_SUCCESS(flag) + + flag = CVodeSetNoInactiveRootWarn(m->cvode_mem); + ASSERT_CV_SUCCESS(flag) + + flag = CVodeSetErrHandlerFn(m->cvode_mem, ehfun, NULL); + ASSERT_CV_SUCCESS(flag) + + flag = CVodeSetUserData(m->cvode_mem, m); + ASSERT_CV_SUCCESS(flag) + + return m; +} + +void fmi2FreeInstance(fmi2Component c) { + + if (!c) return; + Model *m = (Model *)c; + +#ifdef _WIN32 + FreeLibrary(m->libraryHandle); +#else + dlclose(m->libraryHandle); +#endif + + free((void *)m->instanceName); + + /* Free y and abstol vectors */ + N_VDestroy(m->x); + N_VDestroy(m->abstol); + + /* Free integrator memory */ + CVodeFree(&m->cvode_mem); + + /* Free the linear solver memory */ + SUNLinSolFree(m->LS); + + /* Free the matrix memory */ + SUNMatDestroy(m->A); + + free(m); +} + +/* Enter and exit initialization mode, terminate and reset */ +fmi2Status fmi2SetupExperiment(fmi2Component c, + fmi2Boolean toleranceDefined, + fmi2Real tolerance, + fmi2Real startTime, + fmi2Boolean stopTimeDefined, + fmi2Real stopTime) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2SetupExperiment(m->c, toleranceDefined, tolerance, startTime, stopTimeDefined, stopTime); +} + +fmi2Status fmi2EnterInitializationMode(fmi2Component c) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2EnterInitializationMode(m->c); +} + +fmi2Status fmi2ExitInitializationMode(fmi2Component c) { + if (!c) { return fmi2Error; } + Model *m = (Model *)c; + fmi2Status status; + + status = m->fmi2ExitInitializationMode(m->c); + if (status > fmi2Warning) { return status; } + + status = m->fmi2NewDiscreteStates(m->c, &m->eventInfo); + if (status > fmi2Warning) { return status; } + + status = m->fmi2EnterContinuousTimeMode(m->c); + if (status > fmi2Warning) { return status; } + + return status; +} + +fmi2Status fmi2Terminate(fmi2Component c) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2Terminate(m->c); +} + +fmi2Status fmi2Reset(fmi2Component c) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + // TODO: reset solver + return m->fmi2Reset(m->c); +} + +/* Getting and setting variable values */ +fmi2Status fmi2GetReal(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, fmi2Real value[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2GetReal(m->c, vr, nvr, value); +} + +fmi2Status fmi2GetInteger(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, fmi2Integer value[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2GetInteger(m->c, vr, nvr, value); +} + +fmi2Status fmi2GetBoolean(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, fmi2Boolean value[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2GetBoolean(m->c, vr, nvr, value); +} + +fmi2Status fmi2GetString(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, fmi2String value[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2GetString(m->c, vr, nvr, value); +} + +fmi2Status fmi2SetReal(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, const fmi2Real value[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2SetReal(m->c, vr, nvr, value); +} + +fmi2Status fmi2SetInteger(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, const fmi2Integer value[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2SetInteger(m->c, vr, nvr, value); +} + +fmi2Status fmi2SetBoolean(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, const fmi2Boolean value[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2SetBoolean(m->c, vr, nvr, value); +} + +fmi2Status fmi2SetString(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, const fmi2String value[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2SetString(m->c, vr, nvr, value); +} + +/* Getting and setting the internal FMU state */ +fmi2Status fmi2GetFMUstate(fmi2Component c, fmi2FMUstate* FMUstate) { + return fmi2Error; +} + +fmi2Status fmi2SetFMUstate(fmi2Component c, fmi2FMUstate FMUstate) { + return fmi2Error; +} + +fmi2Status fmi2FreeFMUstate(fmi2Component c, fmi2FMUstate* FMUstate) { + return fmi2Error; +} + +fmi2Status fmi2SerializedFMUstateSize(fmi2Component c, fmi2FMUstate FMUstate, size_t* size) { + return fmi2Error; +} + +fmi2Status fmi2SerializeFMUstate(fmi2Component c, fmi2FMUstate FMUstate, fmi2Byte serializedState[], size_t size) { + return fmi2Error; +} + +fmi2Status fmi2DeSerializeFMUstate(fmi2Component c, const fmi2Byte serializedState[], size_t size, fmi2FMUstate* FMUstate) { + return fmi2Error; +} + +/* Getting partial derivatives */ +fmi2Status fmi2GetDirectionalDerivative(fmi2Component c, + const fmi2ValueReference vUnknown_ref[], size_t nUnknown, + const fmi2ValueReference vKnown_ref[], size_t nKnown, + const fmi2Real dvKnown[], + fmi2Real dvUnknown[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2GetDirectionalDerivative(m->c, vUnknown_ref, nUnknown, vKnown_ref, nKnown, dvKnown, dvUnknown); +} + +/*************************************************** +Types for Functions for FMI2 for Co-Simulation +****************************************************/ + +/* Simulating the slave */ +fmi2Status fmi2SetRealInputDerivatives(fmi2Component c, + const fmi2ValueReference vr[], size_t nvr, + const fmi2Integer order[], + const fmi2Real value[]) { + return fmi2Error; +} +fmi2Status fmi2GetRealOutputDerivatives(fmi2Component c, + const fmi2ValueReference vr[], size_t nvr, + const fmi2Integer order[], + fmi2Real value[]) { + return fmi2Error; +} + + + +fmi2Status fmi2DoStep(fmi2Component c, + fmi2Real currentCommunicationPoint, + fmi2Real communicationStepSize, + fmi2Boolean noSetFMUStatePriorToCurrentPoint) { + + if (!c) return fmi2Error; + Model *m = (Model *)c; + + fmi2Status status; + + realtype tret = currentCommunicationPoint; + realtype tNext = currentCommunicationPoint + communicationStepSize; + realtype epsilon = (1.0 + fabs(tNext)) * EPSILON; + + if (m->nx > 0) { + status = m->fmi2GetContinuousStates(m->c, NV_DATA_S(m->x), NV_LENGTH_S(m->x)); + if (status > fmi2Warning) return status; + } + + while (tret + epsilon < tNext) { + + realtype tout = tNext; + + if (m->eventInfo.nextEventTimeDefined && m->eventInfo.nextEventTime < tNext) { + tout = m->eventInfo.nextEventTime; + } + + int flag = CVode(m->cvode_mem, tout, m->x, &tret, CV_NORMAL); + + if (flag < 0) { + // TODO: ehfn() + return fmi2Error; + } + + status = m->fmi2SetTime(m->c, tret); + if (status > fmi2Warning) return status; + + if (m->nx > 0) { + status = m->fmi2SetContinuousStates(m->c, NV_DATA_S(m->x), NV_LENGTH_S(m->x)); + if (status > fmi2Warning) return status; + } + + fmi2Boolean enterEventMode, terminateSimulation; + + status = m->fmi2CompletedIntegratorStep(m->c, fmi2False, &enterEventMode, &terminateSimulation); + if (status > fmi2Warning) return status; + + if (terminateSimulation) return fmi2Error; + + if (flag == CV_ROOT_RETURN || enterEventMode || (m->eventInfo.nextEventTimeDefined && m->eventInfo.nextEventTime == tret)) { + + m->fmi2EnterEventMode(m->c); + if (status > fmi2Warning) return status; + + do { + m->fmi2NewDiscreteStates(m->c, &m->eventInfo); + if (status > fmi2Warning) return status; + } while (m->eventInfo.newDiscreteStatesNeeded && !m->eventInfo.terminateSimulation); + + m->fmi2EnterContinuousTimeMode(m->c); + if (status > fmi2Warning) return status; + + if (m->nx > 0 && m->eventInfo.valuesOfContinuousStatesChanged) { + status = m->fmi2GetContinuousStates(m->c, NV_DATA_S(m->x), NV_LENGTH_S(m->x)); + if (status > fmi2Warning) return status; + } + + flag = CVodeReInit(m->cvode_mem, tret, m->x); + if (flag < 0) return fmi2Error; + } + + } + + return status; +} + +fmi2Status fmi2CancelStep(fmi2Component c) { + return fmi2Error; +} + +/* Inquire slave status */ +fmi2Status fmi2GetStatus(fmi2Component c, const fmi2StatusKind s, fmi2Status* value) { + return fmi2Error; +} + +fmi2Status fmi2GetRealStatus(fmi2Component c, const fmi2StatusKind s, fmi2Real* value) { + return fmi2Error; +} + +fmi2Status fmi2GetIntegerStatus(fmi2Component c, const fmi2StatusKind s, fmi2Integer* value) { + return fmi2Error; +} + +fmi2Status fmi2GetBooleanStatus(fmi2Component c, const fmi2StatusKind s, fmi2Boolean* value) { + return fmi2Error; +} + +fmi2Status fmi2GetStringStatus(fmi2Component c, const fmi2StatusKind s, fmi2String* value) { + return fmi2Error; +} diff --git a/cswrapper/cswrapper_test.c b/cswrapper/cswrapper_test.c new file mode 100644 index 00000000..8c7a36a7 --- /dev/null +++ b/cswrapper/cswrapper_test.c @@ -0,0 +1,238 @@ +#ifdef _WIN32 +#include +#else +#include +#endif +#include +#include + +#include "fmi2Functions.h" + +typedef struct { + +#if defined(_WIN32) + HMODULE libraryHandle; +#else + void *libraryHandle; +#endif + + fmi2Component c; + + /*************************************************** + Common Functions + ****************************************************/ + fmi2GetTypesPlatformTYPE *fmi2GetTypesPlatform; + fmi2GetVersionTYPE *fmi2GetVersion; + fmi2SetDebugLoggingTYPE *fmi2SetDebugLogging; + fmi2InstantiateTYPE *fmi2Instantiate; + fmi2FreeInstanceTYPE *fmi2FreeInstance; + fmi2SetupExperimentTYPE *fmi2SetupExperiment; + fmi2EnterInitializationModeTYPE *fmi2EnterInitializationMode; + fmi2ExitInitializationModeTYPE *fmi2ExitInitializationMode; + fmi2TerminateTYPE *fmi2Terminate; + fmi2ResetTYPE *fmi2Reset; + fmi2GetRealTYPE *fmi2GetReal; + fmi2GetIntegerTYPE *fmi2GetInteger; + fmi2GetBooleanTYPE *fmi2GetBoolean; + fmi2GetStringTYPE *fmi2GetString; + fmi2SetRealTYPE *fmi2SetReal; + fmi2SetIntegerTYPE *fmi2SetInteger; + fmi2SetBooleanTYPE *fmi2SetBoolean; + fmi2SetStringTYPE *fmi2SetString; + fmi2GetFMUstateTYPE *fmi2GetFMUstate; + fmi2SetFMUstateTYPE *fmi2SetFMUstate; + fmi2FreeFMUstateTYPE *fmi2FreeFMUstate; + fmi2SerializedFMUstateSizeTYPE *fmi2SerializedFMUstateSize; + fmi2SerializeFMUstateTYPE *fmi2SerializeFMUstate; + fmi2DeSerializeFMUstateTYPE *fmi2DeSerializeFMUstate; + fmi2GetDirectionalDerivativeTYPE *fmi2GetDirectionalDerivative; + + + /*************************************************** + Functions for FMI2 for Model Exchange + ****************************************************/ + fmi2EnterEventModeTYPE *fmi2EnterEventMode; + fmi2NewDiscreteStatesTYPE *fmi2NewDiscreteStates; + fmi2EnterContinuousTimeModeTYPE *fmi2EnterContinuousTimeMode; + fmi2CompletedIntegratorStepTYPE *fmi2CompletedIntegratorStep; + fmi2SetTimeTYPE *fmi2SetTime; + fmi2SetContinuousStatesTYPE *fmi2SetContinuousStates; + fmi2GetDerivativesTYPE *fmi2GetDerivatives; + fmi2GetEventIndicatorsTYPE *fmi2GetEventIndicators; + fmi2GetContinuousStatesTYPE *fmi2GetContinuousStates; + fmi2GetNominalsOfContinuousStatesTYPE *fmi2GetNominalsOfContinuousStates; + +} Model; + +/*************************************************** +Types for Common Functions +****************************************************/ + +/* Inquire version numbers of header files and setting logging status */ +const char* fmi2GetTypesPlatform(void) { return fmi2TypesPlatform; } + +const char* fmi2GetVersion(void) { return fmi2Version; } + +fmi2Status fmi2SetDebugLogging(fmi2Component c, fmi2Boolean loggingOn, size_t nCategories, const fmi2String categories[]) { + if (!c) return fmi2Error; + Model *m = (Model *)c; + return m->fmi2SetDebugLogging(c, loggingOn, nCategories, categories); +} + +#define GET(f) m->f = dlsym(m->libraryHandle, #f); + +/* Creation and destruction of FMU instances and setting debug status */ +fmi2Component fmi2Instantiate(fmi2String instanceName, + fmi2Type fmuType, + fmi2String fmuGUID, + fmi2String fmuResourceLocation, + const fmi2CallbackFunctions* functions, + fmi2Boolean visible, + fmi2Boolean loggingOn) { + + if (fmuType != fmi2CoSimulation) { + if (functions && functions->logger) { + functions->logger(NULL, instanceName, fmi2Error, "logError", "Argument fmuType must be fmi2CoSimulation."); + } + return NULL; + } + + Model *m = calloc(1, sizeof(Model)); + + const char *dll = "/Users/tors10/Development/Reference-FMUs/build/temp/VanDerPol/binaries/darwin64/VanDerPol.dylib"; + + m->libraryHandle = dlopen(dll, RTLD_LAZY); + + GET(fmi2GetTypesPlatform) + GET(fmi2GetVersion) + GET(fmi2SetDebugLogging) + GET(fmi2Instantiate) + GET(fmi2FreeInstance) + GET(fmi2SetupExperiment) + GET(fmi2EnterInitializationMode) + GET(fmi2ExitInitializationMode) + GET(fmi2Terminate) + GET(fmi2Reset) + GET(fmi2GetReal) + GET(fmi2GetInteger) + GET(fmi2GetBoolean) + GET(fmi2GetString) + GET(fmi2SetReal) + GET(fmi2SetInteger) + GET(fmi2SetBoolean) + GET(fmi2SetString) + GET(fmi2GetFMUstate) + GET(fmi2SetFMUstate) + GET(fmi2FreeFMUstate) + GET(fmi2SerializedFMUstateSize) + GET(fmi2SerializeFMUstate) + GET(fmi2DeSerializeFMUstate) + GET(fmi2GetDirectionalDerivative) + + GET(fmi2EnterEventMode) + GET(fmi2NewDiscreteStates) + GET(fmi2EnterContinuousTimeMode) + GET(fmi2CompletedIntegratorStep) + GET(fmi2SetTime) + GET(fmi2SetContinuousStates) + GET(fmi2GetDerivatives) + GET(fmi2GetEventIndicators) + GET(fmi2GetContinuousStates) + GET(fmi2GetNominalsOfContinuousStates) + + m->c = m->fmi2Instantiate(instanceName, fmuType, fmuGUID, fmuResourceLocation, functions, visible, loggingOn); + + return m; + +} + +void fmi2FreeInstance(fmi2Component c) { + if (!c) return; + Model *m = (Model *)c; + dlclose(m->libraryHandle); + free(m); +} + + +/*************************************************** +Types for Functions for FMI2 for Co-Simulation +****************************************************/ + +/* Simulating the slave */ +fmi2Status fmi2SetRealInputDerivatives(fmi2Component c, + const fmi2ValueReference vr[], size_t nvr, + const fmi2Integer order[], + const fmi2Real value[]) { + return fmi2Error; +} +fmi2Status fmi2GetRealOutputDerivatives(fmi2Component c, + const fmi2ValueReference vr[], size_t nvr, + const fmi2Integer order[], + fmi2Real value[]) { + return fmi2Error; +} + +fmi2Status fmi2DoStep(fmi2Component c, + fmi2Real currentCommunicationPoint, + fmi2Real communicationStepSize, + fmi2Boolean noSetFMUStatePriorToCurrentPoint) { + return fmi2Error; +} + +fmi2Status fmi2CancelStep(fmi2Component c) { + return fmi2Error; +} + +/* Inquire slave status */ +fmi2Status fmi2GetStatus(fmi2Component c, const fmi2StatusKind s, fmi2Status* value) { + return fmi2Error; +} + +fmi2Status fmi2GetRealStatus(fmi2Component c, const fmi2StatusKind s, fmi2Real* value) { + return fmi2Error; +} + +fmi2Status fmi2GetIntegerStatus(fmi2Component c, const fmi2StatusKind s, fmi2Integer* value) { + return fmi2Error; +} + +fmi2Status fmi2GetBooleanStatus(fmi2Component c, const fmi2StatusKind s, fmi2Boolean* value) { + return fmi2Error; +} + +fmi2Status fmi2GetStringStatus(fmi2Component c, const fmi2StatusKind s, fmi2String* value) { + return fmi2Error; +} + +static void cb_logMessage(fmi2ComponentEnvironment componentEnvironment, + fmi2String instanceName, + fmi2Status status, + fmi2String category, + fmi2String message, + ...) { + + puts(message); +} + +int main(int argc, char *argv[]) { + +// const char *dll = "/Users/tors10/Development/Reference-FMUs/build/temp/VanDerPol/binaries/darwin64/VanDerPol.dylib"; + + void *libraryHandle = dlopen("cswrapper.dylib", RTLD_LAZY); + + fmi2InstantiateTYPE *instantiate = dlsym(libraryHandle, "fmi2Instantiate"); + + fmi2CallbackFunctions functions = { .logger = cb_logMessage }; + + fmi2Component c = instantiate("instance1", fmi2CoSimulation, "{8c4e810f-3da3-4a00-8276-176fa3c9f000}", "", &functions, fmi2False, fmi2False); + + dlclose(libraryHandle); + +// fmi2CallbackFunctions functions = { .logger = cb_logMessage }; +// +// fmi2Component c = fmi2Instantiate("instance1", fmi2CoSimulation, "{8c4e810f-3da3-4a00-8276-176fa3c9f000}", "", &functions, fmi2False, fmi2False); +// +// fmi2FreeInstance(c); + + return 0; +} diff --git a/docs/changelog.md b/docs/changelog.md index 7123d6ab..0ee7fbdc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,17 @@ +## v0.2.21 (2020-06-29) + +- `FIXED` Set inputs in CVode root function before getting event indicators (#150) +- `FIXED` Add scipy to required packages for fmpy[plot] (#146) +- `FIXED` Activate conda environment in file open command and desktop shortcut (#131) +- `FIXED` Evaluate terminateSimulation in simulation loop (#145) +- `FIXED` Fix return value of FMU1Model.completedIntegratorStep() +- `FIXED` Add Dimension class and calculate initial shape of FMI 3 model variables +- `FIXED` Raise an exception when a missing FMI function is called (#139) +- `NEW` Update FMI 3 API to v3.0-alpha.4 +- `NEW` Validate model structure in read_model_description() +- `NEW` Add "create-cmake-project" command to CLI (#129) +- `NEW` Add Co-Simulation wrapper and build binaries in CI (#127) + ## v0.2.20 (2020-05-23) - `FIXED` Fix fmi3Functions.h for compile_platform_binary() diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 00000000..534322d1 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,10 @@ +## How do I install a development build? + +- go to the [CI server](https://dev.azure.com/CATIA-Systems/FMPy/) +- select `Pipelines > Pipelines` +- on the `Runs` page select the commit you want +- on the `Summary` tab click `N published` under `Related` (where `N` is the number of artifacts) +- hover over your platform (e.g. `windows-python-3.6`), click on `...` and select `Download artifacts` +- extract the ZIP archive +- open a command line and change into directory where you extracted the archive +- on the command line run `python -m pip install ` (where `` is the path of the FMPy Wheel (*.whl)) diff --git a/fmpy/__init__.py b/fmpy/__init__.py index 9e8e0e87..128ce366 100644 --- a/fmpy/__init__.py +++ b/fmpy/__init__.py @@ -5,7 +5,7 @@ from ctypes import * import _ctypes -__version__ = '0.2.20' +__version__ = '0.2.21' # determine the platform diff --git a/fmpy/c-code/fmi3FunctionTypes.h b/fmpy/c-code/fmi3FunctionTypes.h index 1db5a3da..bd42d7e3 100644 --- a/fmpy/c-code/fmi3FunctionTypes.h +++ b/fmpy/c-code/fmi3FunctionTypes.h @@ -188,7 +188,6 @@ typedef fmi3Status fmi3ExitInitializationModeTYPE(fmi3Instance instance); /* tag::EnterEventMode[] */ typedef fmi3Status fmi3EnterEventModeTYPE(fmi3Instance instance, - fmi3Boolean inputEvent, fmi3Boolean stepEvent, const fmi3Int32 rootsFound[], size_t nEventIndicators, @@ -532,39 +531,43 @@ typedef fmi3Status fmi3SetTimeTYPE(fmi3Instance instance, fmi3Float64 time); /* tag::SetContinuousStates[] */ typedef fmi3Status fmi3SetContinuousStatesTYPE(fmi3Instance instance, - const fmi3Float64 x[], - size_t nx); + const fmi3Float64 continuousStates[], + size_t nContinuousStates); /* end::SetContinuousStates[] */ /* Evaluation of the model equations */ /* tag::GetDerivatives[] */ typedef fmi3Status fmi3GetDerivativesTYPE(fmi3Instance instance, fmi3Float64 derivatives[], - size_t nx); + size_t nCcontinuousStates); /* end::GetDerivatives[] */ /* tag::GetEventIndicators[] */ typedef fmi3Status fmi3GetEventIndicatorsTYPE(fmi3Instance instance, fmi3Float64 eventIndicators[], - size_t ni); + size_t nEventIndicators); /* end::GetEventIndicators[] */ /* tag::GetContinuousStates[] */ -typedef fmi3Status fmi3GetContinuousStatesTYPE(fmi3Instance instance, fmi3Float64 x[], size_t nx); +typedef fmi3Status fmi3GetContinuousStatesTYPE(fmi3Instance instance, + fmi3Float64 continuousStates[], + size_t nContinuousStates); /* end::GetContinuousStates[] */ /* tag::GetNominalsOfContinuousStates[] */ typedef fmi3Status fmi3GetNominalsOfContinuousStatesTYPE(fmi3Instance instance, fmi3Float64 nominals[], - size_t nx); + size_t nContinuousStates); /* end::GetNominalsOfContinuousStates[] */ /* tag::GetNumberOfEventIndicators[] */ -typedef fmi3Status fmi3GetNumberOfEventIndicatorsTYPE(fmi3Instance instance, size_t* nz); +typedef fmi3Status fmi3GetNumberOfEventIndicatorsTYPE(fmi3Instance instance, + size_t* nEventIndicators); /* end::GetNumberOfEventIndicators[] */ /* tag::GetNumberOfContinuousStates[] */ -typedef fmi3Status fmi3GetNumberOfContinuousStatesTYPE(fmi3Instance instance, size_t* nx); +typedef fmi3Status fmi3GetNumberOfContinuousStatesTYPE(fmi3Instance instance, + size_t* nContinuousStates); /* end::GetNumberOfContinuousStates[] */ /*************************************************** diff --git a/fmpy/command_line.py b/fmpy/command_line.py index b93ca36d..36c48685 100644 --- a/fmpy/command_line.py +++ b/fmpy/command_line.py @@ -27,7 +27,8 @@ def main(): parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent(description)) - parser.add_argument('command', choices=['info', 'validate', 'simulate', 'compile', 'add-remoting'], help="Command to execute") + parser.add_argument('command', choices=['info', 'validate', 'simulate', 'compile', 'add-cswrapper', 'add-remoting', 'create-cmake-project'], + help="Command to execute") parser.add_argument('fmu_filename', help="filename of the FMU") parser.add_argument('--validate', action='store_true', help="validate the FMU") @@ -48,6 +49,7 @@ def main(): parser.add_argument('--visible', action='store_true', help="enable interactive mode") parser.add_argument('--fmi-logging', action='store_true', help="enable FMI logging") parser.add_argument('--show-plot', action='store_true', help="plot the results") + parser.add_argument('--cmake-project-dir', help="Directory for the CMake project") args = parser.parse_args() @@ -64,9 +66,9 @@ def main(): messages = validate_fmu(args.fmu_filename) if len(messages) == 0: - print('The validation passed') + print('No problems found.') else: - print('The following errors were found:') + print('The following problems were found:') for message in messages: print() print(message) @@ -77,11 +79,30 @@ def main(): from fmpy.util import compile_platform_binary compile_platform_binary(args.fmu_filename) + elif args.command == 'add-cswrapper': + + from fmpy.cswrapper import add_cswrapper + add_cswrapper(args.fmu_filename) + elif args.command == 'add-remoting': from fmpy.util import add_remoting add_remoting(args.fmu_filename) + elif args.command == 'create-cmake-project': + + import os + from fmpy.util import create_cmake_project + + project_dir = args.cmake_project_dir + + if project_dir is None: + project_dir = os.path.basename(args.fmu_filename) + project_dir, _ = os.path.splitext(project_dir) + print("Creating CMake project in %s" % os.path.abspath(project_dir)) + + create_cmake_project(args.fmu_filename, project_dir) + elif args.command == 'simulate': from fmpy import simulate_fmu diff --git a/fmpy/cswrapper/__init__.py b/fmpy/cswrapper/__init__.py new file mode 100644 index 00000000..66b1a4d5 --- /dev/null +++ b/fmpy/cswrapper/__init__.py @@ -0,0 +1,87 @@ + + +def add_cswrapper(filename, outfilename=None): + + from fmpy import read_model_description, extract, sharedLibraryExtension, platform, __version__ + from lxml import etree + import os + from shutil import copyfile, rmtree + + if outfilename is None: + outfilename = filename + + model_description = read_model_description(filename) + + if model_description.fmiVersion != '2.0': + raise Exception("%s is not an FMI 2.0 FMU." % filename) + + if model_description.modelExchange is None: + raise Exception("%s does not support Model Exchange." % filename) + + unzipdir = extract(filename) + + xml = os.path.join(unzipdir, 'modelDescription.xml') + + tree = etree.parse(xml) + + root = tree.getroot() + + # update description + generation_tool = root.attrib.get('generationTool', 'Unknown') + " with FMPy %s Co-Simulation wrapper" % __version__ + root.attrib['generationTool'] = generation_tool + + # remove any existing element + for e in root.findall('CoSimulation'): + root.remove(e) + + for i, child in enumerate(root): + if child.tag == 'ModelExchange': + break + + model_identifier = '%s_%s_%s' % (model_description.modelExchange.modelIdentifier, + model_description.numberOfContinuousStates, + model_description.numberOfEventIndicators) + + e = etree.Element("CoSimulation") + e.attrib['modelIdentifier'] = model_identifier + root.insert(i + 1, e) + + tree.write(xml, pretty_print=True, encoding='utf-8') + + shared_library = os.path.join(os.path.dirname(__file__), 'cswrapper' + sharedLibraryExtension) + license_file = os.path.join(os.path.dirname(__file__), 'license.txt') + + licenses_dir = os.path.join(unzipdir, 'documentation', 'licenses') + + if not os.path.isdir(licenses_dir): + os.mkdir(licenses_dir) + + copyfile(src=shared_library, dst=os.path.join(unzipdir, 'binaries', platform, model_identifier + sharedLibraryExtension)) + copyfile(license_file, os.path.join(unzipdir, 'documentation', 'licenses', 'fmpy-cswrapper.txt')) + + create_zip_archive(outfilename, unzipdir) + + rmtree(unzipdir, ignore_errors=True) + + +def create_zip_archive(filename, source_dir): + + import zipfile + import os + + with zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED) as zf: + base_path = os.path.normpath(source_dir) + for dirpath, dirnames, filenames in os.walk(source_dir): + for name in sorted(dirnames): + path = os.path.normpath(os.path.join(dirpath, name)) + zf.write(path, os.path.relpath(path, base_path)) + for name in filenames: + path = os.path.normpath(os.path.join(dirpath, name)) + if os.path.isfile(path): + zf.write(path, os.path.relpath(path, base_path)) + + +if __name__ is '__main__': + + add_cswrapper('/Users/tors10/Development/Reference-FMUs/build/dist/Dahlquist.fmu', + '/Users/tors10/Development/Reference-FMUs/build/dist/DahlquistCS1.fmu') diff --git a/fmpy/cswrapper/license.txt b/fmpy/cswrapper/license.txt new file mode 100644 index 00000000..554bb5cb --- /dev/null +++ b/fmpy/cswrapper/license.txt @@ -0,0 +1,66 @@ +FMPy Co-Simulation Wrapper License +================================== + +The Co-Simulation Wrapper binaries (binaries//__.{dll|dylib|so}) +are part of FMPy (https://github.com/CATIA-Systems/FMPy) and released under the 2-Clause BSD license: + +Copyright (c) 2017-2020 Dassault Systemes. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------------------------------------- + +The FMPy Co-Simulation Wrapper uses CVode which is part of the SUNDIALS library +(https://computing.llnl.gov/projects/sundials) that is released under the +3-Clause BSD license: + +BSD 3-Clause License + +Copyright (c) 2002-2019, Lawrence Livermore National Security and Southern Methodist University. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/fmpy/examples/efficient_loops.py b/fmpy/examples/efficient_loops.py index 3027c16a..8026153f 100644 --- a/fmpy/examples/efficient_loops.py +++ b/fmpy/examples/efficient_loops.py @@ -44,5 +44,5 @@ def run_efficient_loop(): shutil.rmtree(unzipdir, ignore_errors=True) -if __name__ is '__main__': +if __name__ == '__main__': run_efficient_loop() diff --git a/fmpy/fmi1.py b/fmpy/fmi1.py index 45471dc0..8594e3af 100644 --- a/fmpy/fmi1.py +++ b/fmpy/fmi1.py @@ -527,8 +527,6 @@ def __init__(self, **kwargs): super(FMU1Model, self).__init__(**kwargs) - self.eventInfo = fmi1EventInfo() - # Inquire version numbers of header files self._fmi1Function('GetModelTypesPlatform', [], [], fmi1String) @@ -624,12 +622,19 @@ def setContinuousStates(self, states, size): def completedIntegratorStep(self): stepEvent = fmi1Boolean() self.fmi1CompletedIntegratorStep(self.component, byref(stepEvent)) - return stepEvent != fmi1False + return stepEvent.value != fmi1False # Evaluation of the model equations def initialize(self, toleranceControlled=fmi1False, relativeTolerance=0.0): - return self.fmi1Initialize(self.component, toleranceControlled, relativeTolerance, byref(self.eventInfo)) + eventInfo = fmi1EventInfo() + self.fmi1Initialize(self.component, toleranceControlled, relativeTolerance, byref(eventInfo)) + return (eventInfo.iterationConverged != fmi1False, + eventInfo.stateValueReferencesChanged != fmi1False, + eventInfo.stateValuesChanged != fmi1False, + eventInfo.terminateSimulation != fmi1False, + eventInfo.upcomingTimeEvent != fmi1False, + eventInfo.nextEventTime) def getDerivatives(self, derivatives, size): return self.fmi1GetDerivatives(self.component, derivatives, size) @@ -638,7 +643,14 @@ def getEventIndicators(self, eventIndicators, size): return self.fmi1GetEventIndicators(self.component, eventIndicators, size) def eventUpdate(self, intermediateResults=fmi1False): - return self.fmi1EventUpdate(self.component, intermediateResults, byref(self.eventInfo)) + eventInfo = fmi1EventInfo() + self.fmi1EventUpdate(self.component, intermediateResults, byref(eventInfo)) + return (eventInfo.iterationConverged != fmi1False, + eventInfo.stateValueReferencesChanged != fmi1False, + eventInfo.stateValuesChanged != fmi1False, + eventInfo.terminateSimulation != fmi1False, + eventInfo.upcomingTimeEvent != fmi1False, + eventInfo.nextEventTime) def getContinuousStates(self, states, size): return self.fmi1GetContinuousStates(self.component, states, size) diff --git a/fmpy/fmi2.py b/fmpy/fmi2.py index 5b2a9bfd..80ecc11d 100644 --- a/fmpy/fmi2.py +++ b/fmpy/fmi2.py @@ -186,7 +186,12 @@ def _fmi2Function(self, fname, argnames, argtypes, restype=fmi2Status): """ if not hasattr(self.dll, fname): - setattr(self, fname, None) + + def raise_exception(*args): + raise Exception("Function %s is missing in shared library." % fname) + + setattr(self, fname, raise_exception) + return # get the exported function form the shared library @@ -408,8 +413,6 @@ def __init__(self, **kwargs): super(FMU2Model, self).__init__(**kwargs) - self.eventInfo = fmi2EventInfo() - self._fmi2Function('fmi2NewDiscreteStates', ['component', 'eventInfo'], [fmi2Component, POINTER(fmi2EventInfo)]) @@ -452,7 +455,17 @@ def enterEventMode(self): return self.fmi2EnterEventMode(self.component) def newDiscreteStates(self): - return self.fmi2NewDiscreteStates(self.component, byref(self.eventInfo)) + + eventInfo = fmi2EventInfo() + + self.fmi2NewDiscreteStates(self.component, byref(eventInfo)) + + return (eventInfo.newDiscreteStatesNeeded != fmi2False, + eventInfo.terminateSimulation != fmi2False, + eventInfo.nominalsOfContinuousStatesChanged != fmi2False, + eventInfo.valuesOfContinuousStatesChanged != fmi2False, + eventInfo.nextEventTimeDefined != fmi2False, + eventInfo.nextEventTime) def enterContinuousTimeMode(self): return self.fmi2EnterContinuousTimeMode(self.component) diff --git a/fmpy/fmi3.py b/fmpy/fmi3.py index 06860653..42431689 100644 --- a/fmpy/fmi3.py +++ b/fmpy/fmi3.py @@ -112,7 +112,6 @@ def __init__(self, **kwargs): self._fmi3Function('fmi3EnterEventMode', [ (fmi3Instance, 'instance'), - (fmi3Boolean, 'inputEvent'), (fmi3Boolean, 'stepEvent'), (POINTER(fmi3Int32), 'rootsFound'), (c_size_t, 'nEventIndicators'), @@ -142,11 +141,11 @@ def __init__(self, **kwargs): for name, _type in types: params = [ - (fmi3Instance, 'instance'), + (fmi3Instance, 'instance'), (POINTER(fmi3ValueReference), 'vr'), - (c_size_t, 'nvr'), - (POINTER(_type), 'value'), - (c_size_t, 'nValues') + (c_size_t, 'nvr'), + (POINTER(_type), 'value'), + (c_size_t, 'nValues') ] self._fmi3Function('fmi3Get' + name, params) @@ -217,12 +216,14 @@ def __init__(self, **kwargs): # Getting partial derivatives self._fmi3Function('fmi3GetDirectionalDerivative', [ (fmi3Instance, 'instance'), - (POINTER(fmi3ValueReference), 'vUnknown_ref'), - (c_size_t, 'nUnknown'), - (POINTER(fmi3ValueReference), 'vKnown_ref'), - (c_size_t, 'nKnown'), - (POINTER(fmi3Float64), 'dvKnown'), - (POINTER(fmi3Float64), 'dvUnknown') + (POINTER(fmi3ValueReference), 'unknowns'), + (c_size_t, 'nUnknowns'), + (POINTER(fmi3ValueReference), 'knowns'), + (c_size_t, 'nKnowns'), + (POINTER(fmi3Float64), 'seed'), + (c_size_t, 'nSeed'), + (POINTER(fmi3Float64), 'sensitivity'), + (c_size_t, 'nSensitivity') ]) # Entering and exiting the Configuration or Reconfiguration Mode @@ -302,7 +303,12 @@ def _fmi3Function(self, fname, params, restype=fmi3Status): """ if not hasattr(self.dll, fname): - setattr(self, fname, None) + + def raise_exception(*args): + raise Exception("Function %s is missing in shared library." % fname) + + setattr(self, fname, raise_exception) + return if len(params) > 0: @@ -378,6 +384,14 @@ def reset(self): # Getting and setting variable values + def getFloat32(self, vr, nValues=None): + if nValues is None: + nValues = len(vr) + vr = (fmi3ValueReference * len(vr))(*vr) + values = (fmi3Float32 * nValues)() + self.fmi3GetFloat32(self.component, vr, len(vr), values, nValues) + return list(values) + def getFloat64(self, vr, nValues=None): if nValues is None: nValues = len(vr) @@ -386,6 +400,38 @@ def getFloat64(self, vr, nValues=None): self.fmi3GetFloat64(self.component, vr, len(vr), values, nValues) return list(values) + def getInt8(self, vr, nValues=None): + if nValues is None: + nValues = len(vr) + vr = (fmi3ValueReference * len(vr))(*vr) + value = (fmi3Int8 * nValues)() + self.fmi3GetInt8(self.component, vr, len(vr), value, nValues) + return list(value) + + def getUInt8(self, vr, nValues=None): + if nValues is None: + nValues = len(vr) + vr = (fmi3ValueReference * len(vr))(*vr) + value = (fmi3UInt8 * nValues)() + self.fmi3GetUInt8(self.component, vr, len(vr), value, nValues) + return list(value) + + def getInt16(self, vr, nValues=None): + if nValues is None: + nValues = len(vr) + vr = (fmi3ValueReference * len(vr))(*vr) + value = (fmi3Int16 * nValues)() + self.fmi3GetInt16(self.component, vr, len(vr), value, nValues) + return list(value) + + def getUInt16(self, vr, nValues=None): + if nValues is None: + nValues = len(vr) + vr = (fmi3ValueReference * len(vr))(*vr) + value = (fmi3UInt16 * nValues)() + self.fmi3GetUInt16(self.component, vr, len(vr), value, nValues) + return list(value) + def getInt32(self, vr, nValues=None): if nValues is None: nValues = len(vr) @@ -394,6 +440,22 @@ def getInt32(self, vr, nValues=None): self.fmi3GetInt32(self.component, vr, len(vr), value, nValues) return list(value) + def getUInt32(self, vr, nValues=None): + if nValues is None: + nValues = len(vr) + vr = (fmi3ValueReference * len(vr))(*vr) + value = (fmi3UInt32 * nValues)() + self.fmi3GetUInt32(self.component, vr, len(vr), value, nValues) + return list(value) + + def getInt64(self, vr, nValues=None): + if nValues is None: + nValues = len(vr) + vr = (fmi3ValueReference * len(vr))(*vr) + value = (fmi3Int64 * nValues)() + self.fmi3GetInt64(self.component, vr, len(vr), value, nValues) + return list(value) + def getUInt64(self, vr, nValues=None): if nValues is None: nValues = len(vr) @@ -416,12 +478,6 @@ def getString(self, vr): self.fmi3GetString(self.component, vr, len(vr), value) return list(value) - def getString(self, vr): - vr = (fmi3ValueReference * len(vr))(*vr) - value = (fmi3String * len(vr))() - self.fmi3GetString(self.component, vr, len(vr), value) - return list(value) - def getBinary(self, vr): vr = (fmi3ValueReference * len(vr))(*vr) value = (fmi3Binary * len(vr))() @@ -429,16 +485,56 @@ def getBinary(self, vr): self.fmi3GetBinary(self.component, vr, len(vr), size, value, len(value)) return list(value) + def setFloat32(self, vr, values): + vr = (fmi3ValueReference * len(vr))(*vr) + values = (fmi3Float32 * len(values))(*values) + self.fmi3SetFloat32(self.component, vr, len(vr), values, len(values)) + def setFloat64(self, vr, values): vr = (fmi3ValueReference * len(vr))(*vr) values = (fmi3Float64 * len(values))(*values) self.fmi3SetFloat64(self.component, vr, len(vr), values, len(values)) + def setInt8(self, vr, values): + vr = (fmi3ValueReference * len(vr))(*vr) + values = (fmi3Int8 * len(values))(*values) + self.fmi3SetInt8(self.component, vr, len(vr), values, len(values)) + + def setUInt8(self, vr, values): + vr = (fmi3ValueReference * len(vr))(*vr) + values = (fmi3UInt8 * len(values))(*values) + self.fmi3SetUInt8(self.component, vr, len(vr), values, len(values)) + + def setInt16(self, vr, values): + vr = (fmi3ValueReference * len(vr))(*vr) + values = (fmi3Int16 * len(values))(*values) + self.fmi3SetInt16(self.component, vr, len(vr), values, len(values)) + + def setUInt16(self, vr, values): + vr = (fmi3ValueReference * len(vr))(*vr) + values = (fmi3UInt16 * len(values))(*values) + self.fmi3SetUInt16(self.component, vr, len(vr), values, len(values)) + def setInt32(self, vr, values): vr = (fmi3ValueReference * len(vr))(*vr) values = (fmi3Int32 * len(values))(*values) self.fmi3SetInt32(self.component, vr, len(vr), values, len(values)) + def setUInt32(self, vr, values): + vr = (fmi3ValueReference * len(vr))(*vr) + values = (fmi3UInt32 * len(values))(*values) + self.fmi3SetUInt32(self.component, vr, len(vr), values, len(values)) + + def setInt64(self, vr, values): + vr = (fmi3ValueReference * len(vr))(*vr) + values = (fmi3Int64 * len(values))(*values) + self.fmi3SetInt64(self.component, vr, len(vr), values, len(values)) + + def setUInt64(self, vr, values): + vr = (fmi3ValueReference * len(vr))(*vr) + values = (fmi3UInt64 * len(values))(*values) + self.fmi3SetUInt64(self.component, vr, len(vr), values, len(values)) + def setBoolean(self, vr, values): vr = (fmi3ValueReference * len(vr))(*vr) values = (fmi3Boolean * len(values))(*values) @@ -553,14 +649,14 @@ def __init__(self, **kwargs): self._fmi3Function('fmi3SetContinuousStates', [ (fmi3Instance, 'instance'), - (POINTER(fmi3Float64), 'x'), - (c_size_t, 'nx') + (POINTER(fmi3Float64), 'continuousStates'), + (c_size_t, 'nContinuousStates') ]) self._fmi3Function('fmi3GetDerivatives', [ (fmi3Instance, 'instance'), (POINTER(fmi3Float64), 'derivatives'), - (c_size_t, 'nx') + (c_size_t, 'nContinuousStates') ]) self._fmi3Function('fmi3GetEventIndicators', [ @@ -571,24 +667,24 @@ def __init__(self, **kwargs): self._fmi3Function('fmi3GetContinuousStates', [ (fmi3Instance, 'instance'), - (POINTER(fmi3Float64), 'x'), - (c_size_t, 'nx') + (POINTER(fmi3Float64), 'continuousStates'), + (c_size_t, 'nContinuousStates') ]) self._fmi3Function('fmi3GetNominalsOfContinuousStates', [ (fmi3Instance, 'instance'), (POINTER(fmi3Float64), 'nominals'), - (c_size_t, 'nx') + (c_size_t, 'nContinuousStates') ]) self._fmi3Function('fmi3GetNumberOfEventIndicators', [ (fmi3Instance, 'instance'), - (POINTER(c_size_t), 'nz') + (POINTER(c_size_t), 'nEventIndicators') ]) self._fmi3Function('fmi3GetNumberOfContinuousStates', [ (fmi3Instance, 'instance'), - (POINTER(c_size_t), 'nx') + (POINTER(c_size_t), 'nContinuousStates') ]) def instantiate(self, visible=False, loggingOn=False): @@ -612,13 +708,12 @@ def instantiate(self, visible=False, loggingOn=False): # Enter and exit the different modes - def enterEventMode(self, inputEvent=False, stepEvent=False, rootsFound=[], timeEvent=False): + def enterEventMode(self, stepEvent=False, rootsFound=[], timeEvent=False): rootsFound = (fmi3Int32 * len(rootsFound))(*rootsFound) return self.fmi3EnterEventMode( self.component, - fmi3True if inputEvent else fmi3False, fmi3True if stepEvent else fmi3False, rootsFound, len(rootsFound), @@ -663,21 +758,21 @@ def completedIntegratorStep(self, noSetFMUStatePriorToCurrentPoint=fmi3True): def setTime(self, time): return self.fmi3SetTime(self.component, time) - def setContinuousStates(self, x, nx): - return self.fmi3SetContinuousStates(self.component, x, nx) + def setContinuousStates(self, continuousStates, nContinuousStates): + return self.fmi3SetContinuousStates(self.component, continuousStates, nContinuousStates) # Evaluation of the model equations - def getDerivatives(self, dx, nx): - return self.fmi3GetDerivatives(self.component, dx, nx) + def getDerivatives(self, derivatives, nContinuousStates): + return self.fmi3GetDerivatives(self.component, derivatives, nContinuousStates) - def getEventIndicators(self, z, nz): - return self.fmi3GetEventIndicators(self.component, z, nz) + def getEventIndicators(self, eventIndicators, nEventIndicators): + return self.fmi3GetEventIndicators(self.component, eventIndicators, nEventIndicators) - def getContinuousStates(self, x, nx): - return self.fmi3GetContinuousStates(self.component, x, nx) + def getContinuousStates(self, continuousStates, nContinuousStates): + return self.fmi3GetContinuousStates(self.component, continuousStates, nContinuousStates) - def getNominalsOfContinuousStatesTYPE(self): + def getNominalsOfContinuousState(self): pass diff --git a/fmpy/gui/MainWindow.py b/fmpy/gui/MainWindow.py index 3cb8d9d3..fb023462 100644 --- a/fmpy/gui/MainWindow.py +++ b/fmpy/gui/MainWindow.py @@ -261,6 +261,7 @@ def __init__(self, parent=None): self.ui.actionCompilePlatformBinary.triggered.connect(self.compilePlatformBinary) self.ui.actionCreateCMakeProject.triggered.connect(self.createCMakeProject) self.ui.actionAddRemoting.triggered.connect(self.addRemoting) + self.ui.actionAddCoSimulationWrapper.triggered.connect(self.addCoSimulationWrapper) # filter menu self.filterMenu = QMenu() @@ -437,6 +438,9 @@ def load(self, filename): can_add_remoting = md.fmiVersion == '2.0' and 'win32' in platforms and 'win64' not in platforms self.ui.actionAddRemoting.setEnabled(can_add_remoting) + can_add_cswrapper = md.fmiVersion == '2.0' and md.coSimulation is None and md.modelExchange is not None + self.ui.actionAddCoSimulationWrapper.setEnabled(can_add_cswrapper) + # variables view self.treeModel.setModelDescription(md) self.tableModel.setModelDescription(md) @@ -875,27 +879,30 @@ def createDesktopShortcut(self): from win32com.client import Dispatch import sys - desktop_locations = QStandardPaths.standardLocations(QStandardPaths.DesktopLocation) - path = os.path.join(desktop_locations[0], "FMPy GUI.lnk") - - python = sys.executable - - root, ext = os.path.splitext(python) - - pythonw = root + 'w' + ext + env = os.environ['CONDA_DEFAULT_ENV'] - if os.path.isfile(pythonw): - target = pythonw + if env is None: + target_path = sys.executable + arguments = '-m fmpy.gui' else: - target = python + for path in os.environ["PATH"].split(os.pathsep): + activate = os.path.join(path, 'activate.bat') + if os.path.isfile(activate): + break + + target_path = '%windir%\System32\cmd.exe' + arguments = '/C ""%s" %s && python -m fmpy.gui"' % (activate, env) file_path = os.path.dirname(__file__) icon = os.path.join(file_path, 'icons', 'app_icon.ico') + desktop_locations = QStandardPaths.standardLocations(QStandardPaths.DesktopLocation) + shortcut_path = os.path.join(desktop_locations[0], "FMPy GUI.lnk") + shell = Dispatch('WScript.Shell') - shortcut = shell.CreateShortCut(path) - shortcut.Targetpath = target - shortcut.Arguments = '-m fmpy.gui' + shortcut = shell.CreateShortCut(shortcut_path) + shortcut.Targetpath = target_path + shortcut.Arguments = arguments # shortcut.WorkingDirectory = ... shortcut.IconLocation = icon shortcut.save() @@ -917,22 +924,34 @@ def addFileAssociation(self): try: from winreg import HKEY_CURRENT_USER, KEY_WRITE, REG_SZ, OpenKey, CreateKey, SetValueEx, CloseKey - python = sys.executable + env = os.environ['CONDA_DEFAULT_ENV'] - root, ext = os.path.splitext(python) + if env is None: + python = sys.executable + root, ext = os.path.splitext(python) + pythonw = root + 'w' + ext - pythonw = root + 'w' + ext + if os.path.isfile(pythonw): + python = pythonw - if os.path.isfile(pythonw): - target = pythonw + target = '"%s" -m fmpy.gui "%%1"' % python else: - target = python + # activate the conda environment + for path in os.environ["PATH"].split(os.pathsep): + activate = os.path.join(path, 'activate.bat') + if os.path.isfile(activate): + break + + windir = os.environ['WINDIR'] + cmd = os.path.join(windir, 'System32', 'cmd.exe') + + target = r'%s /C ""%s" %s && python -m fmpy.gui %%1"' % (cmd, activate, env) key_path = r'Software\Classes\fmpy.gui\shell\open\command' CreateKey(HKEY_CURRENT_USER, key_path) key = OpenKey(HKEY_CURRENT_USER, key_path, 0, KEY_WRITE) - SetValueEx(key, '', 0, REG_SZ, '"%s" -m fmpy.gui "%%1"' % target) + SetValueEx(key, '', 0, REG_SZ, target) CloseKey(key) key_path = r'SOFTWARE\Classes\.fmu' @@ -1165,3 +1184,16 @@ def addRemoting(self): self.load(self.filename) + + def addCoSimulationWrapper(self): + """ Add the Co-Simulation Wrapper to the FMU """ + + from ..cswrapper import add_cswrapper + + try: + add_cswrapper(self.filename) + except Exception as e: + QMessageBox.warning(self, "Failed to add Co-Simulation Wrapper", + "Failed to add Co-Simulation Wrapper %s. %s" % (self.filename, e)) + + self.load(self.filename) diff --git a/fmpy/gui/forms/MainWindow.ui b/fmpy/gui/forms/MainWindow.ui index b67a5bae..b8616dcc 100644 --- a/fmpy/gui/forms/MainWindow.ui +++ b/fmpy/gui/forms/MainWindow.ui @@ -1353,6 +1353,7 @@ QToolButton:checked, QToolButton:hover:pressed { + @@ -1891,7 +1892,7 @@ QToolButton::menu-button { false - Compile Platform Binary + Compile Platform &Binary @@ -1899,7 +1900,15 @@ QToolButton::menu-button { false - Add 32-bit Remoting + Add 32-bit &Remoting + + + + + false + + + Add Co-Simulation &Wrapper diff --git a/fmpy/logging/darwin64/logging.dylib b/fmpy/logging/darwin64/logging.dylib deleted file mode 100644 index f5b5dd11..00000000 Binary files a/fmpy/logging/darwin64/logging.dylib and /dev/null differ diff --git a/fmpy/logging/linux64/logging.so b/fmpy/logging/linux64/logging.so deleted file mode 100644 index ce09d736..00000000 Binary files a/fmpy/logging/linux64/logging.so and /dev/null differ diff --git a/fmpy/logging/win32/logging.dll b/fmpy/logging/win32/logging.dll deleted file mode 100644 index 94a5214e..00000000 Binary files a/fmpy/logging/win32/logging.dll and /dev/null differ diff --git a/fmpy/logging/win64/logging.dll b/fmpy/logging/win64/logging.dll deleted file mode 100644 index 46b13def..00000000 Binary files a/fmpy/logging/win64/logging.dll and /dev/null differ diff --git a/fmpy/model_description.py b/fmpy/model_description.py index e845119a..a80295e9 100644 --- a/fmpy/model_description.py +++ b/fmpy/model_description.py @@ -112,7 +112,7 @@ def __init__(self, name, valueReference): self.type = None "One of 'Real', 'Integer', 'Enumeration', 'Boolean', 'String'" - self.dimensions = None + self.dimensions = [] "List of fixed dimensions" self.dimensionValueReferences = [] @@ -169,6 +169,13 @@ def __repr__(self): return '%s "%s"' % (self.type, self.name) +class Dimension(object): + + def __init__(self, **kwargs): + self.start = kwargs.get('start') + self.valueReference = kwargs.get('valueReference') + + class SimpleType(object): """ Type Definition """ @@ -352,13 +359,14 @@ def read_build_description(filename, validate=True): return build_configurations -def read_model_description(filename, validate=True, validate_variable_names=False): +def read_model_description(filename, validate=True, validate_variable_names=False, validate_model_structure=False): """ Read the model description from an FMU without extracting it Parameters: - filename filename of the FMU or XML file, directory with extracted FMU or file like object - validate whether the model description should be validated - validate_variable_names validate the variable names against the EBNF + filename filename of the FMU or XML file, directory with extracted FMU or file like object + validate whether the model description should be validated + validate_variable_names validate the variable names against the EBNF + validate_model_structure validate the model structure returns: model_description a ModelDescription object @@ -584,11 +592,12 @@ def read_model_description(filename, validate=True, validate_variable_names=Fals # FMI 3 for t in root.findall('TypeDefinitions/*'): - if t.tag not in {'Float32', 'Float64', 'Int8', 'UInt8', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64', - 'Boolean', 'String', 'Binary', 'Enumeration'}: + if t.tag not in {'Float32Type', 'Float64Type', 'Int8Type', 'UInt8Type', 'Int16Type', 'UInt16Type', 'Int32Type', + 'UInt32Type', 'Int64Type', 'UInt64Type', 'BooleanType', 'StringType', 'BinaryType', + 'EnumerationType'}: continue - simple_type = SimpleType(type=t.tag, **t.attrib) + simple_type = SimpleType(type=t.tag[:-4], **t.attrib) # add enumeration items for item in t.findall('Item'): @@ -699,21 +708,33 @@ def read_model_description(filename, validate=True, validate_variable_names=Fals dimensions = variable.findall('Dimension') if dimensions: - sv.dimensions = [] for dimension in dimensions: start = dimension.get('start') - sv.dimensions.append(int(start) if start is not None else None) vr = dimension.get('valueReference') - sv.dimensionValueReferences.append(int(vr) if vr is not None else None) + d = Dimension( + start=int(start) if start is not None else None, + valueReference=int(vr) if vr is not None else None + ) + sv.dimensions.append(d) modelDescription.modelVariables.append(sv) - # variables = dict((v.valueReference, v) for v in modelDescription.modelVariables) - # - # resolve dimensions and calculate extent - # for variable in modelDescription.modelVariables: - # variable.dimensions = list(map(lambda vr: variables[vr], variable.dimensions)) - # variable.extent = tuple(map(lambda d: int(d.start), variable.dimensions)) + variables = dict((v.valueReference, v) for v in modelDescription.modelVariables) + + # calculate initial shape + for variable in modelDescription.modelVariables: + + shape = [] + + for d in variable.dimensions: + + if d.start is not None: + shape.append(int(d.start)) + else: + v = variables[d.valueReference] + shape.append(int(v.start)) + + variable.shape = tuple(shape) if fmiVersion == '2.0': @@ -740,6 +761,12 @@ def read_model_description(filename, validate=True, validate_variable_names=Fals attr.append(unknown) + # resolve derivatives + for variable in modelDescription.modelVariables: + if variable.derivative is not None: + index = int(variable.derivative) - 1 + variable.derivative = modelDescription.modelVariables[index] + if fmiVersion.startswith('3.0'): modelDescription.numberOfEventIndicators = len(root.findall('ModelStructure/EventIndicator')) @@ -799,13 +826,6 @@ def read_model_description(filename, validate=True, validate_variable_names=Fals if (variable.initial in {'exact', 'approx'} or variable.causality == 'input') and variable.start is None: raise Exception('Variable "%s" (line %s) has no start value.' % (variable.sourceline, variable.name)) - # validate outputs - outputs = set([v for v in modelDescription.modelVariables if v.causality == 'output']) - unknowns = set([u.variable for u in modelDescription.outputs]) - - if outputs != unknowns: - raise Exception('ModelStructure/Outputs must have exactly one entry for each variable with causality="output".') - # validate units for variable in modelDescription.modelVariables: @@ -820,6 +840,40 @@ def read_model_description(filename, validate=True, validate_variable_names=Fals if variable.displayUnit is not None and variable.displayUnit not in unit_definitions[unit]: raise Exception('The display unit "%s" of variable "%s" (line %s) is not defined.' % (variable.displayUnit, variable.name, variable.sourceline)) + if validate_model_structure: + + # validate outputs + expected_outputs = set(v for v in modelDescription.modelVariables if v.causality == 'output') + outputs = set(u.variable for u in modelDescription.outputs) + + if expected_outputs != outputs: + raise Exception('ModelStructure/Outputs must have exactly one entry for each variable with causality="output".') + + # TODO: validate derivatives + + # validate initial unknowns + expected_initial_unknowns = set() + + for variable in modelDescription.modelVariables: + + if variable.causality == 'output' and variable.initial in {'approx', 'calculated'}: + expected_initial_unknowns.add(variable) + + if variable.causality == 'calculatedParameter': + expected_initial_unknowns.add(variable) + + for unknown in modelDescription.derivatives: + derivative = unknown.variable + state = derivative.derivative + for variable in [state, derivative]: + if variable.initial in {'approx', 'calculated'}: + expected_initial_unknowns.add(variable) + + initial_unknowns = set(v.variable for v in modelDescription.initialUnknowns) + + if initial_unknowns != expected_initial_unknowns: + raise Exception('ModelStructure/InitialUnkowns does not contain the expected set of variables.') + if validate_variable_names: if modelDescription.variableNamingConvention == 'flat': diff --git a/fmpy/remoting/client.dll b/fmpy/remoting/client.dll deleted file mode 100644 index 54bc52c2..00000000 Binary files a/fmpy/remoting/client.dll and /dev/null differ diff --git a/fmpy/remoting/server.exe b/fmpy/remoting/server.exe deleted file mode 100644 index 65323631..00000000 Binary files a/fmpy/remoting/server.exe and /dev/null differ diff --git a/fmpy/schema/fmi3/fmi3Annotation.xsd b/fmpy/schema/fmi3/fmi3Annotation.xsd index 2231ee9b..4b1a4a45 100644 --- a/fmpy/schema/fmi3/fmi3Annotation.xsd +++ b/fmpy/schema/fmi3/fmi3Annotation.xsd @@ -34,18 +34,18 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- - + - - + + - + - + - + diff --git a/fmpy/schema/fmi3/fmi3AttributeGroups.xsd b/fmpy/schema/fmi3/fmi3AttributeGroups.xsd index 871103dc..f797e5e6 100644 --- a/fmpy/schema/fmi3/fmi3AttributeGroups.xsd +++ b/fmpy/schema/fmi3/fmi3AttributeGroups.xsd @@ -69,20 +69,17 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - + - - + - - + @@ -97,8 +94,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - + @@ -113,7 +109,8 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + + diff --git a/fmpy/schema/fmi3/fmi3BuildDescription.xsd b/fmpy/schema/fmi3/fmi3BuildDescription.xsd index 8682c5f8..c7cd3ca8 100644 --- a/fmpy/schema/fmi3/fmi3BuildDescription.xsd +++ b/fmpy/schema/fmi3/fmi3BuildDescription.xsd @@ -38,7 +38,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -46,6 +46,9 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + @@ -58,6 +61,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + @@ -67,9 +71,13 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + @@ -78,20 +86,23 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + - + - + diff --git a/fmpy/schema/fmi3/fmi3InterfaceType.xsd b/fmpy/schema/fmi3/fmi3InterfaceType.xsd index 8eecf404..37201c93 100644 --- a/fmpy/schema/fmi3/fmi3InterfaceType.xsd +++ b/fmpy/schema/fmi3/fmi3InterfaceType.xsd @@ -37,15 +37,15 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + - - + + @@ -59,7 +59,6 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/fmpy/schema/fmi3/fmi3ModelDescription.xsd b/fmpy/schema/fmi3/fmi3ModelDescription.xsd index a10be64e..1de63b6a 100644 --- a/fmpy/schema/fmi3/fmi3ModelDescription.xsd +++ b/fmpy/schema/fmi3/fmi3ModelDescription.xsd @@ -43,10 +43,10 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - + + + + @@ -64,6 +64,9 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + @@ -73,13 +76,15 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + - @@ -99,6 +104,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + @@ -116,7 +122,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + diff --git a/fmpy/schema/fmi3/fmi3Terminal.xsd b/fmpy/schema/fmi3/fmi3Terminal.xsd index 0e0c6962..60c4302b 100644 --- a/fmpy/schema/fmi3/fmi3Terminal.xsd +++ b/fmpy/schema/fmi3/fmi3Terminal.xsd @@ -39,13 +39,19 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + - + + + + @@ -53,17 +59,17 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + - + - + - + @@ -71,11 +77,11 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + - - + + diff --git a/fmpy/schema/fmi3/fmi3TerminalsAndIcons.xsd b/fmpy/schema/fmi3/fmi3TerminalsAndIcons.xsd index 4d68d608..75e3707c 100644 --- a/fmpy/schema/fmi3/fmi3TerminalsAndIcons.xsd +++ b/fmpy/schema/fmi3/fmi3TerminalsAndIcons.xsd @@ -59,7 +59,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -70,7 +70,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + diff --git a/fmpy/schema/fmi3/fmi3Type.xsd b/fmpy/schema/fmi3/fmi3Type.xsd index f0884c41..134206d5 100644 --- a/fmpy/schema/fmi3/fmi3Type.xsd +++ b/fmpy/schema/fmi3/fmi3Type.xsd @@ -1,5 +1,6 @@ + @@ -37,13 +38,16 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + - + @@ -52,7 +56,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -61,7 +65,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -70,7 +74,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -79,7 +83,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -88,7 +92,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -97,7 +101,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -106,7 +110,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -115,7 +119,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -124,7 +128,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -133,37 +137,40 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + - + - + - - + + - + + + + @@ -175,7 +182,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + diff --git a/fmpy/schema/fmi3/fmi3Unit.xsd b/fmpy/schema/fmi3/fmi3Unit.xsd index d4e4e80b..0c99bd2a 100644 --- a/fmpy/schema/fmi3/fmi3Unit.xsd +++ b/fmpy/schema/fmi3/fmi3Unit.xsd @@ -1,5 +1,6 @@ + Copyright(c) 2008-2011 MODELISAR consortium, @@ -50,15 +51,17 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - - - - - + + + + + + + + + + + diff --git a/fmpy/schema/fmi3/fmi3Variable.xsd b/fmpy/schema/fmi3/fmi3Variable.xsd index 5beddb1e..1607f760 100644 --- a/fmpy/schema/fmi3/fmi3Variable.xsd +++ b/fmpy/schema/fmi3/fmi3Variable.xsd @@ -45,11 +45,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -65,11 +61,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -85,11 +77,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -104,11 +92,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -123,11 +107,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -142,11 +122,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -161,11 +137,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -180,11 +152,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -199,11 +167,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -218,11 +182,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -236,11 +196,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -252,8 +208,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - + @@ -267,9 +222,9 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + - + @@ -279,31 +234,18 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - - - - - - - - - - + @@ -318,11 +260,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - + @@ -339,7 +277,21 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + + + + + + + + + + @@ -347,7 +299,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + @@ -387,10 +339,9 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - + + + diff --git a/fmpy/schema/fmi3/fmi3VariableDependency.xsd b/fmpy/schema/fmi3/fmi3VariableDependency.xsd index 06d06192..bc8039fa 100644 --- a/fmpy/schema/fmi3/fmi3VariableDependency.xsd +++ b/fmpy/schema/fmi3/fmi3VariableDependency.xsd @@ -1,5 +1,6 @@ + Copyright(c) 2008-2011 MODELISAR consortium, @@ -35,6 +36,9 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + diff --git a/fmpy/simulation.py b/fmpy/simulation.py index 1cfd305d..aa94994f 100644 --- a/fmpy/simulation.py +++ b/fmpy/simulation.py @@ -7,7 +7,7 @@ from .fmi2 import _FMU2 from . import fmi3 from . import extract -from .util import auto_interval +from .util import auto_interval, _is_string import numpy as np from time import time as current_time @@ -52,8 +52,8 @@ def __init__(self, fmu, modelDescription, variableNames=None, interval=None): names, vrs, shapes, n_values, getter = self.info.get(type, ([], [], [], 0, getattr(self.fmu, 'get' + type))) names.append(sv.name) vrs.append(sv.valueReference) - shapes.append(sv.dimensions) - n_values += np.prod(sv.dimensions) if sv.dimensions else 1 + shapes.append(sv.shape) + n_values += np.prod(sv.shape) if sv.shape else 1 self.info[type] = names, vrs, shapes, n_values, getter # create the columns for the NumPy array @@ -70,7 +70,7 @@ def __init__(self, fmu, modelDescription, variableNames=None, interval=None): self.cols += zip(names, [dt] * len(names), shapes) # strip the shape for scalars - self.cols = [(n, t) if s is None else (n, t, s) for n, t, s in self.cols] + self.cols = [(n, t) if not s else (n, t, s) for n, t, s in self.cols] @staticmethod def _append_reshaped(row, values, shapes): @@ -364,9 +364,17 @@ def apply_start_values(fmu, model_description, start_values, apply_default_start value = value.lower() == 'true' # convert the type - value = variable._python_type(value) + if variable.shape: + if _is_string(value): + value = value.split() + value = list(map(lambda e: variable._python_type(e), value)) + if len(value) != np.prod(variable.shape): + raise ArgumentError('The start value for variable "%s" must have %d elements but has %d.' % + (variable.name, np.prod(variable.shape), len(value))) + else: + value = [variable._python_type(value)] - setter([vr], [value]) + setter([vr], value) if len(start_values) > 0: raise Exception("The start values for the following variables could not be set because they don't exist: " + @@ -375,7 +383,7 @@ def apply_start_values(fmu, model_description, start_values, apply_default_start class ForwardEuler(object): - def __init__(self, nx, nz, get_x, set_x, get_dx, get_z): + def __init__(self, nx, nz, get_x, set_x, get_dx, get_z, input): self.get_x = get_x self.set_x = set_x @@ -694,28 +702,48 @@ def simulateME(model_description, fmu, start_time, stop_time, solver_name, step_ # initialize if is_fmi1: + input.apply(time) - fmu.initialize() + + (iterationConverged, + stateValueReferencesChanged, + stateValuesChanged, + terminateSimulation, + nextEventTimeDefined, + nextEventTime) = fmu.initialize() + + if terminateSimulation: + raise Exception('Model requested termination during initial event update.') + elif is_fmi2: + fmu.enterInitializationMode() input.apply(time) fmu.exitInitializationMode() - # event iteration - fmu.eventInfo.newDiscreteStatesNeeded = fmi2True - fmu.eventInfo.terminateSimulation = fmi2False + newDiscreteStatesNeeded = True + terminateSimulation = False - while fmu.eventInfo.newDiscreteStatesNeeded == fmi2True and fmu.eventInfo.terminateSimulation == fmi2False: + while newDiscreteStatesNeeded and not terminateSimulation: # update discrete states - fmu.newDiscreteStates() + (newDiscreteStatesNeeded, + terminateSimulation, + nominalsOfContinuousStatesChanged, + valuesOfContinuousStatesChanged, + nextEventTimeDefined, + nextEventTime) = fmu.newDiscreteStates() + + if terminateSimulation: + raise Exception('Model requested termination during initial event update.') fmu.enterContinuousTimeMode() - else: + + elif is_fmi3: + fmu.enterInitializationMode(startTime=start_time) input.apply(time) fmu.exitInitializationMode() - # event iteration newDiscreteStatesNeeded = True terminateSimulation = False @@ -737,7 +765,8 @@ def simulateME(model_description, fmu, start_time, stop_time, solver_name, step_ 'get_x': fmu.getContinuousStates, 'set_x': fmu.setContinuousStates, 'get_dx': fmu.getDerivatives, - 'get_z': fmu.getEventIndicators + 'get_z': fmu.getEventIndicators, + 'input': input } # select the solver @@ -797,15 +826,10 @@ def simulateME(model_description, fmu, start_time, stop_time, solver_name, step_ if input_event: t_next = t_input_event - if is_fmi1: - time_event = fmu.eventInfo.upcomingTimeEvent != fmi1False and fmu.eventInfo.nextEventTime <= t_next - elif is_fmi2: - time_event = fmu.eventInfo.nextEventTimeDefined != fmi2False and fmu.eventInfo.nextEventTime <= t_next - else: - time_event = nextEventTimeDefined and nextEventTime <= t_next + time_event = nextEventTimeDefined and nextEventTime <= t_next if time_event and not fixed_step: - t_next = nextEventTime if is_fmi3 else fmu.eventInfo.nextEventTime + t_next = nextEventTime if t_next - time > eps: # do one step @@ -838,8 +862,20 @@ def simulateME(model_description, fmu, start_time, stop_time, solver_name, step_ if input_event: input.apply(time=time, after_event=True) - - fmu.eventUpdate() + + iterationConverged = False + + # update discrete states + while not iterationConverged and not terminateSimulation: + (iterationConverged, + stateValueReferencesChanged, + stateValuesChanged, + terminateSimulation, + nextEventTimeDefined, + nextEventTime) = fmu.eventUpdate() + + if terminateSimulation: + break elif is_fmi2: @@ -848,24 +884,30 @@ def simulateME(model_description, fmu, start_time, stop_time, solver_name, step_ if input_event: input.apply(time=time, after_event=True) - fmu.eventInfo.newDiscreteStatesNeeded = fmi2True - fmu.eventInfo.terminateSimulation = fmi2False + newDiscreteStatesNeeded = True # update discrete states - while fmu.eventInfo.newDiscreteStatesNeeded != fmi2False and fmu.eventInfo.terminateSimulation == fmi2False: - fmu.newDiscreteStates() + while newDiscreteStatesNeeded and not terminateSimulation: + (newDiscreteStatesNeeded, + terminateSimulation, + nominalsOfContinuousStatesChanged, + valuesOfContinuousStatesChanged, + nextEventTimeDefined, + nextEventTime) = fmu.newDiscreteStates() + + if terminateSimulation: + break fmu.enterContinuousTimeMode() else: - fmu.enterEventMode(inputEvent=input_event, stepEvent=step_event, rootsFound=roots_found, timeEvent=time_event) + fmu.enterEventMode(stepEvent=step_event, rootsFound=roots_found, timeEvent=time_event) if input_event: input.apply(time=time, after_event=True) newDiscreteStatesNeeded = True - terminateSimulation = False # update discrete states while newDiscreteStatesNeeded and not terminateSimulation: @@ -876,6 +918,9 @@ def simulateME(model_description, fmu, start_time, stop_time, solver_name, step_ nextEventTimeDefined, nextEventTime) = fmu.newDiscreteStates() + if terminateSimulation: + break + fmu.enterContinuousTimeMode() solver.reset(time) diff --git a/fmpy/sundials/__init__.py b/fmpy/sundials/__init__.py index ed0fa3ec..e1270965 100644 --- a/fmpy/sundials/__init__.py +++ b/fmpy/sundials/__init__.py @@ -31,7 +31,7 @@ class CVodeSolver(object): """ Interface to the CVode solver """ def __init__(self, - nx, nz, get_x, set_x, get_dx, get_z, set_time, + nx, nz, get_x, set_x, get_dx, get_z, set_time, input, startTime, maxStep=float('inf'), relativeTolerance=1e-5, @@ -56,6 +56,7 @@ def __init__(self, self.get_dx = get_dx self.get_z = get_z self.set_time = set_time + self.input = input self.error_info = None self.discrete = nx == 0 @@ -132,6 +133,7 @@ def g(self, t, y, gout, user_data): """ Root function """ self.set_time(t) + self.input.apply(t) if not self.discrete: self.set_x(NV_DATA_S(y), self.nx) diff --git a/fmpy/sundials/x86_64-darwin/sundials_cvode.dylib b/fmpy/sundials/x86_64-darwin/sundials_cvode.dylib deleted file mode 100644 index f773c549..00000000 Binary files a/fmpy/sundials/x86_64-darwin/sundials_cvode.dylib and /dev/null differ diff --git a/fmpy/sundials/x86_64-darwin/sundials_nvecserial.dylib b/fmpy/sundials/x86_64-darwin/sundials_nvecserial.dylib deleted file mode 100644 index 5d5cecba..00000000 Binary files a/fmpy/sundials/x86_64-darwin/sundials_nvecserial.dylib and /dev/null differ diff --git a/fmpy/sundials/x86_64-darwin/sundials_sunlinsoldense.dylib b/fmpy/sundials/x86_64-darwin/sundials_sunlinsoldense.dylib deleted file mode 100644 index b778f29d..00000000 Binary files a/fmpy/sundials/x86_64-darwin/sundials_sunlinsoldense.dylib and /dev/null differ diff --git a/fmpy/sundials/x86_64-darwin/sundials_sunmatrixdense.dylib b/fmpy/sundials/x86_64-darwin/sundials_sunmatrixdense.dylib deleted file mode 100644 index b370683d..00000000 Binary files a/fmpy/sundials/x86_64-darwin/sundials_sunmatrixdense.dylib and /dev/null differ diff --git a/fmpy/sundials/x86_64-linux/sundials_cvode.so b/fmpy/sundials/x86_64-linux/sundials_cvode.so deleted file mode 100644 index d20edba5..00000000 Binary files a/fmpy/sundials/x86_64-linux/sundials_cvode.so and /dev/null differ diff --git a/fmpy/sundials/x86_64-linux/sundials_nvecserial.so b/fmpy/sundials/x86_64-linux/sundials_nvecserial.so deleted file mode 100644 index e132ac2c..00000000 Binary files a/fmpy/sundials/x86_64-linux/sundials_nvecserial.so and /dev/null differ diff --git a/fmpy/sundials/x86_64-linux/sundials_sunlinsoldense.so b/fmpy/sundials/x86_64-linux/sundials_sunlinsoldense.so deleted file mode 100644 index d087fe8a..00000000 Binary files a/fmpy/sundials/x86_64-linux/sundials_sunlinsoldense.so and /dev/null differ diff --git a/fmpy/sundials/x86_64-linux/sundials_sunmatrixdense.so b/fmpy/sundials/x86_64-linux/sundials_sunmatrixdense.so deleted file mode 100644 index bbb0ce8c..00000000 Binary files a/fmpy/sundials/x86_64-linux/sundials_sunmatrixdense.so and /dev/null differ diff --git a/fmpy/sundials/x86_64-windows/sundials_cvode.dll b/fmpy/sundials/x86_64-windows/sundials_cvode.dll deleted file mode 100644 index b9777e98..00000000 Binary files a/fmpy/sundials/x86_64-windows/sundials_cvode.dll and /dev/null differ diff --git a/fmpy/sundials/x86_64-windows/sundials_nvecserial.dll b/fmpy/sundials/x86_64-windows/sundials_nvecserial.dll deleted file mode 100644 index 0961cb22..00000000 Binary files a/fmpy/sundials/x86_64-windows/sundials_nvecserial.dll and /dev/null differ diff --git a/fmpy/sundials/x86_64-windows/sundials_sunlinsoldense.dll b/fmpy/sundials/x86_64-windows/sundials_sunlinsoldense.dll deleted file mode 100644 index 597731ca..00000000 Binary files a/fmpy/sundials/x86_64-windows/sundials_sunlinsoldense.dll and /dev/null differ diff --git a/fmpy/sundials/x86_64-windows/sundials_sunmatrixdense.dll b/fmpy/sundials/x86_64-windows/sundials_sunmatrixdense.dll deleted file mode 100644 index a294c8ec..00000000 Binary files a/fmpy/sundials/x86_64-windows/sundials_sunmatrixdense.dll and /dev/null differ diff --git a/fmpy/util.py b/fmpy/util.py index 093b6ddd..c16df8ca 100644 --- a/fmpy/util.py +++ b/fmpy/util.py @@ -203,7 +203,7 @@ def validate_fmu(filename): from . import read_model_description try: - read_model_description(filename, validate=True) + read_model_description(filename, validate=True, validate_variable_names=True, validate_model_structure=True) except Exception as e: return [str(e)] diff --git a/remoting/server/CMakeLists.txt b/remoting/server/CMakeLists.txt index 140d3810..81742423 100644 --- a/remoting/server/CMakeLists.txt +++ b/remoting/server/CMakeLists.txt @@ -24,7 +24,6 @@ add_executable(server target_include_directories(server PUBLIC .. ../../fmpy/c-code - ../rpclib-2.2.1/include "${RPCLIB}/include" ) diff --git a/setup.py b/setup.py index 548c3d91..2b1060a8 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ packages = ['fmpy', 'fmpy.cross_check', + 'fmpy.cswrapper', 'fmpy.examples', 'fmpy.logging', 'fmpy.gui', @@ -33,6 +34,10 @@ 'fmpy': [ 'c-code/*.h', 'c-code/CMakeLists.txt', + 'cswrapper/cswrapper.dll', + 'cswrapper/cswrapper.dylib', + 'cswrapper/cswrapper.so', + 'cswrapper/license.txt', 'logging/darwin64/logging.dylib', 'logging/linux64/logging.so', 'logging/win32/logging.dll', @@ -61,14 +66,14 @@ extras_require = { 'examples': ['dask[bag]', 'requests'], - 'plot': ['matplotlib'], + 'plot': ['matplotlib', 'scipy'], 'gui': ['PyQt5', 'pyqtgraph'] } extras_require['complete'] = sorted(set(sum(extras_require.values(), []))) setup(name='FMPy', - version='0.2.20', + version='0.2.21', description="Simulate Functional Mock-up Units (FMUs) in Python", long_description=long_description, author="Torsten Sommer", diff --git a/tests/test_command_line.py b/tests/test_command_line.py index b783f9e2..a8fe4a44 100644 --- a/tests/test_command_line.py +++ b/tests/test_command_line.py @@ -1,6 +1,6 @@ import unittest from subprocess import call, check_output -from fmpy.util import download_test_file +from fmpy.util import download_test_file, download_file class CommandLineTest(unittest.TestCase): @@ -11,13 +11,14 @@ def setUpClass(cls): # download the FMU and input file download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches_in.csv') + download_file('https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/me/win64/Dymola/2019FD01/Rectifier/Rectifier.fmu') def test_info(self): status = call(['fmpy', 'info', 'CoupledClutches.fmu']) self.assertEqual(0, status) def test_validate(self): - status = call(['fmpy', 'validate', 'CoupledClutches.fmu']) + status = call(['fmpy', 'validate', 'Rectifier.fmu']) self.assertEqual(0, status) def test_simulate(self): diff --git a/tests/test_cswrapper.py b/tests/test_cswrapper.py new file mode 100644 index 00000000..27067c6a --- /dev/null +++ b/tests/test_cswrapper.py @@ -0,0 +1,21 @@ +import unittest +from fmpy import read_model_description, simulate_fmu +from fmpy.util import download_test_file +from fmpy.cswrapper import add_cswrapper + + +class CSWrapperTest(unittest.TestCase): + + def test_cswrapper(self): + + filename = 'CoupledClutches.fmu' + + download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', filename) + + model_description = read_model_description(filename) + + self.assertIsNone(model_description.coSimulation) + + add_cswrapper(filename) + + simulate_fmu(filename, fmi_type='CoSimulation')