├── .clang-format ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── ci-cmake.yml │ └── ci-conan.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cmake ├── AddTestExecutable.cmake ├── FindFMILibrary.cmake ├── Findlibcbor.cmake ├── conan.cmake ├── cppreference-doxygen-web.tag.xml └── project-config.cmake.in ├── conanfile.py ├── data └── xsd │ └── OspSystemStructure.xsd ├── docs └── main_page.md ├── include └── cosim │ ├── algorithm.hpp │ ├── algorithm │ ├── algorithm.hpp │ ├── ecco_algorithm.hpp │ ├── fixed_step_algorithm.hpp │ └── simulator.hpp │ ├── config.hpp │ ├── exception.hpp │ ├── execution.hpp │ ├── file_cache.hpp │ ├── fmi │ ├── fmu.hpp │ ├── importer.hpp │ ├── v1 │ │ └── fmu.hpp │ └── v2 │ │ └── fmu.hpp │ ├── fs_portability.hpp │ ├── function │ ├── description.hpp │ ├── function.hpp │ ├── linear_transformation.hpp │ ├── utility.hpp │ └── vector_sum.hpp │ ├── lib_info.hpp │ ├── log │ ├── logger.hpp │ └── simple.hpp │ ├── manipulator.hpp │ ├── manipulator │ ├── manipulator.hpp │ ├── override_manipulator.hpp │ └── scenario_manager.hpp │ ├── model_description.hpp │ ├── observer.hpp │ ├── observer │ ├── file_observer.hpp │ ├── last_value_observer.hpp │ ├── last_value_provider.hpp │ ├── observer.hpp │ ├── time_series_observer.hpp │ └── time_series_provider.hpp │ ├── orchestration.hpp │ ├── osp_config_parser.hpp │ ├── scenario.hpp │ ├── scenario_parser.hpp │ ├── serialization.hpp │ ├── slave.hpp │ ├── ssp │ └── ssp_loader.hpp │ ├── system_structure.hpp │ ├── time.hpp │ ├── timer.hpp │ └── uri.hpp ├── src ├── CMakeLists.txt └── cosim │ ├── algorithm │ ├── ecco_algorithm.cpp │ └── fixed_step_algorithm.cpp │ ├── error.cpp │ ├── error.hpp │ ├── exception.cpp │ ├── execution.cpp │ ├── file_cache.cpp │ ├── fmi │ ├── fmilib.h │ ├── glue.cpp │ ├── glue.hpp │ ├── importer.cpp │ ├── v1 │ │ └── fmu.cpp │ ├── v2 │ │ └── fmu.cpp │ ├── windows.cpp │ └── windows.hpp │ ├── function │ ├── linear_transformation.cpp │ ├── utility.cpp │ └── vector_sum.cpp │ ├── lib_info.cpp.in │ ├── log │ └── logger.cpp │ ├── manipulator │ ├── override_manipulator.cpp │ └── scenario_manager.cpp │ ├── model_description.cpp │ ├── observer │ ├── file_observer.cpp │ ├── last_value_observer.cpp │ ├── slave_value_provider.cpp │ ├── slave_value_provider.hpp │ └── time_series_observer.cpp │ ├── orchestration.cpp │ ├── osp_config_parser.cpp │ ├── proxy │ ├── proxy_uri_sub_resolver.cpp │ ├── proxy_uri_sub_resolver.hpp │ ├── remote_fmu.cpp │ ├── remote_fmu.hpp │ ├── remote_slave.cpp │ └── remote_slave.hpp │ ├── scenario_parser.cpp │ ├── serialization.cpp │ ├── slave_simulator.cpp │ ├── slave_simulator.hpp │ ├── ssp │ ├── ssp_loader.cpp │ ├── ssp_parser.cpp │ └── ssp_parser.hpp │ ├── system_structure.cpp │ ├── timer.cpp │ ├── uri.cpp │ └── utility │ ├── concurrency.cpp │ ├── concurrency.hpp │ ├── filesystem.cpp │ ├── filesystem.hpp │ ├── thread_pool.hpp │ ├── utility.hpp │ ├── uuid.cpp │ ├── uuid.hpp │ ├── zip.cpp │ └── zip.hpp ├── tests ├── CMakeLists.txt ├── config_end_time_test.cpp ├── data │ ├── LogConfig.xml │ ├── LogConfig.xsd │ ├── fmi1 │ │ ├── README.md │ │ ├── identity.fmu │ │ └── viproma_demo-fmus_LICENSE │ ├── fmi2 │ │ ├── CraneController.fmu │ │ ├── Dahlquist.fmu │ │ ├── KnuckleBoomCrane.fmu │ │ ├── KnuckleBoomCrane_OspModelDescription.xml │ │ ├── README.md │ │ ├── StateInitExample.fmu │ │ ├── osp_cpp-fmus_LICENSE │ │ ├── quarter_truck │ │ │ ├── Chassis.fmu │ │ │ ├── LICENSE │ │ │ ├── LogConfig.xml │ │ │ ├── OspSystemStructure.xml │ │ │ ├── README.md │ │ │ ├── Wheel.fmu │ │ │ ├── chassis_OspModelDescription.xml │ │ │ └── wheel_OspModelDescription.xml │ │ ├── reference-fmus_LICENCE │ │ └── vector.fmu │ ├── msmi │ │ ├── CraneController_OspModelDescription.xml │ │ ├── OspSystemStructure.xml │ │ ├── OspSystemStructure_Bond.xml │ │ ├── OspSystemStructure_Dahlquist.xml │ │ ├── OspSystemStructure_Dahlquist_proxyfmu.xml │ │ ├── OspSystemStructure_EndTime.xml │ │ ├── OspSystemStructure_StateInitExample.xml │ │ ├── OspSystemStructure_proxyfmu.xml │ │ ├── msmi2.xml │ │ └── schema │ │ │ ├── OspModelDescription.xsd │ │ │ └── fmi2Unit.xsd │ ├── scenarios │ │ ├── scenario1.json │ │ └── scenario1.yml │ ├── ssp │ │ ├── OSPAnnotations.xsd │ │ ├── SSP10 │ │ │ ├── SystemStructureCommon.xsd │ │ │ ├── SystemStructureDescription.xsd │ │ │ ├── SystemStructureDescription11.xsd │ │ │ ├── SystemStructureParameterMapping.xsd │ │ │ ├── SystemStructureParameterValues.xsd │ │ │ └── SystemStructureSignalDictionary.xsd │ │ ├── demo │ │ │ ├── SystemStructure.ssd │ │ │ ├── demo.ssp │ │ │ ├── no_algorithm_element │ │ │ │ └── SystemStructure.ssd │ │ │ └── proxy │ │ │ │ └── SystemStructure.ssd │ │ └── linear_transformation │ │ │ ├── SystemStructure.ssd │ │ │ ├── initial_values.ssv │ │ │ └── initial_values2.ssv │ └── ziptest.zip ├── ecco_algorithm_from_system_structure_test.cpp ├── ecco_algorithm_multi_bond_test.cpp ├── ecco_algorithm_test.cpp ├── file_observer_dynamic_logging_test.cpp ├── file_observer_logging_from_config_test.cpp ├── file_observer_logging_test.cpp ├── fixed_step_algorithm_test.cpp ├── fmi_v1_fmu_unittest.cpp ├── fmi_v2_fmu_unittest.cpp ├── function_unittest.cpp ├── last_value_observer_test.cpp ├── mock_slave.hpp ├── monitor_modified_variables_test.cpp ├── multi_fixed_step_algorithm_test.cpp ├── orchestration_unittest.cpp ├── osp_config_parser_test.cpp ├── proxyfmu_integration_unittest.cpp ├── proxyfmu_library_unittest.cpp ├── proxyfmu_osp_config_parser_test.cpp ├── proxyfmu_save_state_test.cpp ├── ramp_modifier_test.cpp ├── save_state_test.cpp ├── scenario_manager_test.cpp ├── scenario_parser_unittest.cpp ├── slave_simulator_unittest.cpp ├── ssp_loader_unittest.cpp ├── state_init_test.cpp ├── synchronized_xy_series_test.cpp ├── system_structure_unittest.cpp ├── time_series_observer_test.cpp ├── time_unittest.cpp ├── trend_buffer_test.cpp ├── uri_unittest.cpp ├── utility_concurrency_unittest.cpp ├── utility_filesystem_unittest.cpp ├── utility_uuid_unittest.cpp └── utility_zip_unittest.cpp ├── tools ├── housekeeping └── tmp_cleanup └── version.txt /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | Standard: Cpp11 3 | BasedOnStyle: WebKit 4 | NamespaceIndentation: None 5 | IndentCaseLabels: true 6 | IndentPPDirectives: AfterHash 7 | FixNamespaceComments: true 8 | MaxEmptyLinesToKeep: 2 9 | SpaceAfterTemplateKeyword: false 10 | AllowShortCaseLabelsOnASingleLine: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortIfStatementsOnASingleLine: true 13 | AllowShortBlocksOnASingleLine: true 14 | AllowShortLoopsOnASingleLine: true 15 | BreakBeforeBinaryOperators: None 16 | Cpp11BracedListStyle: true 17 | SpaceBeforeCpp11BracedList: false 18 | BreakBeforeBraces: Custom 19 | BraceWrapping: 20 | AfterEnum: true 21 | AfterStruct: true 22 | AfterClass: true 23 | SplitEmptyFunction: false 24 | AfterFunction: true 25 | AfterNamespace: true 26 | AfterControlStatement: false 27 | IncludeBlocks: Regroup 28 | IncludeCategories: 29 | - Regex: '^[<"]cosim[/.]' 30 | Priority: 20 31 | - Regex: '^[<"](boost|event2|fmilib|gsl|nlohmann|proxyfmu|xercesc|zip)[/.]' 32 | Priority: 30 33 | - Regex: '^"' 34 | Priority: 10 35 | - Regex: '.*' 36 | Priority: 40 37 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; Cross-editor configuration file (http://editorconfig.org/) 2 | root = true ; This is the topmost .editorconfig file 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Declare files that will always have LF line endings on checkout. 5 | conanfile.py text eol=lf 6 | version.txt text eol=lf 7 | -------------------------------------------------------------------------------- /.github/workflows/ci-cmake.yml: -------------------------------------------------------------------------------- 1 | name: CI without Conan 2 | 3 | # This workflow is triggered on pushes to the repository. 4 | on: [push] 5 | 6 | jobs: 7 | linux: 8 | name: Linux 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | build_type: [Debug, Release] 14 | shared_libs: [SHARED_LIBS=ON, SHARED_LIBS=OFF] 15 | timeout-minutes: 35 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Generate Dockerfile 20 | run: | 21 | mkdir /tmp/osp-builder-docker 22 | cat </tmp/osp-builder-docker/Dockerfile 23 | FROM debian:bookworm 24 | RUN apt-get update && apt-get install -y --no-install-recommends \ 25 | cmake \ 26 | build-essential \ 27 | doxygen \ 28 | wget \ 29 | libboost-all-dev \ 30 | libmsgsl-dev \ 31 | libyaml-cpp-dev \ 32 | libxerces-c-dev \ 33 | libzip-dev zipcmp zipmerge ziptool 34 | RUN wget \ 35 | --no-check-certificate \ 36 | "https://github.com/viproma/debian-fmilib/releases/download/debian%2F2.0.3-1/libfmilib2_2.0.3-1_amd64.deb" \ 37 | "https://github.com/viproma/debian-fmilib/releases/download/debian%2F2.0.3-1/libfmilib2-dev_2.0.3-1_amd64.deb" 38 | RUN dpkg -i libfmilib2_2.0.3-1_amd64.deb libfmilib2-dev_2.0.3-1_amd64.deb 39 | RUN wget --no-check-certificate https://github.com/PJK/libcbor/archive/refs/tags/v0.11.0.tar.gz && \ 40 | tar xaf v0.11.0.tar.gz && cd libcbor-0.11.0 && \ 41 | cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON . && make && make install 42 | COPY entrypoint.sh / 43 | ENTRYPOINT /entrypoint.sh 44 | EOF 45 | - name: Generate entrypoint.sh 46 | run: | 47 | cat <<'EOF' >/tmp/osp-builder-docker/entrypoint.sh 48 | #!/bin/bash 49 | mkdir build 50 | cd build 51 | cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_${{ matrix.shared_libs }} /mnt/source 52 | cmake --build . 53 | ctest --output-on-failure 54 | EOF 55 | chmod 0755 /tmp/osp-builder-docker/entrypoint.sh 56 | - name: Build Docker image 57 | run: docker build -t osp-builder /tmp/osp-builder-docker/ 58 | - name: Build cosim 59 | run: docker run --rm -v $(pwd):/mnt/source:ro osp-builder 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | build/ 3 | debug-build/ 4 | release-build/ 5 | 6 | # CTest: 7 | Testing/ 8 | 9 | # JetBrains CLion: 10 | .idea/ 11 | cmake-build-*/ 12 | 13 | # Visual Studio Code 14 | .vscode/ 15 | .lsp/ 16 | .clj-kondo 17 | 18 | CMakeUserPresets.json 19 | -------------------------------------------------------------------------------- /cmake/AddTestExecutable.cmake: -------------------------------------------------------------------------------- 1 | # This function is a short-hand way to create an executable target, add 2 | # dependencies to it, and create a test that runs it. 3 | # 4 | # add_test_executable( 5 | # 6 | # FOLDER 7 | # SOURCES 8 | # DEPENDENCIES 9 | # DATA_DIR 10 | # ) 11 | # 12 | # "target folder" is just a human-readable name for the IDE folder that the 13 | # target will be grouped under. If omitted, it will be a top-level target. 14 | function(add_test_executable name) 15 | # Parse argument list 16 | set(params "FOLDER" "SOURCES" "DEPENDENCIES" "DATA_DIR") 17 | foreach(p IN LISTS params) 18 | set(arg_${p}) 19 | endforeach() 20 | set(mode) 21 | foreach(arg IN LISTS ARGN) 22 | if("${arg}" IN_LIST params) 23 | set(mode "${arg}") 24 | elseif("${mode}" IN_LIST params) 25 | list(APPEND "arg_${mode}" "${arg}") 26 | else() 27 | message(FATAL_ERROR "Invalid/unexpected argument: ${arg}") 28 | endif() 29 | endforeach() 30 | 31 | # Add target 32 | add_executable("${name}" ${arg_SOURCES}) 33 | target_link_libraries(${name} PRIVATE ${arg_DEPENDENCIES}) 34 | if(arg_FOLDER) 35 | set_property(TARGET "${name}" PROPERTY FOLDER "${arg_FOLDER}") 36 | endif() 37 | 38 | # Add test 39 | add_test(NAME "${name}" COMMAND "${name}") 40 | if(arg_DATA_DIR) 41 | set_property(TEST "${name}" PROPERTY ENVIRONMENT "TEST_DATA_DIR=${arg_DATA_DIR}") 42 | endif() 43 | endfunction() 44 | -------------------------------------------------------------------------------- /cmake/Findlibcbor.cmake: -------------------------------------------------------------------------------- 1 | # FindLibCBOR.cmake 2 | # Locate libcbor library and headers, and define libcbor::libcbor target 3 | 4 | find_path(LIBCBOR_INCLUDE_DIR 5 | NAMES cbor.h 6 | PATH_SUFFIXES include 7 | ) 8 | 9 | find_library(LIBCBOR_LIBRARY 10 | NAMES cbor libcbor 11 | PATH_SUFFIXES lib 12 | ) 13 | 14 | include(FindPackageHandleStandardArgs) 15 | find_package_handle_standard_args(libcbor 16 | REQUIRED_VARS LIBCBOR_LIBRARY LIBCBOR_INCLUDE_DIR 17 | ) 18 | 19 | if(LIBCBOR_FOUND) 20 | set(LIBCBOR_INCLUDE_DIRS ${LIBCBOR_INCLUDE_DIR}) 21 | set(LIBCBOR_LIBRARIES ${LIBCBOR_LIBRARY}) 22 | 23 | if(NOT TARGET libcbor::libcbor) 24 | add_library(libcbor::libcbor UNKNOWN IMPORTED) 25 | set_target_properties(libcbor::libcbor PROPERTIES 26 | IMPORTED_LOCATION "${LIBCBOR_LIBRARY}" 27 | INTERFACE_INCLUDE_DIRECTORIES "${LIBCBOR_INCLUDE_DIR}" 28 | ) 29 | endif() 30 | endif() 31 | 32 | mark_as_advanced(LIBCBOR_INCLUDE_DIR LIBCBOR_LIBRARY) 33 | -------------------------------------------------------------------------------- /cmake/project-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include ("@PACKAGE_targetsFile@") 4 | 5 | include(CMakeFindDependencyMacro) 6 | list(APPEND CMAKE_MODULE_PATH "${PACKAGE_PREFIX_DIR}/@LIBCOSIM_CMAKE_INSTALL_DIR@") 7 | find_dependency(Boost REQUIRED COMPONENTS date_time log) 8 | set(FMILibrary_USE_SHARED_LIB @FMILibrary_USE_SHARED_LIB@) 9 | find_dependency(FMILibrary MODULE REQUIRED) 10 | find_dependency(libzip REQUIRED) 11 | find_dependency(Microsoft.GSL REQUIRED) 12 | find_dependency(yaml-cpp REQUIRED) 13 | find_dependency(XercesC REQUIRED) 14 | if(@LIBCOSIM_WITH_PROXYFMU@) 15 | find_dependency(PROXYFMU CONFIG REQUIRED) 16 | endif() 17 | list(REMOVE_AT CMAKE_MODULE_PATH -1) 18 | -------------------------------------------------------------------------------- /include/cosim/algorithm.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Co-simulation algorithms. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_ALGORITHM_HPP 11 | #define COSIM_ALGORITHM_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /include/cosim/algorithm/ecco_algorithm.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Defines the class for a ECCO (Energy-Conservation-based Co-Simulation) algorithm 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef LIBCOSIM_ALGORITHM_ECCO_ALGORITHM_HPP 11 | #define LIBCOSIM_ALGORITHM_ECCO_ALGORITHM_HPP 12 | 13 | #include 14 | 15 | namespace cosim 16 | { 17 | 18 | struct ecco_algorithm_params 19 | { 20 | double safety_factor; 21 | duration step_size; 22 | duration min_step_size; 23 | duration max_step_size; 24 | double min_change_rate; 25 | double max_change_rate; 26 | double abs_tolerance; 27 | double rel_tolerance; 28 | double p_gain; 29 | double i_gain; 30 | }; 31 | 32 | /** 33 | * A fixed-stepsize co-simulation algorithm. 34 | * 35 | * A simple implementation of `algorithm`. The simulation progresses 36 | * at a fixed base stepsize. Simulators are stepped in parallel at an optional 37 | * multiple of this base step size. 38 | */ 39 | class ecco_algorithm : public algorithm 40 | { 41 | public: 42 | /** 43 | * Constructor. 44 | * 45 | * \param baseStepSize 46 | * The base communication interval length. 47 | * 48 | * \param workerThreadCount 49 | * The number of worker threads to spawn for running FMUs 50 | */ 51 | explicit ecco_algorithm(ecco_algorithm_params params, std::optional workerThreadCount = std::nullopt); 52 | 53 | ~ecco_algorithm() noexcept; 54 | 55 | ecco_algorithm(const ecco_algorithm&) = delete; 56 | ecco_algorithm& operator=(const ecco_algorithm&) = delete; 57 | 58 | ecco_algorithm(ecco_algorithm&&) noexcept; 59 | ecco_algorithm& operator=(ecco_algorithm&&) noexcept; 60 | 61 | // `algorithm` methods 62 | void add_simulator(simulator_index i, simulator* s, duration stepSizeHint) override; 63 | void remove_simulator(simulator_index i) override; 64 | void add_function(function_index i, function* f) override; 65 | void connect_variables(variable_id output, variable_id input) override; 66 | void connect_variables(variable_id output, function_io_id input) override; 67 | void connect_variables(function_io_id output, variable_id input) override; 68 | void disconnect_variable(variable_id input) override; 69 | void disconnect_variable(function_io_id input) override; 70 | void setup(time_point startTime, std::optional stopTime) override; 71 | void initialize() override; 72 | std::pair> do_step(time_point currentT) override; 73 | serialization::node export_current_state() const override; 74 | void import_state(const serialization::node& exportedState) override; 75 | 76 | /** 77 | * Adds a variable pair for the power residual calculation. 78 | * \param uVec 79 | * The index of the variable. 80 | */ 81 | void add_power_bond(cosim::variable_id input_a, cosim::variable_id output_a, cosim::variable_id input_b, cosim::variable_id output_b); 82 | 83 | /** 84 | * Retrieves the energies in the power bond for the given simulator index.add_variable_value 85 | * \param simulator_index 86 | * The index of the simulator. 87 | */ 88 | std::vector get_powerbond_energies(cosim::simulator_index simulator_index); 89 | 90 | private: 91 | class impl; 92 | std::unique_ptr pimpl_; 93 | }; 94 | 95 | } // namespace cosim 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /include/cosim/algorithm/fixed_step_algorithm.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Defines the class for a fixed step algorithm 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef LIBCOSIM_ALGORITHM_FIXED_STEP_ALGORITHM_HPP 11 | #define LIBCOSIM_ALGORITHM_FIXED_STEP_ALGORITHM_HPP 12 | 13 | #include 14 | 15 | namespace cosim 16 | { 17 | 18 | struct fixed_step_algorithm_params 19 | { 20 | duration stepSize; 21 | }; 22 | 23 | /** 24 | * A fixed-stepsize co-simulation algorithm. 25 | * 26 | * A simple implementation of `algorithm`. The simulation progresses 27 | * at a fixed base stepsize. Simulators are stepped in parallel at an optional 28 | * multiple of this base step size. 29 | */ 30 | class fixed_step_algorithm : public algorithm 31 | { 32 | public: 33 | /** 34 | * Constructor. 35 | * 36 | * \param baseStepSize 37 | * The base communication interval length. 38 | * 39 | * \param workerThreadCount 40 | * The number of worker threads to spawn for running FMUs 41 | */ 42 | explicit fixed_step_algorithm(duration baseStepSize, std::optional workerThreadCount = std::nullopt); 43 | explicit fixed_step_algorithm(fixed_step_algorithm_params params, std::optional workerThreadCount = std::nullopt); 44 | 45 | ~fixed_step_algorithm() noexcept; 46 | 47 | fixed_step_algorithm(const fixed_step_algorithm&) = delete; 48 | fixed_step_algorithm& operator=(const fixed_step_algorithm&) = delete; 49 | 50 | fixed_step_algorithm(fixed_step_algorithm&&) noexcept; 51 | fixed_step_algorithm& operator=(fixed_step_algorithm&&) noexcept; 52 | 53 | // `algorithm` methods 54 | void add_simulator(simulator_index i, simulator* s, duration stepSizeHint) override; 55 | void remove_simulator(simulator_index i) override; 56 | void add_function(function_index i, function* f) override; 57 | void connect_variables(variable_id output, variable_id input) override; 58 | void connect_variables(variable_id output, function_io_id input) override; 59 | void connect_variables(function_io_id output, variable_id input) override; 60 | void disconnect_variable(variable_id input) override; 61 | void disconnect_variable(function_io_id input) override; 62 | void setup(time_point startTime, std::optional stopTime) override; 63 | void initialize() override; 64 | std::pair> do_step(time_point currentT) override; 65 | serialization::node export_current_state() const override; 66 | void import_state(const serialization::node& exportedState) override; 67 | 68 | /** 69 | * Sets step size decimation factor for a simulator. 70 | * 71 | * This will effectively set the simulator step size to a multiple 72 | * of the algorithm's base step size. The default decimation factor is 1. 73 | * Must be called *after* the simulator has been added to the algorithm with `add_simulator()`. 74 | * 75 | * \param simulator 76 | * The index of the simulator. 77 | * 78 | * \param factor 79 | * The stepsize decimation factor. 80 | */ 81 | void set_stepsize_decimation_factor(simulator_index simulator, int factor); 82 | 83 | private: 84 | class impl; 85 | std::unique_ptr pimpl_; 86 | }; 87 | 88 | } // namespace cosim 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /include/cosim/config.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Library-wide configuration macros. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_CONFIG_HPP 11 | #define COSIM_CONFIG_HPP 12 | 13 | 14 | /** 15 | * Top-level libcosim namespace 16 | * 17 | */ 18 | namespace cosim 19 | { 20 | } 21 | 22 | 23 | #endif // header guard 24 | -------------------------------------------------------------------------------- /include/cosim/fmi/fmu.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Defines a version-independent FMU interface. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_FMI_FMU_HPP 11 | #define COSIM_FMI_FMU_HPP 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | 20 | namespace cosim 21 | { 22 | namespace fmi 23 | { 24 | 25 | 26 | /// Constants that refer to FMI version numbers. 27 | enum class fmi_version 28 | { 29 | /// Unknown (or possibly unsupported) 30 | unknown = 0, 31 | 32 | /// FMI 1.0 33 | v1_0 = 10000, 34 | 35 | /// FMI 2.0 36 | v2_0 = 20000, 37 | }; 38 | 39 | 40 | class importer; 41 | class slave_instance; 42 | 43 | 44 | /** 45 | * An interface for classes that represent imported FMUs. 46 | * 47 | * This is an abstract class which only defines the functions that are common 48 | * between different FMI versions. Use `fmi::importer::import()` to import 49 | * an FMU and create an `fmu` object. 50 | */ 51 | class fmu 52 | { 53 | public: 54 | /// Which FMI standard version is used in this FMU. 55 | virtual fmi::fmi_version fmi_version() const = 0; 56 | 57 | /// A description of this FMU. 58 | virtual std::shared_ptr 59 | model_description() const = 0; 60 | 61 | /// Creates a co-simulation slave instance of this FMU. 62 | virtual std::shared_ptr instantiate_slave( 63 | std::string_view instanceName) = 0; 64 | 65 | /// The `fmi::importer` which was used to import this FMU. 66 | virtual std::shared_ptr importer() const = 0; 67 | 68 | virtual ~fmu() { } 69 | }; 70 | 71 | 72 | /// An FMI co-simulation slave instance. 73 | class slave_instance : public cosim::slave 74 | { 75 | public: 76 | /// Returns a reference to the FMU of which this is an instance. 77 | virtual std::shared_ptr fmu() const = 0; 78 | 79 | cosim::model_description model_description() const final override 80 | { 81 | return *(fmu()->model_description()); 82 | } 83 | 84 | virtual ~slave_instance() { } 85 | }; 86 | 87 | 88 | } // namespace fmi 89 | } // namespace cosim 90 | #endif // header guard 91 | -------------------------------------------------------------------------------- /include/cosim/fmi/importer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * FMU import functionality. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_FMI_IMPORTER_HPP 11 | #define COSIM_FMI_IMPORTER_HPP 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | // Forward declarations to avoid external dependency on FMI Library. 22 | struct fmi_xml_context_t; 23 | typedef fmi_xml_context_t fmi_import_context_t; 24 | struct jm_callbacks; 25 | 26 | 27 | namespace cosim 28 | { 29 | namespace fmi 30 | { 31 | 32 | class fmu; 33 | 34 | 35 | /** 36 | * Imports and caches FMUs. 37 | * 38 | * The main purpose of this class is to read FMU files and create 39 | * `cosim::fmi::fmu` objects to represent them. This is done with the 40 | * `import()` function. 41 | */ 42 | class importer : public std::enable_shared_from_this 43 | { 44 | public: 45 | /** 46 | * Creates a new FMU importer that uses a specific cache 47 | * directory. 48 | * 49 | * The cache directory will not be removed or emptied on destruction. 50 | * 51 | * \param [in] cache 52 | * The cache to which FMUs will be unpacked. 53 | * By default, a non-persistent cache is used. 54 | */ 55 | static std::shared_ptr create( 56 | std::shared_ptr cache = std::make_shared()); 57 | 58 | private: 59 | // Private constructors, to force use of factory functions. 60 | explicit importer(std::shared_ptr cache); 61 | 62 | public: 63 | /** 64 | * Imports and loads an FMU. 65 | * 66 | * Loaded FMUs are managed using reference counting. If an FMU is loaded, 67 | * and then the same FMU is loaded again before the first one has been 68 | * destroyed, the second call will return a reference to the first one. 69 | * (Two FMUs are deemed to be the same if they have the same path *or* the 70 | * same GUID.) 71 | * 72 | * \param [in] fmuPath 73 | * The path to the FMU file. 74 | * \returns 75 | * An object which represents the imported FMU. 76 | */ 77 | std::shared_ptr import(const cosim::filesystem::path& fmuPath); 78 | 79 | /** 80 | * Imports and loads an FMU that has already been unpacked. 81 | * 82 | * This is more or less equivalent to `import()`, but since the FMU is 83 | * already unpacked its contents will be read from the specified directory 84 | * rather than the cache. (The contents will not be copied to the cache.) 85 | * 86 | * \param [in] unpackedFMUPath 87 | * The path to a directory that holds the unpacked contents of an FMU. 88 | * \returns 89 | * An object which represents the imported FMU. 90 | */ 91 | std::shared_ptr import_unpacked( 92 | const cosim::filesystem::path& unpackedFMUPath); 93 | 94 | /// Returns the last FMI Library error message. 95 | std::string last_error_message(); 96 | 97 | /// Returns a pointer to the underlying FMI Library import context. 98 | fmi_import_context_t* fmilib_handle() const; 99 | 100 | private: 101 | void prune_ptr_caches(); 102 | 103 | // Note: The order of these declarations is important! 104 | std::shared_ptr fileCache_; 105 | std::unique_ptr callbacks_; 106 | std::unique_ptr handle_; 107 | 108 | std::map> pathCache_; 109 | std::map> guidCache_; 110 | }; 111 | 112 | 113 | } // namespace fmi 114 | } // namespace cosim 115 | #endif // header guard 116 | -------------------------------------------------------------------------------- /include/cosim/fs_portability.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_FS_PORTABILITY_HPP 10 | #define LIBCOSIM_FS_PORTABILITY_HPP 11 | 12 | #if __has_include() 13 | # include 14 | namespace cosim 15 | { 16 | namespace filesystem = std::filesystem; 17 | } 18 | #else 19 | # include 20 | namespace cosim 21 | { 22 | namespace filesystem = std::experimental::filesystem; 23 | } 24 | #endif 25 | 26 | #endif // LIBCOSIM_FS_PORTABILITY_HPP 27 | -------------------------------------------------------------------------------- /include/cosim/function/linear_transformation.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Linear transformation function. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_FUNCTION_LINEAR_TRANSFORMATION_HPP 11 | #define COSIM_FUNCTION_LINEAR_TRANSFORMATION_HPP 12 | 13 | #include 14 | 15 | 16 | namespace cosim 17 | { 18 | 19 | 20 | /** 21 | * A scalar linear transformation function instance. 22 | * 23 | * See `linear_transformation_function_type` for a description of this 24 | * function. 25 | */ 26 | class linear_transformation_function : public function 27 | { 28 | public: 29 | /// Reference to the "in" variable, for convencience. 30 | constexpr static auto in_io_reference = function_io_reference{0, 0, 0, 0}; 31 | 32 | /// Reference to the "out" variable, for convencience. 33 | constexpr static auto out_io_reference = function_io_reference{1, 0, 0, 0}; 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * \param [in] offset 39 | * The constant term. 40 | * \param [in] factor 41 | * The scaling factor. 42 | */ 43 | linear_transformation_function(double offset, double factor); 44 | 45 | // Overridden `function` methods 46 | function_description description() const override; 47 | void set_real(const function_io_reference& reference, double value) override; 48 | void set_integer(const function_io_reference& reference, int value) override; 49 | void set_boolean(const function_io_reference& reference, bool value) override; 50 | void set_string(const function_io_reference& reference, std::string_view value) override; 51 | double get_real(const function_io_reference& reference) const override; 52 | int get_integer(const function_io_reference& reference) const override; 53 | bool get_boolean(const function_io_reference& reference) const override; 54 | std::string_view get_string(const function_io_reference& reference) const override; 55 | void calculate() override; 56 | 57 | private: 58 | double offset_ = 0.0; 59 | double factor_ = 1.0; 60 | double input_ = 0.0; 61 | double output_ = 0.0; 62 | }; 63 | 64 | 65 | /** 66 | * A scalar linear transformation function type. 67 | * 68 | * ### Operation 69 | * 70 | * out = offset + factor * in 71 | * 72 | * ### Parameters 73 | * 74 | * | Parameter | Type | Default | Description | 75 | * |-----------|------|---------|-----------------------| 76 | * | offset | real | 0.0 | Constant term | 77 | * | factor | real | 1.0 | Linear scaling factor | 78 | * 79 | * ### Variables 80 | * 81 | * | Group | Count | Variable | Count | Causality | Type | Description | 82 | * |-------|-------|-----------|-------|-----------|------|--------------| 83 | * | in | 1 | (unnamed) | 1 | input | real | Input value | 84 | * | out | 1 | (unnamed) | 1 | output | real | Output value | 85 | * 86 | * ### Instance type 87 | * 88 | * `linear_transformation_function` 89 | */ 90 | class linear_transformation_function_type : public function_type 91 | { 92 | public: 93 | /// Parameter indexes, for convenience. 94 | enum 95 | { 96 | offset_parameter_index, 97 | factor_parameter_index, 98 | }; 99 | 100 | // Overridden `function_type` methods 101 | function_type_description description() const override; 102 | 103 | std::unique_ptr instantiate( 104 | const function_parameter_value_map& parameters) override; 105 | }; 106 | 107 | 108 | } // namespace cosim 109 | #endif // header guard 110 | -------------------------------------------------------------------------------- /include/cosim/function/utility.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Utilities for `function` implementers. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_FUNCTION_UTILITY_HPP 11 | #define COSIM_FUNCTION_UTILITY_HPP 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | namespace cosim 21 | { 22 | 23 | 24 | /** 25 | * Retrieves a parameter value from a parameter value map. 26 | * 27 | * This is a convenience function meant to aid in the implementation of 28 | * `function_type::instantiate()` by providing a simple and safe way 29 | * to extract parameter values from the map passed to this function. 30 | * 31 | * \param functionTypeDescription 32 | * The function type description (to get the list of parameter 33 | * definitions). 34 | * \param parameterValues 35 | * A mapping from parameter indexes to parameter values. 36 | * \param parameterIndex 37 | * The index of the parameter whose value should be retrieved. 38 | */ 39 | template 40 | T get_function_parameter( 41 | const function_type_description& functionTypeDescription, 42 | const function_parameter_value_map& parameterValues, 43 | int parameterIndex) 44 | { 45 | const auto& description = functionTypeDescription.parameters.at(parameterIndex); 46 | const auto it = parameterValues.find(parameterIndex); 47 | if (it == parameterValues.end()) { 48 | return std::get(description.default_value); 49 | } 50 | const auto value = std::get(it->second); 51 | if constexpr (std::is_arithmetic_v) { 52 | if ((description.min_value && value < std::get(*description.min_value)) || 53 | (description.max_value && value > std::get(*description.max_value))) { 54 | throw std::domain_error( 55 | "Parameter '" + description.name + "' is out of bounds"); 56 | } 57 | } 58 | return value; 59 | } 60 | 61 | 62 | /** 63 | * Returns a `function_description` with the same contents as the 64 | * `function_description` part of `functionTypeDescription`, but with 65 | * all placeholders replaced by actual parameter values. 66 | * 67 | * The `parameterValues` map *must* contain values for all placeholders 68 | * in `functionTypeDescription`. Otherwise, an exception is thrown. 69 | */ 70 | function_description substitute_function_parameters( 71 | const function_type_description& functionTypeDescription, 72 | const function_parameter_value_map& parameterValues); 73 | 74 | 75 | } // namespace cosim 76 | #endif // header guard 77 | -------------------------------------------------------------------------------- /include/cosim/lib_info.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Information about this library. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_LIB_INFO_HPP 11 | #define COSIM_LIB_INFO_HPP 12 | 13 | 14 | namespace cosim 15 | { 16 | 17 | 18 | /** 19 | * Short form of the library name, guaranteed to contain only alphanumeric 20 | * characters, dashes and underscores (no spaces). 21 | */ 22 | constexpr const char* library_short_name = "libcosim"; 23 | 24 | 25 | /// Software version 26 | struct version 27 | { 28 | int major = 0; 29 | int minor = 0; 30 | int patch = 0; 31 | }; 32 | 33 | 34 | /// Returns the version of the libcosim library. 35 | version library_version(); 36 | 37 | 38 | } // namespace cosim 39 | #endif // header guard 40 | -------------------------------------------------------------------------------- /include/cosim/log/logger.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Defines the logger for this library, based on Boost.Log. 4 | * 5 | * For convenience, this header also includes some Boost.Log headers that 6 | * provide useful short-hand logging macros (like `BOOST_LOG_SEV`). 7 | * 8 | * \copyright 9 | * This Source Code Form is subject to the terms of the Mozilla Public 10 | * License, v. 2.0. If a copy of the MPL was not distributed with this 11 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 12 | */ 13 | #ifndef COSIM_LOG_LOGGER_HPP 14 | #define COSIM_LOG_LOGGER_HPP 15 | 16 | #include 17 | #include // for convenience 18 | #include // for convenience 19 | #include 20 | #include 21 | 22 | 23 | namespace cosim 24 | { 25 | namespace log 26 | { 27 | 28 | 29 | /** 30 | * Severity levels for logged messages. 31 | * 32 | * This is currently an alias of `boost::log::trivial::severity_level`, 33 | * mainly to take advantage of the built-in support that this type has in 34 | * Boost.Log. However, this may change in the future, so it should be 35 | * treated and used as if it were a separate `enum` whose enumerators are 36 | * `trace`, `debug`, `info`, `warning`, `error`, and `fatal`. 37 | */ 38 | using severity_level = boost::log::trivial::severity_level; 39 | 40 | constexpr severity_level trace = boost::log::trivial::trace; 41 | constexpr severity_level debug = boost::log::trivial::debug; 42 | constexpr severity_level info = boost::log::trivial::info; 43 | constexpr severity_level warning = boost::log::trivial::warning; 44 | constexpr severity_level error = boost::log::trivial::error; 45 | constexpr severity_level fatal = boost::log::trivial::fatal; 46 | 47 | 48 | /** 49 | * Severity level keyword. 50 | * 51 | * This keyword should be used when referring to the "Severity" attribute 52 | * in filters and formatters. 53 | */ 54 | const auto severity = boost::log::trivial::severity; 55 | 56 | 57 | /// The logger type used by this library, a thread-safe severity logger. 58 | using logger_type = boost::log::sources::severity_logger_mt; 59 | 60 | 61 | /// The logger used by this library. 62 | logger_type& logger(); 63 | 64 | 65 | } // namespace log 66 | } // namespace cosim 67 | #endif // header guard 68 | -------------------------------------------------------------------------------- /include/cosim/log/simple.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Simple and convenient functions for setting up and controlling logging. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_LOG_SIMPLE_HPP 11 | #define COSIM_LOG_SIMPLE_HPP 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | 26 | namespace cosim 27 | { 28 | namespace log 29 | { 30 | 31 | 32 | /** 33 | * Convenience function that sets the global output log level. 34 | * 35 | * This function adds the following global filter: `severity >= lvl` 36 | */ 37 | inline void set_global_output_level(severity_level lvl) 38 | { 39 | boost::log::core::get()->set_filter(severity >= lvl); 40 | } 41 | 42 | 43 | /** 44 | * Convenience function that makes a simple log record formatter which includes 45 | * time stamp, severity and message. 46 | */ 47 | inline auto simple_formatter() 48 | { 49 | using namespace boost::log; 50 | return expressions::stream 51 | << expressions::format_date_time("TimeStamp", "%H:%M:%S.%f") 52 | << " [" << std::left << std::setw(7) << severity << "] " 53 | << expressions::smessage; 54 | } 55 | 56 | 57 | /** 58 | * Convenience function that sets up simple console logging. 59 | * 60 | * This function performs the following operations: 61 | * 62 | * 1. Calls `boost::log::add_common_attributes()`. 63 | * 2. Calls `boost::log::add_console_log()`. 64 | * 3. Sets the formatter of the console sink to `simple_formatter()`. 65 | */ 66 | inline void setup_simple_console_logging() 67 | { 68 | boost::log::add_common_attributes(); 69 | const auto sink = boost::log::add_console_log(); 70 | sink->set_formatter(simple_formatter()); 71 | } 72 | 73 | 74 | } // namespace log 75 | } // namespace cosim 76 | #endif // header guard 77 | -------------------------------------------------------------------------------- /include/cosim/manipulator.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Includes `manipulator/manipulator.hpp` plus all headers that define 4 | * concrete manipulator classes. 5 | * 6 | * \copyright 7 | * This Source Code Form is subject to the terms of the Mozilla Public 8 | * License, v. 2.0. If a copy of the MPL was not distributed with this 9 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 10 | */ 11 | 12 | #ifndef LIBCOSIM_MANIPULATOR_HPP 13 | #define LIBCOSIM_MANIPULATOR_HPP 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /include/cosim/manipulator/override_manipulator.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_MANIPULATOR_OVERRIDE_MANIPULATOR_HPP 10 | #define LIBCOSIM_MANIPULATOR_OVERRIDE_MANIPULATOR_HPP 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | namespace cosim 20 | { 21 | 22 | /** 23 | * A manipulator implementation handling overrides of variable values. 24 | */ 25 | class override_manipulator : public manipulator 26 | { 27 | public: 28 | void simulator_added(simulator_index, manipulable*, time_point) override; 29 | 30 | void simulator_removed(simulator_index, time_point) override; 31 | 32 | void step_commencing(time_point currentTime) override; 33 | 34 | /// Override the value of a variable with type `real` 35 | void override_real_variable(simulator_index, value_reference, double value); 36 | /// Override the value of a variable with type `integer` 37 | void override_integer_variable(simulator_index, value_reference, int value); 38 | /// Override the value of a variable with type `boolean` 39 | void override_boolean_variable(simulator_index, value_reference, bool value); 40 | /// Override the value of a variable with type `string` 41 | void override_string_variable(simulator_index, value_reference, const std::string& value); 42 | /// Reset override of a variable 43 | void reset_variable(simulator_index, variable_type, value_reference); 44 | 45 | ~override_manipulator() noexcept override; 46 | 47 | private: 48 | void add_action( 49 | simulator_index index, 50 | value_reference variable, 51 | variable_type type, 52 | const std::variant< 53 | scenario::real_modifier, 54 | scenario::integer_modifier, 55 | scenario::boolean_modifier, 56 | scenario::string_modifier>& m); 57 | 58 | std::unordered_map simulators_; 59 | std::vector actions_; 60 | std::mutex lock_; 61 | }; 62 | 63 | } // namespace cosim 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /include/cosim/manipulator/scenario_manager.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_MANIPULATOR_SCENARIO_MANAGER_HPP 10 | #define LIBCOSIM_MANIPULATOR_SCENARIO_MANAGER_HPP 11 | 12 | #include 13 | #include 14 | 15 | 16 | namespace cosim 17 | { 18 | 19 | /** 20 | * A manipulator implementation handling execution and control of scenarios. 21 | */ 22 | class scenario_manager : public manipulator 23 | { 24 | public: 25 | void simulator_added(simulator_index, manipulable*, time_point) override; 26 | 27 | void simulator_removed(simulator_index, time_point) override; 28 | 29 | void step_commencing( 30 | time_point currentTime) override; 31 | 32 | /// Constructor. 33 | scenario_manager(); 34 | 35 | scenario_manager(const scenario_manager&) = delete; 36 | scenario_manager& operator=(const scenario_manager&) = delete; 37 | 38 | scenario_manager(scenario_manager&&) noexcept; 39 | scenario_manager& operator=(scenario_manager&&) noexcept; 40 | 41 | ~scenario_manager() noexcept override; 42 | 43 | /** 44 | * Load a scenario for execution. 45 | * 46 | * This function may only be called after the object has been added to an 47 | * execution with `execution::add_manipulator()`. 48 | * 49 | * \param s 50 | * The in-memory constructed scenario. 51 | * \param currentTime 52 | * The time point at which the scenario will start. The scenario's events 53 | * will be executed relative to this time point. 54 | */ 55 | void load_scenario(const scenario::scenario& s, time_point currentTime); 56 | 57 | /** 58 | * Load a scenario for execution. 59 | * 60 | * This function may only be called after the object has been added to an 61 | * execution with `execution::add_manipulator()`. 62 | * 63 | * \param scenarioFile 64 | * The path to a proprietary `json` file defining the scenario. 65 | * \param currentTime 66 | * The time point at which the scenario will start. The scenario's events 67 | * will be executed relative to this time point. 68 | */ 69 | void load_scenario(const cosim::filesystem::path& scenarioFile, time_point currentTime); 70 | 71 | /// Return if a scenario is running. 72 | bool is_scenario_running(); 73 | 74 | /// Abort the execution of a running scenario. 75 | void abort_scenario(); 76 | 77 | private: 78 | class impl; 79 | std::unique_ptr pimpl_; 80 | }; 81 | 82 | } // namespace cosim 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /include/cosim/observer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Includes `observer/observer.hpp` plus all headers that define 4 | * concrete observer classes. 5 | * 6 | * \copyright 7 | * This Source Code Form is subject to the terms of the Mozilla Public 8 | * License, v. 2.0. If a copy of the MPL was not distributed with this 9 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 10 | */ 11 | #ifndef COSIM_OBSERVER_HPP 12 | #define COSIM_OBSERVER_HPP 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #endif // header guard 20 | -------------------------------------------------------------------------------- /include/cosim/observer/last_value_observer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Observer-related functionality. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_OBSERVER_LAST_VALUE_OBSERVER_HPP 11 | #define COSIM_OBSERVER_LAST_VALUE_OBSERVER_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | 22 | namespace cosim 23 | { 24 | 25 | class slave_value_provider; 26 | 27 | 28 | /** 29 | * An observer implementation, storing the last observed variable values in memory. 30 | */ 31 | class last_value_observer : public last_value_provider 32 | { 33 | public: 34 | last_value_observer(); 35 | 36 | void simulator_added(simulator_index, observable*, time_point) override; 37 | 38 | void simulator_removed(simulator_index, time_point) override; 39 | 40 | void variables_connected(variable_id output, variable_id input, time_point) override; 41 | 42 | void variable_disconnected(variable_id input, time_point) override; 43 | 44 | void simulation_initialized( 45 | step_number firstStep, 46 | time_point startTime) override; 47 | 48 | void step_complete( 49 | step_number lastStep, 50 | duration lastStepSize, 51 | time_point currentTime) override; 52 | 53 | void simulator_step_complete( 54 | simulator_index index, 55 | step_number lastStep, 56 | duration lastStepSize, 57 | time_point currentTime) override; 58 | 59 | void state_restored(step_number currentStep, time_point currentTime) override; 60 | 61 | void get_real( 62 | simulator_index sim, 63 | gsl::span variables, 64 | gsl::span values) override; 65 | 66 | void get_integer( 67 | simulator_index sim, 68 | gsl::span variables, 69 | gsl::span values) override; 70 | 71 | void get_boolean( 72 | simulator_index sim, 73 | gsl::span variables, 74 | gsl::span values) override; 75 | 76 | void get_string( 77 | simulator_index sim, 78 | gsl::span variables, 79 | gsl::span values) override; 80 | 81 | ~last_value_observer() noexcept override; 82 | 83 | private: 84 | std::unordered_map> valueProviders_; 85 | }; 86 | 87 | 88 | } // namespace cosim 89 | #endif // header guard 90 | -------------------------------------------------------------------------------- /include/cosim/observer/last_value_provider.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_LAST_VALUE_PROVIDER_HPP 10 | #define LIBCOSIM_LAST_VALUE_PROVIDER_HPP 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | 19 | namespace cosim 20 | { 21 | 22 | /** 23 | * An interface for last-value providers. 24 | * 25 | * The methods in this interface represent ways to extract data from an 26 | * observer providing the last observed values for a range of variables. 27 | */ 28 | class last_value_provider : public observer 29 | { 30 | public: 31 | /** 32 | * Retrieves the latest observed values for a range of real variables. 33 | * 34 | * \param [in] sim index of the simulator 35 | * \param [in] variables the variable indices to retrieve values for 36 | * \param [out] values a collection where the observed values will be stored 37 | */ 38 | virtual void get_real( 39 | simulator_index sim, 40 | gsl::span variables, 41 | gsl::span values) = 0; 42 | 43 | /** 44 | * Retrieves the latest observed values for a range of integer variables. 45 | * 46 | * \param [in] sim index of the simulator 47 | * \param [in] variables the variable indices to retrieve values for 48 | * \param [out] values a collection where the observed values will be stored 49 | */ 50 | virtual void get_integer( 51 | simulator_index sim, 52 | gsl::span variables, 53 | gsl::span values) = 0; 54 | 55 | /** 56 | * Retrieves the latest observed values for a range of boolean variables. 57 | * 58 | * \param [in] sim index of the simulator 59 | * \param [in] variables the variable indices to retrieve values for 60 | * \param [out] values a collection where the observed values will be stored 61 | */ 62 | virtual void get_boolean( 63 | simulator_index sim, 64 | gsl::span variables, 65 | gsl::span values) = 0; 66 | 67 | /** 68 | * Retrieves the latest observed values for a range of string variables. 69 | * 70 | * \param [in] sim index of the simulator 71 | * \param [in] variables the variable indices to retrieve values for 72 | * \param [out] values a collection where the observed values will be stored 73 | */ 74 | virtual void get_string( 75 | simulator_index sim, 76 | gsl::span variables, 77 | gsl::span values) = 0; 78 | }; 79 | 80 | } // namespace cosim 81 | 82 | #endif // LIBCOSIM_LAST_VALUE_PROVIDER_HPP 83 | -------------------------------------------------------------------------------- /include/cosim/observer/time_series_observer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Observer-related functionality. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_OBSERVER_TIME_SERIES_OBSERVER_HPP 11 | #define COSIM_OBSERVER_TIME_SERIES_OBSERVER_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | 22 | namespace cosim 23 | { 24 | 25 | 26 | class slave_value_provider; 27 | 28 | 29 | /** 30 | * An observer implementation, storing all observed variable values for a user-specified set of variables in memory. 31 | */ 32 | class time_series_observer : public time_series_provider 33 | { 34 | public: 35 | /** 36 | * Default constructor. Creates a buffered `time_series_observer`, with a fixed buffer size of 10000 samples for each observed variable. 37 | */ 38 | time_series_observer(); 39 | 40 | /** 41 | * Constructor for a buffered `time_series_observer`, which will store up to `bufferSize` samples for each observed variable. 42 | */ 43 | explicit time_series_observer(size_t bufferSize); 44 | 45 | void simulator_added(simulator_index, observable*, time_point) override; 46 | 47 | void simulator_removed(simulator_index, time_point) override; 48 | 49 | void variables_connected(variable_id output, variable_id input, time_point) override; 50 | 51 | void variable_disconnected(variable_id input, time_point) override; 52 | 53 | void simulation_initialized( 54 | step_number firstStep, 55 | time_point startTime) override; 56 | 57 | void step_complete( 58 | step_number lastStep, 59 | duration lastStepSize, 60 | time_point currentTime) override; 61 | 62 | void simulator_step_complete( 63 | simulator_index index, 64 | step_number lastStep, 65 | duration lastStepSize, 66 | time_point currentTime) override; 67 | 68 | void state_restored(step_number currentStep, time_point currentTime) override; 69 | 70 | /** 71 | * Start observing a variable. 72 | * 73 | * After calling this method, it will then be possible to extract observed values for this variable 74 | * with `get_real_samples()` or `get_integer_samples()`. 75 | */ 76 | void start_observing(variable_id id); 77 | 78 | /** 79 | * Stop observing a variable. 80 | * 81 | * After calling this method, it will no longer be possible to extract observed values for this variable. 82 | */ 83 | void stop_observing(variable_id id); 84 | 85 | std::size_t get_real_samples( 86 | simulator_index sim, 87 | value_reference valueReference, 88 | step_number fromStep, 89 | gsl::span values, 90 | gsl::span steps, 91 | gsl::span times) override; 92 | 93 | std::size_t get_integer_samples( 94 | simulator_index sim, 95 | value_reference valueReference, 96 | step_number fromStep, 97 | gsl::span values, 98 | gsl::span steps, 99 | gsl::span times) override; 100 | 101 | void get_step_numbers( 102 | simulator_index sim, 103 | duration duration, 104 | gsl::span steps) override; 105 | 106 | void get_step_numbers( 107 | simulator_index sim, 108 | time_point tBegin, 109 | time_point tEnd, 110 | gsl::span steps) override; 111 | 112 | std::size_t get_synchronized_real_series( 113 | simulator_index sim1, 114 | value_reference valueReference1, 115 | simulator_index sim2, 116 | value_reference valueReference2, 117 | step_number fromStep, 118 | gsl::span values1, 119 | gsl::span values2) override; 120 | 121 | ~time_series_observer() noexcept override; 122 | 123 | private: 124 | class single_slave_observer; 125 | size_t bufSize_; 126 | std::unordered_map> slaveObservers_; 127 | }; 128 | 129 | } // namespace cosim 130 | #endif // header guard 131 | -------------------------------------------------------------------------------- /include/cosim/osp_config_parser.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_OSP_CONFIG_PARSER_HPP 10 | #define LIBCOSIM_OSP_CONFIG_PARSER_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace cosim 19 | { 20 | 21 | 22 | /// The information contained in an OSP-IS system structure file. 23 | struct osp_config 24 | { 25 | /// The system structure 26 | cosim::system_structure system_structure; 27 | 28 | // The algorithm 29 | std::variant algorithm_configuration; 30 | 31 | /// The default start time for a simulation 32 | time_point start_time; 33 | 34 | /// The optional end time for a simulation 35 | std::optional end_time = std::nullopt; 36 | 37 | /// A set of default initial values 38 | variable_value_map initial_values; 39 | }; 40 | 41 | 42 | /** 43 | * Loads an OSP-IS system structure file. 44 | */ 45 | osp_config load_osp_config( 46 | const cosim::filesystem::path& configPath, 47 | cosim::model_uri_resolver& resolver); 48 | 49 | 50 | } // namespace cosim 51 | #endif // LIBCOSIM_OSP_CONFIG_PARSER_HPP 52 | -------------------------------------------------------------------------------- /include/cosim/scenario.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_SCENARIO_HPP 10 | #define LIBCOSIM_SCENARIO_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace cosim 23 | { 24 | 25 | namespace scenario 26 | { 27 | 28 | /// The modification of the value of a variable with type `real`. 29 | struct real_modifier 30 | { 31 | /// A function which may be called any number of times. Can be `nullptr`. 32 | std::function f; 33 | }; 34 | 35 | /// The modification of the value of a variable with type `integer`. 36 | struct integer_modifier 37 | { 38 | /// A function which may be called any number of times. Can be `nullptr`. 39 | std::function f; 40 | }; 41 | 42 | /// The modification of the value of a variable with type `boolean`. 43 | struct boolean_modifier 44 | { 45 | /// A function which may be called any number of times. Can be `nullptr`. 46 | std::function f; 47 | }; 48 | 49 | /// The modification of the value of a variable with type `string`. 50 | struct string_modifier 51 | { 52 | /// A function which may be called any number of times. Can be `nullptr`. 53 | std::function f; 54 | }; 55 | 56 | /// A struct specifying a variable and the modification of its value. 57 | struct variable_action 58 | { 59 | /// The simulator index. 60 | simulator_index simulator; 61 | /// The variable value reference. 62 | value_reference variable; 63 | /// The modification to be done to the variable's value. 64 | std::variant< 65 | real_modifier, 66 | integer_modifier, 67 | boolean_modifier, 68 | string_modifier> 69 | modifier; 70 | /** 71 | * Flag which should be set to `true` if the variable is an *input* to the 72 | * slave (i.e. causality input or parameter), or `false` if the variable is 73 | * an *output* from a slave (i.e. causality output or calculatedParameter). 74 | */ 75 | bool is_input; 76 | }; 77 | 78 | /// A struct representing an event. 79 | struct event 80 | { 81 | /// The time point at which the event should trigger. 82 | time_point time; 83 | /// Something which should happen to a variable. 84 | variable_action action; 85 | }; 86 | 87 | /// A struct representing an executable scenario. 88 | struct scenario 89 | { 90 | /// A collection of time-based events. 91 | std::vector events; 92 | /// An optional time point at which the scenario should terminate. 93 | std::optional end; 94 | }; 95 | 96 | } // namespace scenario 97 | 98 | } // namespace cosim 99 | 100 | #endif // LIBCOSIM_SCENARIO_HPP 101 | -------------------------------------------------------------------------------- /include/cosim/scenario_parser.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_SCENARIO_PARSER_H 10 | #define LIBCOSIM_SCENARIO_PARSER_H 11 | 12 | #include 13 | #include 14 | 15 | namespace cosim 16 | { 17 | /** 18 | * Parses a scenario from file. 19 | * 20 | * \param scenarioFile 21 | * The path to a proprietary `json` or `yaml` file defining the scenario. 22 | * 23 | * \param simulators 24 | * A map containing the simulators currently loaded in the execution. 25 | */ 26 | scenario::scenario parse_scenario( 27 | const cosim::filesystem::path& scenarioFile, 28 | const std::unordered_map& simulators); 29 | } // namespace cosim 30 | 31 | #endif // LIBCOSIM_SCENARIO_PARSER_H 32 | -------------------------------------------------------------------------------- /include/cosim/ssp/ssp_loader.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_SSP_LOADER_HPP 10 | #define LIBCOSIM_SSP_LOADER_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | namespace cosim 24 | { 25 | 26 | /// A configuration loaded from an SSP file. 27 | struct ssp_configuration 28 | { 29 | /// The system structure. 30 | cosim::system_structure system_structure; 31 | 32 | /// The start time. 33 | time_point start_time; 34 | 35 | /// The co-simulation algorithm. 36 | std::shared_ptr algorithm; 37 | 38 | /** 39 | * Named parameter sets. 40 | * 41 | * This always contains at least one parameter set whose key is the 42 | * empty string, which represents the default parameter values. 43 | * (If the SSP configuration does not specify any parameter values, 44 | * this set will be empty but nevertheless present.) 45 | */ 46 | std::unordered_map parameter_sets; 47 | }; 48 | 49 | 50 | /// Class for loading an execution from a SSP configuration. 51 | class ssp_loader 52 | { 53 | 54 | public: 55 | ssp_loader(); 56 | 57 | /** 58 | * Assign a custom `model_uri_resolver`. 59 | * 60 | * \param [in] modelResolver 61 | * The `model_uri_resolver` to use by the loader. 62 | */ 63 | void set_model_uri_resolver(std::shared_ptr resolver); 64 | 65 | /** 66 | * Specify a non-default SystemStructureDefinition (.ssd) file to load. 67 | * 68 | * \param [in] name 69 | * The name of the (custom) SystemStructureDefinition file to load. 70 | */ 71 | void set_ssd_file_name(const std::string& name); 72 | 73 | /** 74 | * Load an SSP configuration. 75 | * 76 | * \param [in] configPath 77 | * Path to the .ssp archive, or a directory holding one or more .ssd files. 78 | */ 79 | ssp_configuration load(const cosim::filesystem::path& configPath); 80 | 81 | private: 82 | std::optional ssdFileName_; 83 | std::shared_ptr modelResolver_; 84 | }; 85 | 86 | 87 | } // namespace cosim 88 | #endif 89 | -------------------------------------------------------------------------------- /include/cosim/timer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_TIMER_H 10 | #define LIBCOSIM_TIMER_H 11 | 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | namespace cosim 20 | { 21 | 22 | /// A struct containing real time execution configuration. 23 | struct real_time_config 24 | { 25 | /// Real-time-synchronized simulation on or off. 26 | std::atomic real_time_simulation = false; 27 | 28 | /** 29 | * Real time factor target used for real-time-synchronized simulation. 30 | * Values smaller than or equal to zero will disable real-time synchronization. 31 | */ 32 | std::atomic real_time_factor_target = 1.0; 33 | 34 | /** 35 | * The number of steps used in the rolling average real time factor calculation. 36 | * This value is used for monitoring purposes only. 37 | */ 38 | std::atomic steps_to_monitor = 5; 39 | 40 | /** 41 | * A sampling period in real time (wall clock) in milliseconds used in the rolling average real time factor calculation. 42 | * This can be useful when simulation step size is small and a fixed value for `steps_to_monitor` 43 | * would not compute an accurate rolling average real time factor. 44 | * When the value is greater than zero, the real time factor is computed periodically using the 45 | * specified time instead of the `steps_to_monitor` value. 46 | */ 47 | std::atomic sampling_period_to_monitor = std::chrono::milliseconds(-1); 48 | }; 49 | 50 | } // namespace cosim 51 | 52 | // Specialisations of std::hash for real_time_config 53 | namespace std 54 | { 55 | 56 | template<> 57 | class hash 58 | { 59 | public: 60 | std::size_t operator()(const cosim::real_time_config& v) const noexcept 61 | { 62 | std::size_t seed = 0; 63 | boost::hash_combine(seed, v.real_time_simulation.load()); 64 | boost::hash_combine(seed, v.real_time_factor_target.load()); 65 | boost::hash_combine(seed, v.steps_to_monitor.load()); 66 | boost::hash_combine(seed, v.sampling_period_to_monitor.load().count()); 67 | return seed; 68 | } 69 | }; 70 | 71 | } // namespace std 72 | 73 | namespace cosim 74 | { 75 | 76 | /// A struct containing real time metrics. 77 | struct real_time_metrics 78 | { 79 | /// The current rolling average real time factor measurement. 80 | std::atomic rolling_average_real_time_factor = 1.0; 81 | /// The total average real time factor measurement since the simulation was started. 82 | std::atomic total_average_real_time_factor = 1.0; 83 | }; 84 | 85 | /** 86 | * A class for controlling real-time execution. 87 | */ 88 | class real_time_timer 89 | { 90 | public: 91 | /** 92 | * Reset the timer. To be called when the execution is started/resumed. 93 | * 94 | * \param [in] currentTime The current simulation time. 95 | */ 96 | void start(time_point currentTime); 97 | 98 | /** 99 | * Calls thread sleep for the amount of time it would take to keep real time. 100 | * 101 | * If real time simulation is enabled, expected progress as well as elapsed time 102 | * are calculated. Thread sleep is called for the amount of time it would take 103 | * to synchronize against real time. 104 | * 105 | * To be called at the tail end of each execution step. 106 | * 107 | * \param [in] currentTime The current simulation time. 108 | */ 109 | void sleep(time_point currentTime); 110 | 111 | std::shared_ptr get_real_time_config() const; 112 | 113 | /// Returns a pointer to an object containing real time metrics 114 | std::shared_ptr get_real_time_metrics() const; 115 | 116 | /// Constructor 117 | real_time_timer(); 118 | ~real_time_timer() noexcept; 119 | 120 | private: 121 | class impl; 122 | std::unique_ptr pimpl_; 123 | }; 124 | 125 | } // namespace cosim 126 | #endif // LIBCOSIM_TIMER_H 127 | -------------------------------------------------------------------------------- /src/cosim/error.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/error.hpp" 7 | 8 | #include 9 | #include 10 | 11 | 12 | namespace cosim 13 | { 14 | namespace detail 15 | { 16 | 17 | 18 | void precondition_violated( 19 | const char* function, 20 | const char* condition) noexcept 21 | { 22 | std::fprintf( 23 | stderr, 24 | "%s: Precondition violated: %s\n", 25 | function, 26 | condition); 27 | std::fflush(stderr); 28 | std::terminate(); 29 | } 30 | 31 | 32 | void panic(const char* file, int line, const char* msg) noexcept 33 | { 34 | std::fprintf(stderr, "%s:%d: Internal error", file, line); 35 | if (msg != nullptr) { 36 | std::fprintf(stderr, ": %s", msg); 37 | } 38 | std::fputc('\n', stderr); 39 | std::fflush(stderr); 40 | std::terminate(); 41 | } 42 | } // namespace detail 43 | } // namespace cosim 44 | -------------------------------------------------------------------------------- /src/cosim/exception.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/exception.hpp" 7 | 8 | #include "cosim/error.hpp" 9 | #include "cosim/lib_info.hpp" 10 | 11 | 12 | namespace cosim 13 | { 14 | 15 | 16 | namespace 17 | { 18 | class my_error_category : public std::error_category 19 | { 20 | public: 21 | const char* name() const noexcept final override 22 | { 23 | return library_short_name; 24 | } 25 | 26 | std::string message(int ev) const final override 27 | { 28 | switch (static_cast(ev)) { 29 | case errc::success: 30 | return "Success"; 31 | case errc::bad_file: 32 | return "Bad file"; 33 | case errc::unsupported_feature: 34 | return "Unsupported feature"; 35 | case errc::dl_load_error: 36 | return "Error loading dynamic library"; 37 | case errc::model_error: 38 | return "Model error"; 39 | case errc::nonfatal_bad_value: 40 | return "Variable value is invalid or out of range"; 41 | case errc::simulation_error: 42 | return "Simulation error"; 43 | case errc::invalid_system_structure: 44 | return "Invalid system structure"; 45 | case errc::zip_error: 46 | return "ZIP file error"; 47 | default: 48 | COSIM_PANIC(); 49 | } 50 | } 51 | }; 52 | } // namespace 53 | 54 | 55 | const std::error_category& error_category() noexcept 56 | { 57 | static my_error_category instance; 58 | return instance; 59 | } 60 | 61 | 62 | std::error_condition make_error_condition(errc e) noexcept 63 | { 64 | return std::error_condition(static_cast(e), error_category()); 65 | } 66 | 67 | 68 | std::error_code make_error_code(errc e) noexcept 69 | { 70 | return std::error_code(static_cast(e), error_category()); 71 | } 72 | 73 | 74 | } // namespace cosim 75 | -------------------------------------------------------------------------------- /src/cosim/fmi/fmilib.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Includes the FMI Library header, locally disabling some compilation 4 | * warnings for it. 5 | * 6 | * \copyright 7 | * This Source Code Form is subject to the terms of the Mozilla Public 8 | * License, v. 2.0. If a copy of the MPL was not distributed with this 9 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 10 | */ 11 | #ifndef COSIM_FMI_FMILIB_H 12 | #define COSIM_FMI_FMILIB_H 13 | 14 | #ifdef _MSC_VER 15 | # pragma warning(push, 0) 16 | #endif 17 | #ifdef __GNUC__ 18 | # pragma GCC diagnostic push 19 | # pragma GCC diagnostic ignored "-Wunused-function" 20 | # pragma GCC diagnostic ignored "-Wunused-parameter" 21 | #endif 22 | 23 | #include 24 | 25 | #ifdef _MSC_VER 26 | # pragma warning(pop) 27 | #endif 28 | #ifdef __GNUC__ 29 | # pragma GCC diagnostic pop 30 | #endif 31 | 32 | #endif // header guard 33 | -------------------------------------------------------------------------------- /src/cosim/fmi/glue.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Conversions between FMI variable attributes and "our" attributes. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_FMI_GLUE_HPP 11 | #define COSIM_FMI_GLUE_HPP 12 | 13 | #include "cosim/fmi/fmilib.h" 14 | #include "cosim/model_description.hpp" 15 | 16 | 17 | namespace cosim 18 | { 19 | namespace fmi 20 | { 21 | 22 | 23 | /// Converts an FMI 1.0 base type to "our" data type. 24 | variable_type to_variable_type(fmi1_base_type_enu_t t); 25 | 26 | 27 | /// Converts an FMI 2.0 base type to "our" data type. 28 | variable_type to_variable_type(fmi2_base_type_enu_t t); 29 | 30 | 31 | /** 32 | * Converts an FMI 1.0 variable causality to "our" corresponding causality. 33 | * 34 | * The causality mapping is not unique, so the variable's variability is also 35 | * needed. 36 | */ 37 | variable_causality to_variable_causality( 38 | fmi1_causality_enu_t c, 39 | fmi1_variability_enu_t v); 40 | 41 | 42 | /// Converts an FMI 2.0 variable causality to "our" corresponding causality. 43 | variable_causality to_variable_causality(fmi2_causality_enu_t c); 44 | 45 | 46 | /// Converts an FMI 1.0 variable variability to "our" corresponding variability. 47 | variable_variability to_variable_variability(fmi1_variability_enu_t v); 48 | 49 | 50 | /// Converts an FMI 2.0 variable variability to "our" corresponding variability. 51 | variable_variability to_variable_variability(fmi2_variability_enu_t v); 52 | 53 | 54 | /// Converts an FMI 1.0 variable description to a `variable_description` object. 55 | variable_description to_variable_description(fmi1_import_variable_t* fmiVariable); 56 | 57 | 58 | /// Converts an FMI 2.0 variable description to a `variable_description` object. 59 | variable_description to_variable_description(fmi2_import_variable_t* fmiVariable); 60 | 61 | 62 | } // namespace fmi 63 | } // namespace cosim 64 | #endif // header guard 65 | -------------------------------------------------------------------------------- /src/cosim/fmi/windows.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/fmi/windows.hpp" 7 | #ifdef _WIN32 8 | 9 | # include 10 | # include 11 | # include 12 | # include 13 | 14 | 15 | namespace cosim 16 | { 17 | namespace fmi 18 | { 19 | 20 | namespace 21 | { 22 | // Maximum size of environment variables 23 | constexpr std::size_t max_env_var_size = 32767; 24 | 25 | // Mutex to protect against concurrent access to PATH 26 | std::mutex path_env_var_mutex; 27 | } // namespace 28 | 29 | 30 | detail::additional_path::additional_path(const cosim::filesystem::path& p) 31 | { 32 | std::lock_guard lock(path_env_var_mutex); 33 | 34 | WCHAR currentPathZ[max_env_var_size]; 35 | const auto currentPathLen = 36 | GetEnvironmentVariableW(L"PATH", currentPathZ, max_env_var_size); 37 | const auto currentPath = std::wstring(currentPathZ, currentPathLen); 38 | 39 | if (currentPathLen > 0) { 40 | addedPath_ = L";"; 41 | } 42 | addedPath_ += p.wstring(); 43 | 44 | const auto newPath = currentPath + addedPath_; 45 | if (!SetEnvironmentVariableW(L"PATH", newPath.c_str())) { 46 | assert(!"Failed to modify PATH environment variable"); 47 | } 48 | } 49 | 50 | 51 | detail::additional_path::~additional_path() 52 | { 53 | std::lock_guard lock(path_env_var_mutex); 54 | 55 | WCHAR currentPathZ[max_env_var_size]; 56 | const auto currentPathLen = 57 | GetEnvironmentVariableW(L"PATH", currentPathZ, max_env_var_size); 58 | const auto currentPath = std::wstring(currentPathZ, currentPathLen); 59 | 60 | const auto pos = currentPath.find(addedPath_); 61 | if (pos < std::wstring::npos) { 62 | auto newPath = currentPath.substr(0, pos) + currentPath.substr(pos + addedPath_.size()); 63 | if (!SetEnvironmentVariableW(L"PATH", newPath.c_str())) { 64 | assert(!"Failed to reset PATH environment variable"); 65 | } 66 | } 67 | } 68 | 69 | 70 | cosim::filesystem::path fmu_binaries_dir(const cosim::filesystem::path& baseDir) 71 | { 72 | # ifdef _WIN64 73 | const auto platformSubdir = L"win64"; 74 | # else 75 | const auto platformSubdir = L"win32"; 76 | # endif // _WIN64 77 | return baseDir / L"binaries" / platformSubdir; 78 | } 79 | 80 | 81 | } // namespace fmi 82 | } // namespace cosim 83 | #endif // _WIN32 84 | -------------------------------------------------------------------------------- /src/cosim/fmi/windows.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Windows-specific things. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_FMI_WINDOWS_HPP 11 | #define COSIM_FMI_WINDOWS_HPP 12 | #ifdef _WIN32 13 | 14 | # include 15 | 16 | # include 17 | 18 | 19 | namespace cosim 20 | { 21 | namespace fmi 22 | { 23 | namespace detail 24 | { 25 | 26 | 27 | /** 28 | * Temporarily adds a path to the `PATH` environment variable for the current 29 | * process. 30 | * 31 | * The path is added to `PATH` when the class is instantiated, and removed 32 | * again when the instance is destroyed. 33 | * 34 | * The purpose of this class is to add an FMU's `binaries/` directory 35 | * to Windows' DLL search path. This solves a problem where Windows was 36 | * unable to locate some DLLs that are indirectly loaded. Specifically, 37 | * the problem has been observed when the main FMU model DLL runs Java code 38 | * (through JNI), and that Java code loaded a second DLL, which again was 39 | * linked to further DLLs. The latter were located in the 40 | * `binaries/` directory, but were not found by the dynamic loader 41 | * because that directory was not in the search path. 42 | * 43 | * Since environment variables are shared by the entire process, the functions 44 | * use a mutex to protect against concurrent access to the `PATH` variable 45 | * while it's being read, modified and written. (This does not protect 46 | * against access by client code, of course, which is a potential source of 47 | * bugs.) 48 | */ 49 | class additional_path 50 | { 51 | public: 52 | /// Constructor. Adds `p` to `PATH`. 53 | additional_path(const cosim::filesystem::path& p); 54 | 55 | /// Destructor. Removes the path from `PATH` again. 56 | ~additional_path(); 57 | 58 | private: 59 | std::wstring addedPath_; 60 | }; 61 | 62 | 63 | } // namespace detail 64 | 65 | 66 | /// Given `path/to/fmu`, returns `path/to/fmu/binaries/` 67 | cosim::filesystem::path fmu_binaries_dir(const cosim::filesystem::path& baseDir); 68 | 69 | 70 | } // namespace fmi 71 | } // namespace cosim 72 | 73 | #endif // _WIN32 74 | #endif // header guard 75 | -------------------------------------------------------------------------------- /src/cosim/function/utility.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/function/utility.hpp" 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | 14 | namespace cosim 15 | { 16 | namespace 17 | { 18 | template 19 | class replace_placeholder 20 | { 21 | public: 22 | replace_placeholder( 23 | const function_parameter_value_map& parameterValues, 24 | const std::vector& parameterDescriptions) 25 | : parameterValues_(parameterValues) 26 | , parameterDescriptions_(parameterDescriptions) 27 | { } 28 | 29 | T operator()(const T& value) { return value; } 30 | 31 | T operator()(const function_parameter_placeholder& placeholder) 32 | { 33 | const auto index = placeholder.parameter_index; 34 | if (index < 0 || 35 | static_cast(index) >= parameterDescriptions_.size()) { 36 | throw std::out_of_range( 37 | "Invalid parameter index in placeholder: " + 38 | std::to_string(index)); 39 | } 40 | const auto& descr = parameterDescriptions_[index]; 41 | 42 | auto valueIt = parameterValues_.find(placeholder.parameter_index); 43 | if (valueIt == parameterValues_.end()) { 44 | return get_value(descr.default_value, descr); 45 | } 46 | const auto value = get_value(valueIt->second, descr); 47 | if constexpr (std::is_arithmetic_v) { 48 | if (descr.min_value && value < std::get(*descr.min_value)) { 49 | throw std::domain_error( 50 | "Value of parameter '" + descr.name + "' too small: " + 51 | boost::lexical_cast(value)); 52 | } 53 | if (descr.max_value && value > std::get(*descr.max_value)) { 54 | throw std::domain_error( 55 | "Value of parameter '" + descr.name + "' too small: " + 56 | boost::lexical_cast(value)); 57 | } 58 | } 59 | return value; 60 | } 61 | 62 | private: 63 | static T get_value( 64 | const function_parameter_value& v, 65 | const function_parameter_description& d) 66 | { 67 | if (const auto p = std::get_if(&v)) return *p; 68 | throw std::logic_error( 69 | "Parameter '" + d.name + "': Illegal value type"); 70 | } 71 | 72 | const function_parameter_value_map& parameterValues_; 73 | const std::vector& parameterDescriptions_; 74 | }; 75 | } // namespace 76 | 77 | 78 | function_description substitute_function_parameters( 79 | const function_type_description& functionTypeDescription, 80 | const function_parameter_value_map& parameterValues) 81 | { 82 | auto functionDescription = function_description(functionTypeDescription); 83 | for (auto& group : functionDescription.io_groups) { 84 | group.count = std::visit( 85 | replace_placeholder(parameterValues, functionTypeDescription.parameters), 86 | group.count); 87 | 88 | for (auto& io : group.ios) { 89 | io.count = std::visit( 90 | replace_placeholder(parameterValues, functionTypeDescription.parameters), 91 | io.count); 92 | io.type = std::visit( 93 | replace_placeholder(parameterValues, functionTypeDescription.parameters), 94 | io.type); 95 | } 96 | } 97 | return functionDescription; 98 | } 99 | 100 | } // namespace cosim 101 | -------------------------------------------------------------------------------- /src/cosim/function/vector_sum.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/function/vector_sum.hpp" 7 | 8 | #include "cosim/function/utility.hpp" 9 | 10 | 11 | using namespace std::literals; 12 | 13 | namespace cosim 14 | { 15 | 16 | 17 | namespace detail 18 | { 19 | function_type_description vector_sum_description( 20 | std::variant inputCount, 21 | std::variant numericType, 22 | std::variant dimension) 23 | { 24 | function_type_description f; 25 | f.parameters = { 26 | // parameters (in the same order as the enum in vector_sum_function_type!) 27 | function_parameter_description{ 28 | "inputCount"s, // name 29 | function_parameter_type::integer, // type 30 | 1, // default_value 31 | 1, // min_value 32 | {} // max_value 33 | }, 34 | function_parameter_description{ 35 | "numericType"s, // name 36 | function_parameter_type::type, // type 37 | variable_type::real, // default_value 38 | {}, // min_value 39 | {} // max_value 40 | }, 41 | function_parameter_description{ 42 | "dimension"s, // name 43 | function_parameter_type::integer, // type 44 | 1, // default_value 45 | 1, // min_value 46 | {} // max_value 47 | }, 48 | }; 49 | f.io_groups = { 50 | // io_groups 51 | function_io_group_description{ 52 | "in"s, // name 53 | inputCount, // count 54 | { 55 | // ios 56 | function_io_description{ 57 | ""s, // name (inherited from group) 58 | numericType, // type 59 | variable_causality::input, // causality 60 | dimension, // count 61 | }, 62 | }}, 63 | function_io_group_description{ 64 | "out"s, // name 65 | 1, // count 66 | { 67 | // ios 68 | function_io_description{ 69 | ""s, // name = (inherited from group) 70 | numericType, // type 71 | variable_causality::output, // causality 72 | dimension, // count 73 | }, 74 | }}, 75 | }; 76 | return f; 77 | } 78 | } // namespace detail 79 | 80 | 81 | function_type_description vector_sum_function_type::description() const 82 | { 83 | return detail::vector_sum_description( 84 | function_parameter_placeholder{vector_sum_function_type::inputCount_parameter_index}, 85 | function_parameter_placeholder{vector_sum_function_type::numericType_parameter_index}, 86 | function_parameter_placeholder{vector_sum_function_type::dimension_parameter_index}); 87 | } 88 | 89 | 90 | std::unique_ptr vector_sum_function_type::instantiate( 91 | const function_parameter_value_map& parameters) 92 | { 93 | const auto descr = description(); 94 | const auto inputCount = get_function_parameter( 95 | descr, parameters, vector_sum_function_type::inputCount_parameter_index); 96 | const auto numericType = get_function_parameter( 97 | descr, parameters, vector_sum_function_type::numericType_parameter_index); 98 | const auto dimension = get_function_parameter( 99 | descr, parameters, vector_sum_function_type::dimension_parameter_index); 100 | 101 | if (numericType == variable_type::real) { 102 | return std::make_unique>(inputCount, dimension); 103 | } else if (numericType == variable_type::integer) { 104 | return std::make_unique>(inputCount, dimension); 105 | } else { 106 | throw std::domain_error("Parameter 'numericType' must be 'real' or 'integer'"); 107 | } 108 | } 109 | 110 | 111 | } // namespace cosim 112 | -------------------------------------------------------------------------------- /src/cosim/lib_info.cpp.in: -------------------------------------------------------------------------------- 1 | #include "cosim/lib_info.hpp" 2 | 3 | 4 | namespace cosim 5 | { 6 | 7 | 8 | version library_version() 9 | { 10 | // clang-format off 11 | return { @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@ }; 12 | // clang-format on 13 | } 14 | 15 | 16 | } // namespace cosim 17 | -------------------------------------------------------------------------------- /src/cosim/log/logger.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/log/logger.hpp" 7 | 8 | 9 | namespace cosim 10 | { 11 | namespace log 12 | { 13 | 14 | 15 | logger_type& logger() 16 | { 17 | static logger_type logger_{info}; 18 | return logger_; 19 | } 20 | 21 | 22 | } // namespace log 23 | } // namespace cosim 24 | -------------------------------------------------------------------------------- /src/cosim/model_description.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/model_description.hpp" 7 | 8 | #include 9 | 10 | 11 | namespace cosim 12 | { 13 | 14 | std::optional find_variable(const model_description& description, const std::string& variable_name) 15 | { 16 | for (const auto& variable : description.variables) { 17 | if (variable.name == variable_name) { 18 | return variable; 19 | } 20 | } 21 | return std::nullopt; 22 | } 23 | 24 | } // namespace cosim 25 | -------------------------------------------------------------------------------- /src/cosim/observer/last_value_observer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/observer/last_value_observer.hpp" 7 | 8 | #include "cosim/error.hpp" 9 | #include "cosim/observer/slave_value_provider.hpp" 10 | 11 | #include 12 | #include 13 | 14 | 15 | namespace cosim 16 | { 17 | 18 | last_value_observer::last_value_observer() 19 | { } 20 | 21 | void last_value_observer::simulator_added(simulator_index index, observable* simulator, time_point /*currentTime*/) 22 | { 23 | valueProviders_[index] = std::make_unique(simulator); 24 | } 25 | 26 | void last_value_observer::simulator_removed(simulator_index index, time_point /*currentTime*/) 27 | { 28 | valueProviders_.erase(index); 29 | } 30 | 31 | void last_value_observer::variables_connected(variable_id /*output*/, variable_id /*input*/, time_point /*currentTime*/) 32 | { 33 | } 34 | 35 | void last_value_observer::variable_disconnected(variable_id /*input*/, time_point /*currentTime*/) 36 | { 37 | } 38 | 39 | void last_value_observer::simulation_initialized( 40 | step_number /*firstStep*/, 41 | time_point /*startTime*/) 42 | { 43 | for (const auto& entry : valueProviders_) { 44 | entry.second->observe(); 45 | } 46 | } 47 | 48 | void last_value_observer::step_complete( 49 | step_number /*lastStep*/, 50 | duration /*lastStepSize*/, 51 | time_point /*currentTime*/) 52 | { 53 | } 54 | 55 | void last_value_observer::simulator_step_complete( 56 | simulator_index index, 57 | step_number /*lastStep*/, 58 | duration /*lastStepSize*/, 59 | time_point /*currentTime*/) 60 | { 61 | valueProviders_.at(index)->observe(); 62 | } 63 | 64 | void last_value_observer::state_restored( 65 | step_number /*currentStep*/, 66 | time_point /*currentTime*/) 67 | { 68 | for (const auto& entry : valueProviders_) { 69 | entry.second->observe(); 70 | } 71 | } 72 | 73 | void last_value_observer::get_real( 74 | simulator_index sim, 75 | gsl::span variables, 76 | gsl::span values) 77 | { 78 | COSIM_INPUT_CHECK(variables.size() == values.size()); 79 | valueProviders_.at(sim)->get_real(variables, values); 80 | } 81 | 82 | void last_value_observer::get_integer( 83 | simulator_index sim, 84 | gsl::span variables, 85 | gsl::span values) 86 | { 87 | COSIM_INPUT_CHECK(variables.size() == values.size()); 88 | valueProviders_.at(sim)->get_int(variables, values); 89 | } 90 | 91 | void last_value_observer::get_boolean( 92 | simulator_index sim, 93 | gsl::span variables, 94 | gsl::span values) 95 | { 96 | COSIM_INPUT_CHECK(variables.size() == values.size()); 97 | valueProviders_.at(sim)->get_boolean(variables, values); 98 | } 99 | 100 | void last_value_observer::get_string( 101 | simulator_index sim, 102 | gsl::span variables, 103 | gsl::span values) 104 | { 105 | COSIM_INPUT_CHECK(variables.size() == values.size()); 106 | valueProviders_.at(sim)->get_string(variables, values); 107 | } 108 | 109 | last_value_observer::~last_value_observer() noexcept = default; 110 | 111 | } // namespace cosim 112 | -------------------------------------------------------------------------------- /src/cosim/observer/slave_value_provider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/observer/slave_value_provider.hpp" 7 | 8 | #include "cosim/error.hpp" 9 | 10 | #include 11 | 12 | namespace cosim 13 | { 14 | 15 | namespace 16 | { 17 | 18 | template 19 | void get( 20 | gsl::span variables, 21 | const std::unordered_map& samples, 22 | gsl::span values) 23 | { 24 | if (samples.empty()) { 25 | throw std::out_of_range("no samples available"); 26 | } 27 | for (std::size_t i = 0; i < values.size(); i++) { 28 | value_reference valueRef = variables[i]; 29 | values[i] = samples.at(valueRef); 30 | } 31 | } 32 | 33 | } // namespace 34 | 35 | slave_value_provider::slave_value_provider(observable* observable) 36 | : observable_(observable) 37 | { 38 | for (const auto& vd : observable->model_description().variables) { 39 | observable->expose_for_getting(vd.type, vd.reference); 40 | switch (vd.type) { 41 | case cosim::variable_type::real: 42 | realSamples_[vd.reference] = double(); 43 | break; 44 | case cosim::variable_type::integer: 45 | intSamples_[vd.reference] = int(); 46 | break; 47 | case cosim::variable_type::boolean: 48 | boolSamples_[vd.reference] = bool(); 49 | break; 50 | case cosim::variable_type::string: 51 | stringSamples_[vd.reference] = std::string(); 52 | break; 53 | default: 54 | COSIM_PANIC(); 55 | } 56 | } 57 | } 58 | 59 | slave_value_provider::~slave_value_provider() noexcept = default; 60 | 61 | void slave_value_provider::observe() 62 | { 63 | std::lock_guard lock(lock_); 64 | 65 | for (auto& [idx, value] : realSamples_) { 66 | value = observable_->get_real(idx); 67 | } 68 | for (auto& [idx, value] : intSamples_) { 69 | value = observable_->get_integer(idx); 70 | } 71 | for (auto& [idx, value] : boolSamples_) { 72 | value = observable_->get_boolean(idx); 73 | } 74 | for (auto& [idx, value] : stringSamples_) { 75 | value = observable_->get_string(idx); 76 | } 77 | } 78 | 79 | void slave_value_provider::get_real(gsl::span variables, gsl::span values) 80 | { 81 | std::lock_guard lock(lock_); 82 | get(variables, realSamples_, values); 83 | } 84 | 85 | void slave_value_provider::get_int(gsl::span variables, gsl::span values) 86 | { 87 | std::lock_guard lock(lock_); 88 | get(variables, intSamples_, values); 89 | } 90 | 91 | void slave_value_provider::get_boolean(gsl::span variables, gsl::span values) 92 | { 93 | std::lock_guard lock(lock_); 94 | get(variables, boolSamples_, values); 95 | } 96 | 97 | void slave_value_provider::get_string(gsl::span variables, gsl::span values) 98 | { 99 | std::lock_guard lock(lock_); 100 | get(variables, stringSamples_, values); 101 | } 102 | 103 | } // namespace cosim 104 | -------------------------------------------------------------------------------- /src/cosim/observer/slave_value_provider.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef COSIM_OBSERVER_SLAVE_VALUE_PROVIDER_HPP 10 | #define COSIM_OBSERVER_SLAVE_VALUE_PROVIDER_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace cosim 24 | { 25 | 26 | class slave_value_provider 27 | { 28 | 29 | public: 30 | slave_value_provider(observable* obs); 31 | ~slave_value_provider() noexcept; 32 | void observe(); 33 | void get_real(gsl::span variables, gsl::span values); 34 | void get_int(gsl::span variables, gsl::span values); 35 | void get_boolean(gsl::span variables, gsl::span values); 36 | void get_string(gsl::span variables, gsl::span values); 37 | 38 | private: 39 | std::unordered_map realSamples_; 40 | std::unordered_map intSamples_; 41 | std::unordered_map boolSamples_; 42 | std::unordered_map stringSamples_; 43 | observable* observable_; 44 | std::mutex lock_; 45 | }; 46 | 47 | } // namespace cosim 48 | 49 | #endif // Header guard 50 | -------------------------------------------------------------------------------- /src/cosim/proxy/proxy_uri_sub_resolver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/log/logger.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | std::pair parse_authority(std::string_view auth) 13 | { 14 | const auto colonIdx = auth.find(':'); 15 | if (colonIdx == std::string::npos) { 16 | return {std::string(auth), -1}; 17 | } else { 18 | const auto host = std::string(auth.substr(0, colonIdx)); 19 | const auto port = std::stoi(std::string(auth.substr(colonIdx + 1))); 20 | return {host, port}; 21 | } 22 | } 23 | 24 | std::shared_ptr cosim::proxy::proxy_uri_sub_resolver::lookup_model( 25 | const cosim::uri& baseUri, 26 | const cosim::uri& modelUriReference) 27 | { 28 | const auto& mur = modelUriReference; 29 | const auto query = mur.query(); 30 | if (query) { 31 | if (query->find("file=file:///") < query->size()) { 32 | const auto newQuery = "file=" + std::string(query->substr(13)); 33 | return model_uri_sub_resolver::lookup_model( 34 | baseUri, uri(mur.scheme(), mur.authority(), mur.path(), newQuery, mur.fragment())); 35 | } else if (query->find("file=") < query->size()) { 36 | const auto pathToAppend = cosim::file_uri_to_path(baseUri).parent_path().string(); 37 | const auto newQuery = "file=" + pathToAppend + "/" + std::string(query->substr(5)); 38 | return model_uri_sub_resolver::lookup_model( 39 | baseUri, uri(mur.scheme(), mur.authority(), mur.path(), newQuery, mur.fragment())); 40 | } 41 | } 42 | return model_uri_sub_resolver::lookup_model(baseUri, mur); 43 | } 44 | 45 | std::shared_ptr cosim::proxy::proxy_uri_sub_resolver::lookup_model(const cosim::uri& modelUri) 46 | { 47 | assert(modelUri.scheme().has_value()); 48 | if (*modelUri.scheme() != "proxyfmu") return nullptr; 49 | COSIM_INPUT_CHECK(modelUri.authority()); 50 | COSIM_INPUT_CHECK(modelUri.query()); 51 | 52 | const auto auth = parse_authority(*modelUri.authority()); 53 | const auto query = *modelUri.query(); 54 | if (query.substr(0, 5) == "file=") { 55 | const auto file = cosim::filesystem::path(std::string(query.substr(5))); 56 | if (!cosim::filesystem::exists(file)) { 57 | throw error(make_error_code(errc::bad_file), "No such file: " + file.string()); 58 | } 59 | if (auth.first == "localhost" && auth.second == -1) { 60 | return std::make_shared(file); 61 | } else { 62 | return std::make_shared(file, proxyfmu::remote_info(auth.first, auth.second)); 63 | } 64 | 65 | } else { 66 | return nullptr; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/cosim/proxy/proxy_uri_sub_resolver.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef COSIM_PROXY_FILE_URI_SUB_RESOLVER_HPP 10 | #define COSIM_PROXY_FILE_URI_SUB_RESOLVER_HPP 11 | 12 | #include 13 | 14 | namespace cosim 15 | { 16 | 17 | namespace proxy 18 | { 19 | 20 | /** 21 | * Class for resolving proxy-fmu URI schemes. 22 | * 23 | * From file: 'proxyfmu:///localhost?file=models/my_model.fmu' 24 | */ 25 | class proxy_uri_sub_resolver : public model_uri_sub_resolver 26 | { 27 | 28 | public: 29 | std::shared_ptr lookup_model(const uri& baseUri, const uri& modelUriReference) override; 30 | std::shared_ptr lookup_model(const cosim::uri& uri) override; 31 | }; 32 | 33 | } // namespace proxy 34 | 35 | } // namespace cosim 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/cosim/proxy/remote_fmu.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef COSIM_PROXY_REMOTE_FMU_HPP 10 | #define COSIM_PROXY_REMOTE_FMU_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | namespace cosim 23 | { 24 | 25 | namespace proxy 26 | { 27 | 28 | class remote_fmu : public cosim::model 29 | { 30 | 31 | public: 32 | explicit remote_fmu(const cosim::filesystem::path& fmuPath, const std::optional& remote = std::nullopt); 33 | 34 | std::shared_ptr description() const noexcept override; 35 | 36 | std::shared_ptr instantiate(std::string_view name) override; 37 | 38 | private: 39 | std::unique_ptr fmu_; 40 | std::shared_ptr modelDescription_; 41 | }; 42 | 43 | } // namespace proxy 44 | 45 | } // namespace cosim 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/cosim/proxy/remote_slave.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef COSIM_PROXY_REMOTE_SLAVE_HPP 10 | #define COSIM_PROXY_REMOTE_SLAVE_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include 19 | 20 | namespace cosim 21 | { 22 | 23 | namespace proxy 24 | { 25 | 26 | class remote_slave : public slave 27 | { 28 | 29 | public: 30 | remote_slave(std::unique_ptr slave, 31 | std::shared_ptr modelDescription); 32 | 33 | cosim::model_description model_description() const override; 34 | 35 | void setup(time_point startTime, std::optional stopTime, 36 | std::optional relativeTolerance) override; 37 | 38 | void start_simulation() override; 39 | 40 | void end_simulation() override; 41 | 42 | cosim::step_result do_step(time_point currentT, duration deltaT) override; 43 | 44 | void get_real_variables(gsl::span variables, gsl::span values) const override; 45 | 46 | void get_integer_variables(gsl::span variables, gsl::span values) const override; 47 | 48 | void get_boolean_variables(gsl::span variables, gsl::span values) const override; 49 | 50 | void get_string_variables(gsl::span variables, 51 | gsl::span values) const override; 52 | 53 | void set_real_variables(gsl::span variables, gsl::span values) override; 54 | 55 | void set_integer_variables(gsl::span variables, gsl::span values) override; 56 | 57 | void set_boolean_variables(gsl::span variables, gsl::span values) override; 58 | 59 | void set_string_variables(gsl::span variables, 60 | gsl::span values) override; 61 | 62 | state_index save_state() override; 63 | 64 | void save_state(state_index stateIndex) override; 65 | 66 | void restore_state(state_index stateIndex) override; 67 | 68 | void release_state(state_index stateIndex) override; 69 | 70 | serialization::node export_state(state_index stateIndex) const override; 71 | 72 | state_index import_state(const serialization::node& exportedState) override; 73 | 74 | ~remote_slave() override; 75 | 76 | private: 77 | bool terminated_; 78 | cosim::time_point startTime_; 79 | std::unique_ptr slave_; 80 | std::shared_ptr modelDescription_; 81 | }; 82 | 83 | } // namespace proxy 84 | 85 | } // namespace cosim 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /src/cosim/slave_simulator.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Defines `cosim::slave_simulator` 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_SLAVE_SIMULATOR_HPP 11 | #define COSIM_SLAVE_SIMULATOR_HPP 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | 20 | namespace cosim 21 | { 22 | 23 | class slave_simulator : public simulator 24 | { 25 | public: 26 | slave_simulator(std::shared_ptr slave, std::string_view name); 27 | 28 | ~slave_simulator() noexcept; 29 | 30 | slave_simulator(const slave_simulator&) = delete; 31 | slave_simulator& operator=(const slave_simulator&) = delete; 32 | 33 | slave_simulator(slave_simulator&&) noexcept; 34 | slave_simulator& operator=(slave_simulator&&) noexcept; 35 | 36 | // `observable` methods 37 | std::string name() const override; 38 | cosim::model_description model_description() const override; 39 | 40 | void expose_for_getting(variable_type type, value_reference ref) override; 41 | double get_real(value_reference reference) const override; 42 | int get_integer(value_reference reference) const override; 43 | bool get_boolean(value_reference reference) const override; 44 | std::string_view get_string(value_reference reference) const override; 45 | 46 | // `simulator` methods 47 | void expose_for_setting(variable_type type, value_reference ref) override; 48 | void set_real(value_reference reference, double value) override; 49 | void set_integer(value_reference reference, int value) override; 50 | void set_boolean(value_reference reference, bool value) override; 51 | void set_string(value_reference reference, std::string_view value) override; 52 | 53 | void set_real_input_modifier( 54 | value_reference reference, 55 | std::function modifier) override; 56 | void set_integer_input_modifier( 57 | value_reference reference, 58 | std::function modifier) override; 59 | void set_boolean_input_modifier( 60 | value_reference reference, 61 | std::function modifier) override; 62 | void set_string_input_modifier( 63 | value_reference reference, 64 | std::function modifier) override; 65 | void set_real_output_modifier( 66 | value_reference reference, 67 | std::function modifier) override; 68 | void set_integer_output_modifier( 69 | value_reference reference, 70 | std::function modifier) override; 71 | void set_boolean_output_modifier( 72 | value_reference reference, 73 | std::function modifier) override; 74 | void set_string_output_modifier( 75 | value_reference reference, 76 | std::function modifier) override; 77 | 78 | std::unordered_set& get_modified_real_variables() const override; 79 | std::unordered_set& get_modified_integer_variables() const override; 80 | std::unordered_set& get_modified_boolean_variables() const override; 81 | std::unordered_set& get_modified_string_variables() const override; 82 | 83 | void setup( 84 | time_point startTime, 85 | std::optional stopTime, 86 | std::optional relativeTolerance) override; 87 | 88 | void do_iteration() override; 89 | 90 | void start_simulation() override; 91 | 92 | step_result do_step( 93 | time_point currentT, 94 | duration deltaT) override; 95 | 96 | state_index save_state() override; 97 | void save_state(state_index stateIndex) override; 98 | void restore_state(state_index stateIndex) override; 99 | void release_state(state_index stateIndex) override; 100 | serialization::node export_state(state_index stateIndex) const override; 101 | state_index import_state(const serialization::node& exportedState) override; 102 | 103 | private: 104 | class impl; 105 | std::unique_ptr pimpl_; 106 | }; 107 | 108 | 109 | } // namespace cosim 110 | #endif // COSIM_SLAVE_SIMULATOR_HPP 111 | -------------------------------------------------------------------------------- /src/cosim/ssp/ssp_parser.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef LIBCOSIM_SSP_PARSER_HPP 10 | #define LIBCOSIM_SSP_PARSER_HPP 11 | 12 | #include "cosim/algorithm.hpp" 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace cosim 21 | { 22 | 23 | class ssp_parser 24 | { 25 | 26 | public: 27 | explicit ssp_parser(const cosim::filesystem::path& xmlPath); 28 | ~ssp_parser() noexcept; 29 | 30 | struct DefaultExperiment 31 | { 32 | double startTime = 0.0; 33 | std::optional stopTime; 34 | std::shared_ptr algorithm; 35 | }; 36 | 37 | const DefaultExperiment& get_default_experiment() const; 38 | 39 | struct System 40 | { 41 | std::string name; 42 | std::optional description; 43 | }; 44 | 45 | struct SystemDescription 46 | { 47 | std::string name; 48 | std::string version; 49 | System system; 50 | }; 51 | 52 | struct Connector 53 | { 54 | std::string name; 55 | std::string kind; 56 | cosim::variable_type type; 57 | }; 58 | 59 | struct Parameter 60 | { 61 | std::string name; 62 | cosim::variable_type type; 63 | scalar_value value; 64 | }; 65 | 66 | struct ParameterSet 67 | { 68 | std::string name; 69 | std::vector parameters; 70 | }; 71 | 72 | struct Component 73 | { 74 | std::string name; 75 | std::string source; 76 | std::optional stepSizeHint; 77 | std::vector parameterSets; 78 | std::unordered_map connectors; 79 | }; 80 | 81 | [[nodiscard]] const std::unordered_map& get_elements() const; 82 | 83 | struct LinearTransformation 84 | { 85 | double offset; 86 | double factor; 87 | }; 88 | 89 | struct Connection 90 | { 91 | Component startElement; 92 | Connector startConnector; 93 | Component endElement; 94 | Connector endConnector; 95 | std::optional linearTransformation; 96 | }; 97 | 98 | [[nodiscard]] const std::vector& get_connections() const; 99 | 100 | private: 101 | SystemDescription systemDescription_; 102 | DefaultExperiment defaultExperiment_; 103 | 104 | std::vector connections_; 105 | std::unordered_map elements_; 106 | }; 107 | 108 | struct slave_info 109 | { 110 | cosim::simulator_index index; 111 | std::map variables; 112 | }; 113 | 114 | cosim::time_point get_default_start_time(const ssp_parser& parser); 115 | 116 | cosim::variable_id get_variable( 117 | const std::map& slaves, 119 | const std::string& element, 120 | const std::string& connector); 121 | 122 | 123 | std::optional get_parameter_set( 124 | const ssp_parser::Component& component, 125 | std::optional parameterSetName); 126 | 127 | } // namespace cosim 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /src/cosim/utility/filesystem.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/utility/filesystem.hpp" 7 | 8 | #include "cosim/utility/uuid.hpp" 9 | 10 | #include 11 | #include 12 | 13 | 14 | namespace cosim 15 | { 16 | namespace utility 17 | { 18 | 19 | 20 | temp_dir::temp_dir(const cosim::filesystem::path& parent) 21 | { 22 | if (parent.empty()) { 23 | path_ = cosim::filesystem::temp_directory_path() / ("libcosim_" + random_uuid()); 24 | } else if (parent.is_absolute()) { 25 | path_ = parent / random_uuid(); 26 | } else { 27 | path_ = cosim::filesystem::temp_directory_path() / parent / random_uuid(); 28 | } 29 | cosim::filesystem::create_directories(path_); 30 | } 31 | 32 | temp_dir::temp_dir(temp_dir&& other) noexcept 33 | : path_(std::move(other.path_)) 34 | { 35 | other.path_.clear(); 36 | } 37 | 38 | temp_dir& temp_dir::operator=(temp_dir&& other) noexcept 39 | { 40 | delete_noexcept(); 41 | path_ = std::move(other.path_); 42 | other.path_.clear(); 43 | return *this; 44 | } 45 | 46 | temp_dir::~temp_dir() 47 | { 48 | delete_noexcept(); 49 | } 50 | 51 | const cosim::filesystem::path& temp_dir::path() const 52 | { 53 | return path_; 54 | } 55 | 56 | void temp_dir::delete_noexcept() noexcept 57 | { 58 | if (!path_.empty()) { 59 | std::error_code errorCode; 60 | cosim::filesystem::remove_all(path_, errorCode); 61 | path_.clear(); 62 | } 63 | } 64 | 65 | 66 | } // namespace utility 67 | } // namespace cosim 68 | -------------------------------------------------------------------------------- /src/cosim/utility/filesystem.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * File system utilities. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_UTILITY_FILESYSTEM_HPP 11 | #define COSIM_UTILITY_FILESYSTEM_HPP 12 | 13 | #include 14 | 15 | 16 | namespace cosim 17 | { 18 | namespace utility 19 | { 20 | 21 | 22 | /** 23 | * An RAII object that creates a unique directory on construction and 24 | * recursively deletes it again on destruction. 25 | */ 26 | class temp_dir 27 | { 28 | public: 29 | /** 30 | * Creates a new temporary directory. 31 | * 32 | * The name of the new directory will be randomly generated, and there are 33 | * three options of where it will be created, depending on the value of 34 | * `parent`. In the following, `temp` refers to a directory suitable for 35 | * temporary files under the conventions of the operating system (e.g. 36 | * `/tmp` under UNIX-like systems), and `name` refers to the randomly 37 | * generated name mentioned above. 38 | * 39 | * - If `parent` is empty: `temp/name` 40 | * - If `parent` is relative: `temp/parent/name` 41 | * - If `parent` is absolute: `parent/name` 42 | */ 43 | explicit temp_dir( 44 | const cosim::filesystem::path& parent = cosim::filesystem::path()); 45 | 46 | temp_dir(const temp_dir&) = delete; 47 | temp_dir& operator=(const temp_dir&) = delete; 48 | 49 | /** 50 | * Move constructor. 51 | * 52 | * Ownership of the directory is transferred from `other` to `this`. 53 | * Afterwards, `other` no longer refers to any directory, meaning that 54 | * `other.Path()` will return an empty path, and its destructor will not 55 | * perform any filesystem operations. 56 | */ 57 | temp_dir(temp_dir&& other) noexcept; 58 | 59 | /// Move assignment operator. See temp_dir(temp_dir&&) for semantics. 60 | temp_dir& operator=(temp_dir&&) noexcept; 61 | 62 | /// Destructor. Recursively deletes the directory. 63 | ~temp_dir() noexcept; 64 | 65 | /// Returns the path to the directory. 66 | const cosim::filesystem::path& path() const; 67 | 68 | private: 69 | void delete_noexcept() noexcept; 70 | 71 | cosim::filesystem::path path_; 72 | }; 73 | 74 | 75 | } // namespace utility 76 | } // namespace cosim 77 | #endif // header guard 78 | -------------------------------------------------------------------------------- /src/cosim/utility/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Slave interface. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_UTILITY_THREAD_POOL_HPP 11 | #define COSIM_UTILITY_THREAD_POOL_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace cosim 21 | { 22 | namespace utility 23 | { 24 | 25 | class thread_pool 26 | { 27 | private: 28 | bool done_; 29 | std::queue> work_queue_; 30 | std::vector threads_; 31 | std::mutex m_; 32 | std::condition_variable cv_finished_; 33 | std::condition_variable cv_worker_; 34 | unsigned int pending_tasks_; 35 | 36 | void worker_thread() 37 | { 38 | while (true) { 39 | std::unique_lock lck(m_); 40 | 41 | // If no work is available, block the thread here 42 | cv_worker_.wait(lck, [this]() { return done_ || !work_queue_.empty(); }); 43 | if (!work_queue_.empty()) { 44 | pending_tasks_++; 45 | 46 | auto task = std::move(work_queue_.front()); 47 | work_queue_.pop(); 48 | 49 | lck.unlock(); 50 | 51 | // Run work function outside mutex lock context 52 | task(); 53 | 54 | lck.lock(); 55 | pending_tasks_--; 56 | lck.unlock(); 57 | cv_finished_.notify_one(); 58 | } else if (done_) 59 | break; 60 | } 61 | } 62 | 63 | public: 64 | explicit thread_pool(unsigned int thread_count) 65 | : done_(false) 66 | , pending_tasks_(0) 67 | { 68 | try { 69 | for (unsigned i = 0; i < thread_count; ++i) { 70 | threads_.emplace_back(&thread_pool::worker_thread, this); 71 | } 72 | } catch (...) { 73 | done_ = true; 74 | throw; 75 | } 76 | } 77 | 78 | thread_pool(const thread_pool&) = delete; 79 | thread_pool(const thread_pool&&) = delete; 80 | 81 | [[nodiscard]] size_t numWorkerThreads() const 82 | { 83 | return threads_.size(); 84 | } 85 | 86 | void wait_for_tasks_to_finish() 87 | { 88 | std::unique_lock lck(m_); 89 | cv_finished_.wait(lck, [this]() { return work_queue_.empty() && (pending_tasks_ == 0); }); 90 | } 91 | 92 | void submit(std::function f) 93 | { 94 | if (threads_.empty()) { 95 | f(); 96 | } else { 97 | std::unique_lock lck(m_); 98 | work_queue_.emplace(std::move(f)); 99 | lck.unlock(); 100 | cv_worker_.notify_one(); 101 | } 102 | } 103 | 104 | ~thread_pool() noexcept 105 | { 106 | std::unique_lock lck(m_); 107 | done_ = true; 108 | lck.unlock(); 109 | cv_worker_.notify_all(); 110 | 111 | for (auto& thread : threads_) { 112 | thread.join(); 113 | } 114 | } 115 | }; 116 | 117 | } // namespace utility 118 | } // namespace cosim 119 | 120 | #endif // COSIM_UTILITY_THREAD_POOL_HPP 121 | -------------------------------------------------------------------------------- /src/cosim/utility/utility.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \copyright 5 | * This Source Code Form is subject to the terms of the Mozilla Public 6 | * License, v. 2.0. If a copy of the MPL was not distributed with this 7 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | */ 9 | #ifndef COSIM_UTILITY_UTILITY_HPP 10 | #define COSIM_UTILITY_UTILITY_HPP 11 | 12 | #include 13 | 14 | namespace cosim 15 | { 16 | 17 | 18 | /** 19 | * A generic visitor that can be constructed on the fly from a set of lambdas. 20 | * 21 | * Inspired by: 22 | * https://arne-mertz.de/2018/05/overload-build-a-variant-visitor-on-the-fly/ 23 | */ 24 | template 25 | struct visitor : Functors... 26 | { 27 | visitor(const Functors&... functors) 28 | : Functors(functors)... 29 | { 30 | } 31 | 32 | using Functors::operator()...; 33 | }; 34 | 35 | /** 36 | * Utility for printing value of variant types like `scalar_value` 37 | * 38 | * See https://stackoverflow.com/questions/47168477/how-to-stream-stdvariant 39 | */ 40 | template 41 | struct streamer 42 | { 43 | const T& val; 44 | }; 45 | 46 | template 47 | streamer(T) -> streamer; 48 | 49 | template 50 | std::ostream& operator<<(std::ostream& os, streamer s) 51 | { 52 | os << s.val; 53 | return os; 54 | } 55 | 56 | template 57 | std::ostream& operator<<(std::ostream& os, streamer> sv) 58 | { 59 | std::visit([&os](const auto& v) { os << streamer{v}; }, sv.val); 60 | return os; 61 | } 62 | 63 | } // namespace cosim 64 | #endif // header guard 65 | -------------------------------------------------------------------------------- /src/cosim/utility/uuid.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | #include "cosim/utility/uuid.hpp" 7 | 8 | #include 9 | #include 10 | 11 | 12 | namespace cosim 13 | { 14 | namespace utility 15 | { 16 | 17 | 18 | std::string random_uuid() noexcept 19 | { 20 | boost::uuids::random_generator gen; 21 | return boost::uuids::to_string(gen()); 22 | } 23 | 24 | 25 | } // namespace utility 26 | } // namespace cosim 27 | -------------------------------------------------------------------------------- /src/cosim/utility/uuid.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Utility functions for dealing with UUIDs. 4 | * 5 | * \copyright 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef COSIM_UTILITY_UUID_HPP 11 | #define COSIM_UTILITY_UUID_HPP 12 | 13 | #include 14 | 15 | 16 | namespace cosim 17 | { 18 | namespace utility 19 | { 20 | 21 | 22 | /// Returns a randomly generated UUID. 23 | std::string random_uuid() noexcept; 24 | 25 | 26 | } // namespace utility 27 | } // namespace cosim 28 | #endif // header guard 29 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(tests 2 | "ecco_algorithm_from_system_structure_test" 3 | "ecco_algorithm_multi_bond_test" 4 | "ecco_algorithm_test" 5 | "file_observer_dynamic_logging_test" 6 | "file_observer_logging_test" 7 | "file_observer_logging_from_config_test" 8 | "fixed_step_algorithm_test" 9 | "last_value_observer_test" 10 | "monitor_modified_variables_test" 11 | "multi_fixed_step_algorithm_test" 12 | "osp_config_parser_test" 13 | "ramp_modifier_test" 14 | "save_state_test" 15 | "time_series_observer_test" 16 | "trend_buffer_test" 17 | "scenario_manager_test" 18 | "synchronized_xy_series_test" 19 | "config_end_time_test" 20 | "state_init_test" 21 | ) 22 | 23 | set(unittests 24 | "function_unittest" 25 | "fmi_v1_fmu_unittest" 26 | "fmi_v2_fmu_unittest" 27 | "orchestration_unittest" 28 | "scenario_parser_unittest" 29 | "slave_simulator_unittest" 30 | "ssp_loader_unittest" 31 | "system_structure_unittest" 32 | "time_unittest" 33 | "uri_unittest" 34 | "utility_concurrency_unittest" 35 | "utility_filesystem_unittest" 36 | "utility_uuid_unittest" 37 | "utility_zip_unittest" 38 | ) 39 | 40 | if(LIBCOSIM_WITH_PROXYFMU) 41 | list(APPEND unittests 42 | "proxyfmu_integration_unittest" 43 | "proxyfmu_library_unittest" 44 | ) 45 | list(APPEND tests 46 | "proxyfmu_osp_config_parser_test" 47 | "proxyfmu_save_state_test" 48 | ) 49 | endif() 50 | 51 | include("AddTestExecutable") 52 | foreach(test IN LISTS tests) 53 | add_test_executable( 54 | "cpp_${test}" 55 | FOLDER "C++ tests" 56 | SOURCES "${test}.cpp" 57 | DEPENDENCIES "cosim" 58 | DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data" 59 | ) 60 | endforeach() 61 | 62 | foreach(test IN LISTS unittests) 63 | 64 | set(dependencies cosim "Boost::unit_test_framework" "Boost::timer") 65 | if (LIBCOSIM_WITH_PROXYFMU) 66 | list(APPEND dependencies "proxyfmu::proxyfmu-client") 67 | endif() 68 | 69 | add_test_executable( 70 | "cpp_${test}" 71 | FOLDER "C++ unit tests" 72 | SOURCES "${test}.cpp" 73 | DEPENDENCIES ${dependencies} 74 | DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data" 75 | ) 76 | target_include_directories("cpp_${test}" PRIVATE "$") 77 | endforeach() 78 | -------------------------------------------------------------------------------- /tests/config_end_time_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define REQUIRE(test) \ 13 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 14 | 15 | 16 | void test(const cosim::filesystem::path& configPath) 17 | { 18 | auto resolver = cosim::default_model_uri_resolver(); 19 | const auto config = cosim::load_osp_config(configPath, *resolver); 20 | auto algorithm_cfg = std::get(config.algorithm_configuration); 21 | 22 | auto execution = cosim::execution( 23 | config.start_time, 24 | std::make_shared(algorithm_cfg)); 25 | 26 | const auto entityMaps = cosim::inject_system_structure( 27 | execution, config.system_structure, config.initial_values); 28 | 29 | REQUIRE(entityMaps.simulators.size() == 4); 30 | 31 | auto obs = std::make_shared(); 32 | execution.add_observer(obs); 33 | 34 | if (config.end_time.has_value()) { 35 | REQUIRE(config.end_time.value().time_since_epoch().count() / 1e9 == 0.001); 36 | auto result = execution.simulate_until(config.end_time); 37 | REQUIRE(result); 38 | } else { 39 | auto result = execution.simulate_until(cosim::to_time_point(0.001)); 40 | REQUIRE(result); 41 | } 42 | } 43 | 44 | int main() 45 | { 46 | try { 47 | cosim::log::setup_simple_console_logging(); 48 | cosim::log::set_global_output_level(cosim::log::info); 49 | 50 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 51 | REQUIRE(testDataDir); 52 | 53 | test(cosim::filesystem::path(testDataDir) / "msmi" / "OspSystemStructure.xml"); 54 | test(cosim::filesystem::path(testDataDir) / "msmi" / "OspSystemStructure_EndTime.xml"); 55 | } catch (const std::exception& e) { 56 | std::cerr << "Error: " << e.what(); 57 | return 1; 58 | } 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /tests/data/LogConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/data/LogConfig.xsd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/fmi1/README.md: -------------------------------------------------------------------------------- 1 | # FMI 1.0 test FMUs 2 | 3 | | Name | Origin | License | 4 | | -------------- | ------------------- | ------------------------------------------- | 5 | | `identity.fmu` | [ViProMa demo-fmus] | [BSD 3-clause](./viproma_demo-fmus_LICENSE) | 6 | 7 | 8 | [ViProMa demo-fmus]: https://github.com/viproma/demo-fmus 9 | -------------------------------------------------------------------------------- /tests/data/fmi1/identity.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/fmi1/identity.fmu -------------------------------------------------------------------------------- /tests/data/fmi1/viproma_demo-fmus_LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015-2019, SINTEF Ocean. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /tests/data/fmi2/CraneController.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/fmi2/CraneController.fmu -------------------------------------------------------------------------------- /tests/data/fmi2/Dahlquist.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/fmi2/Dahlquist.fmu -------------------------------------------------------------------------------- /tests/data/fmi2/KnuckleBoomCrane.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/fmi2/KnuckleBoomCrane.fmu -------------------------------------------------------------------------------- /tests/data/fmi2/KnuckleBoomCrane_OspModelDescription.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/data/fmi2/README.md: -------------------------------------------------------------------------------- 1 | # FMI 2.0 test FMUs 2 | 3 | | Name | Origin | License | 4 | |------------------------|-------------------------|------------------------------------------| 5 | | `CraneController.fmu` | OSP | [MPL 2.0](../../../LICENSE) | 6 | | `Dahlquist.fmu` | [Reference FMUs] 0.0.31 | [2-clause BSD](./reference-fmus_LICENSE) | 7 | | `KnuckleBoomCrane.fmu` | OSP | [MPL 2.0](../../../LICENSE) | 8 | | `vector.fmu` | [OSP cpp-fmus] | [MIT](./osp_cpp-fmus_LICENSE) | 9 | 10 | 11 | [OSP cpp-fmus]: https://github.com/open-simulation-platform/cpp-fmus 12 | [Reference FMUs]: https://github.com/modelica/Reference-FMUs 13 | -------------------------------------------------------------------------------- /tests/data/fmi2/StateInitExample.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/fmi2/StateInitExample.fmu -------------------------------------------------------------------------------- /tests/data/fmi2/osp_cpp-fmus_LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 DNV GL AS, Kongsberg Maritime CM AS, SINTEF Ocean AS, and NTNU 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/data/fmi2/quarter_truck/Chassis.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/fmi2/quarter_truck/Chassis.fmu -------------------------------------------------------------------------------- /tests/data/fmi2/quarter_truck/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 SINTEF Nordvest 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /tests/data/fmi2/quarter_truck/LogConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/data/fmi2/quarter_truck/OspSystemStructure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.0001 4 | ecco 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 0.99 45 | 0.0001 46 | 0.00001 47 | 0.01 48 | 0.2 49 | 1.5 50 | 0.2 51 | 0.15 52 | 1e-6 53 | 1e-6 54 | 55 | 56 | -------------------------------------------------------------------------------- /tests/data/fmi2/quarter_truck/README.md: -------------------------------------------------------------------------------- 1 | # FMI 2.0 test FMUs 2 | 3 | | Name | Origin | License | 4 | | ---------------------- | -------------- | ----------------------------- | 5 | | `Chassis.fmu` | OSP | [MIT](LICENSE) | 6 | | `Wheel.fmu` | OSP | [MIT](LICENSE) | 7 | 8 | [OSP demo-cases]: https://github.com/open-simulation-platform/demo-cases 9 | -------------------------------------------------------------------------------- /tests/data/fmi2/quarter_truck/Wheel.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/fmi2/quarter_truck/Wheel.fmu -------------------------------------------------------------------------------- /tests/data/fmi2/quarter_truck/chassis_OspModelDescription.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/data/fmi2/quarter_truck/wheel_OspModelDescription.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/data/fmi2/reference-fmus_LICENCE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/fmi2/reference-fmus_LICENCE -------------------------------------------------------------------------------- /tests/data/fmi2/vector.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/fmi2/vector.fmu -------------------------------------------------------------------------------- /tests/data/msmi/CraneController_OspModelDescription.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/data/msmi/OspSystemStructure.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 0.01 8 | 1e-4 9 | fixedStep 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /tests/data/msmi/OspSystemStructure_Bond.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 0.0 6 | 1e-4 7 | fixedStep 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests/data/msmi/OspSystemStructure_Dahlquist.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 0.0 6 | 0.01 7 | fixedStep 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/data/msmi/OspSystemStructure_Dahlquist_proxyfmu.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 0.0 6 | 0.01 7 | fixedStep 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/data/msmi/OspSystemStructure_EndTime.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 0.0 8 | 0.001 9 | 1e-4 10 | fixedStep 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /tests/data/msmi/OspSystemStructure_StateInitExample.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.0 4 | 0.01 5 | fixedStep 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/data/msmi/OspSystemStructure_proxyfmu.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 0.0 6 | 1e-4 7 | fixedStep 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests/data/msmi/schema/OspModelDescription.xsd: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /tests/data/scenarios/scenario1.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaults": { 3 | "model": "slave uno", 4 | "variable": "realOut", 5 | "action": "override" 6 | }, 7 | "events": [ 8 | { 9 | "time": 0.5, 10 | "variable": "realIn", 11 | "action": "bias", 12 | "value": 1.001 13 | }, 14 | { 15 | "time": 0.2, 16 | "value": -1.0 17 | }, 18 | { 19 | "time": 0.3, 20 | "action": "reset" 21 | }, 22 | { 23 | "time": 0.65, 24 | "variable": "intIn", 25 | "value": 2 26 | }, 27 | { 28 | "time": 0.8, 29 | "variable": "intOut", 30 | "value": 5 31 | } 32 | ], 33 | "end": 1.0 34 | } 35 | -------------------------------------------------------------------------------- /tests/data/scenarios/scenario1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | defaults: 3 | model: slave uno 4 | variable: realOut 5 | action: override 6 | events: 7 | - time: 0.5 8 | variable: realIn 9 | action: bias 10 | value: 1.001 11 | - time: 0.2 12 | value: -1 13 | - time: 0.3 14 | action: reset 15 | - time: 0.65 16 | variable: intIn 17 | value: 2 18 | - time: 0.8 19 | variable: intOut 20 | value: 5 21 | end: 1 22 | -------------------------------------------------------------------------------- /tests/data/ssp/OSPAnnotations.xsd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/data/ssp/demo/demo.ssp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/ssp/demo/demo.ssp -------------------------------------------------------------------------------- /tests/data/ssp/linear_transformation/SystemStructure.ssd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/data/ssp/linear_transformation/initial_values.ssv: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/data/ssp/linear_transformation/initial_values2.ssv: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/data/ziptest.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-simulation-platform/libcosim/5044092ff2a5f9cc1240cc7f479ba90d44815d13/tests/data/ziptest.zip -------------------------------------------------------------------------------- /tests/ecco_algorithm_from_system_structure_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /** 17 | * This test showcases how the ECCO algorithm may be configured via the OSPSystemStructure XMLs. 18 | * 19 | * The OspSystemStructure in the quarter_truck directory can be used as an example configuration. The key points are: 20 | * 21 | * * in OspSystemStructure now accepts either "ecco" or "fixedStep". 22 | * * The configuration for the Ecco algorithm can be added to the root of OspSystemStructure as seen in the quarter_truck example. 23 | * * To describe a powerbond, add the attribute powerbond="mypowerbond" to either a VariableConnection or VariableGroupConnection element. 24 | * This defines a name for the powerbond that the parsing uses to group correctly. 25 | * * To define the individual variables, the attribute port has been added to the Variable element. Here the user must specify whether the 26 | * variable is the input or output port of it's side of the bond. So, if we are coupling for instance a force <-> velocity bond, this 27 | * results in a tuple with one input and one output port for each VariableConnection that is used in the bond. 28 | * * This information is then parsed by osp_config_parser, which results in a power_bond_map available through the system_structure object. 29 | * * Finally, the power_bond_map is iterated and power bonds added to the algorithm by execution::inject_system_structure. 30 | * 31 | **/ 32 | 33 | #define REQUIRE(test) \ 34 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 35 | 36 | int main() 37 | { 38 | try { 39 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 40 | REQUIRE(testDataDir); 41 | cosim::log::setup_simple_console_logging(); 42 | cosim::log::set_global_output_level(cosim::log::debug); 43 | 44 | constexpr cosim::time_point endTime = cosim::to_time_point(0.1); 45 | 46 | auto resolver = cosim::default_model_uri_resolver(); 47 | auto configPath = cosim::filesystem::path(testDataDir) / "fmi2" / "quarter_truck"; 48 | const auto logXmlPath = cosim::filesystem::path(testDataDir) / "fmi2" / "quarter_truck" / "LogConfig.xml"; 49 | const auto config = cosim::load_osp_config(configPath, *resolver); 50 | 51 | auto ecco_params = std::get(config.algorithm_configuration); 52 | auto ecco_algo = std::make_shared(ecco_params); 53 | 54 | auto execution = cosim::execution(config.start_time, ecco_algo); 55 | 56 | const auto entityMaps = cosim::inject_system_structure(execution, config.system_structure, config.initial_values); 57 | const auto realTimeConfig = execution.get_real_time_config(); 58 | 59 | REQUIRE(entityMaps.simulators.size() == 2); 60 | REQUIRE(!realTimeConfig->real_time_simulation); 61 | 62 | const auto logPath = cosim::filesystem::current_path() / "logs"; 63 | std::cout << "Log path:" << logPath.c_str(); 64 | auto file_obs = std::make_unique(logPath, logXmlPath); 65 | execution.add_observer(std::move(file_obs)); 66 | 67 | auto simResult = execution.simulate_until(endTime); 68 | REQUIRE(simResult); 69 | 70 | 71 | } catch (const std::exception& e) { 72 | std::cerr << "Error: " << e.what() << std::endl; 73 | return 1; 74 | } 75 | 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /tests/file_observer_dynamic_logging_test.cpp: -------------------------------------------------------------------------------- 1 | #include "mock_slave.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | // A helper macro to test various assertions 17 | #define REQUIRE(test) \ 18 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 19 | 20 | int64_t filecount(const cosim::filesystem::path& path) 21 | { 22 | return std::count_if( 23 | cosim::filesystem::directory_iterator(path), 24 | cosim::filesystem::directory_iterator(), 25 | static_cast(cosim::filesystem::is_regular_file)); 26 | } 27 | 28 | void remove_directory_contents(const cosim::filesystem::path& path) 29 | { 30 | for (cosim::filesystem::directory_iterator end_dir_it, it(path); it != end_dir_it; ++it) { 31 | cosim::filesystem::remove_all(it->path()); 32 | } 33 | } 34 | 35 | int main() 36 | { 37 | try { 38 | cosim::log::setup_simple_console_logging(); 39 | cosim::log::set_global_output_level(cosim::log::debug); 40 | 41 | constexpr cosim::time_point startTime = cosim::to_time_point(0.0); 42 | constexpr cosim::duration stepSize = cosim::to_duration(0.1); 43 | 44 | const auto logPath = cosim::filesystem::current_path() / "logs" / "clean"; 45 | cosim::filesystem::create_directories(logPath); 46 | cosim::filesystem::remove_all(logPath); 47 | 48 | // Set up the execution 49 | auto execution = cosim::execution(startTime, std::make_unique(stepSize)); 50 | 51 | // Set up and add file observer to the execution 52 | auto observer = std::make_shared(logPath); 53 | execution.add_observer(observer); 54 | 55 | // Add slaves to the execution 56 | execution.add_slave( 57 | std::make_unique( 58 | [](double x) { return x - 1.1; }), 59 | "slave_one"); 60 | execution.add_slave( 61 | std::make_unique( 62 | [](double x) { return x + 1.1; }, 63 | [](int y) { return y - 4; }, 64 | [](bool z) { return !z; }), 65 | "slave_two"); 66 | 67 | // Run the simulation 68 | auto t = execution.simulate_until_async(std::nullopt); 69 | 70 | constexpr std::chrono::duration sleepTime = std::chrono::milliseconds(500); 71 | 72 | std::this_thread::sleep_for(sleepTime); 73 | 74 | REQUIRE(observer->is_recording()); 75 | observer->stop_recording(); 76 | REQUIRE(!observer->is_recording()); 77 | 78 | REQUIRE(filecount(logPath) == 4); 79 | 80 | remove_directory_contents(logPath); 81 | REQUIRE(filecount(logPath) == 0); 82 | 83 | observer->start_recording(); 84 | std::this_thread::sleep_for(sleepTime); 85 | observer->stop_recording(); 86 | 87 | std::this_thread::sleep_for(sleepTime); 88 | 89 | observer->start_recording(); 90 | std::this_thread::sleep_for(sleepTime); 91 | observer->stop_recording(); 92 | 93 | execution.stop_simulation(); 94 | t.get(); 95 | 96 | REQUIRE(filecount(logPath) == 8); 97 | 98 | // Test that files are released. 99 | remove_directory_contents(logPath); 100 | 101 | REQUIRE(filecount(logPath) == 0); 102 | 103 | } catch (const std::exception& e) { 104 | std::cerr << "Error: " << e.what() << std::endl; 105 | return 1; 106 | } 107 | 108 | return 0; 109 | } 110 | -------------------------------------------------------------------------------- /tests/file_observer_logging_from_config_test.cpp: -------------------------------------------------------------------------------- 1 | #include "mock_slave.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | // A helper macro to test various assertions 15 | #define REQUIRE(test) \ 16 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 17 | 18 | int main() 19 | { 20 | try { 21 | cosim::log::setup_simple_console_logging(); 22 | cosim::log::set_global_output_level(cosim::log::debug); 23 | 24 | constexpr cosim::time_point startTime = cosim::to_time_point(0.0); 25 | constexpr cosim::time_point endTime = cosim::to_time_point(10.0); 26 | constexpr cosim::duration stepSize = cosim::to_duration(0.1); 27 | 28 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 29 | REQUIRE(testDataDir); 30 | cosim::filesystem::path configPath = cosim::filesystem::path(testDataDir) / "LogConfig.xml"; 31 | 32 | const auto logPath = cosim::filesystem::current_path() / "logs"; 33 | cosim::filesystem::path csvPath = cosim::filesystem::path(logPath); 34 | 35 | 36 | // Set up the execution and add observer 37 | auto execution = cosim::execution(startTime, std::make_unique(stepSize)); 38 | auto csv_config = cosim::file_observer_config::parse(configPath); 39 | auto csv_observer = std::make_shared(csvPath, csv_config); 40 | execution.add_observer(csv_observer); 41 | 42 | // Add two slaves to the execution and connect variables 43 | execution.add_slave( 44 | std::make_unique( 45 | [](double x) { return x + 1.234; }, 46 | [](int x) { return x + 1; }, 47 | [](bool x) { return x; }, 48 | [](std::string_view) { return std::string("hello log"); }), 49 | "slave"); 50 | 51 | execution.add_slave( 52 | std::make_unique( 53 | [](double x) { return x + 123.456; }, 54 | [](int x) { return x - 1; }), 55 | "slave1"); 56 | 57 | execution.add_slave( 58 | std::make_unique( 59 | [](double x) { return x + 1.234; }, 60 | [](int x) { return x + 1; }), 61 | "slave2"); 62 | 63 | // Run the simulation 64 | auto simResult = execution.simulate_until(endTime); 65 | REQUIRE(simResult); 66 | 67 | } catch (const std::exception& e) { 68 | std::cerr << "Error: " << e.what() << std::endl; 69 | return 1; 70 | } 71 | 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /tests/file_observer_logging_test.cpp: -------------------------------------------------------------------------------- 1 | #include "mock_slave.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | // A helper macro to test various assertions 15 | #define REQUIRE(test) \ 16 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 17 | 18 | int main() 19 | { 20 | try { 21 | cosim::log::setup_simple_console_logging(); 22 | cosim::log::set_global_output_level(cosim::log::debug); 23 | 24 | constexpr cosim::time_point startTime = cosim::to_time_point(0.0); 25 | constexpr cosim::time_point endTime = cosim::to_time_point(10.0); 26 | constexpr cosim::duration stepSize = cosim::to_duration(0.1); 27 | 28 | const auto logPath = cosim::filesystem::current_path() / "logs"; 29 | cosim::filesystem::path csvPath = cosim::filesystem::path(logPath); 30 | 31 | // Set up the execution 32 | auto execution = cosim::execution(startTime, std::make_unique(stepSize)); 33 | 34 | // Set up and add file observer to the execution 35 | auto csv_observer = std::make_shared(csvPath); 36 | execution.add_observer(csv_observer); 37 | 38 | // Add a slave to the execution and connect variables 39 | execution.add_slave( 40 | std::make_unique([](double x) { return x + 1.234; }), "slave uno"); 41 | execution.add_slave( 42 | std::make_unique([](double x) { return x + 1.234; }, 43 | [](int y) { return y - 4; }, 44 | [](bool z) { return !z; }), 45 | "slave dos"); 46 | 47 | // Run the simulation 48 | auto simResult = execution.simulate_until(endTime); 49 | REQUIRE(simResult); 50 | 51 | // Print the log paths 52 | std::cout << "Log directory: " << csv_observer->get_log_path() << std::endl; 53 | 54 | } catch (const std::exception& e) { 55 | std::cerr << "Error: " << e.what() << std::endl; 56 | return 1; 57 | } 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /tests/fmi_v2_fmu_unittest.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE cosim::fmi::v2::fmu unittest 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | 9 | using namespace cosim; 10 | 11 | BOOST_TEST_DONT_PRINT_LOG_VALUE(fmi::fmi_version) 12 | 13 | BOOST_AUTO_TEST_CASE(v2_fmu) 14 | { 15 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 16 | BOOST_TEST_REQUIRE(!!testDataDir); 17 | auto importer = fmi::importer::create(); 18 | auto fmu = importer->import( 19 | cosim::filesystem::path(testDataDir) / "fmi2" / "vector.fmu"); 20 | 21 | BOOST_TEST(fmu->fmi_version() == fmi::fmi_version::v2_0); 22 | const auto d = fmu->model_description(); 23 | BOOST_TEST(d->name == "com.open-simulation-platform.vector"); 24 | BOOST_TEST(d->uuid == "b928fd8c-743f-50d0-b4bb-98bc5a5f804e"); 25 | BOOST_TEST(d->description == "Has one input and one input, both 3D vectors. Copies the input to the output each step."); 26 | BOOST_TEST(d->author == "SINTEF Ocean & DNV GL"); 27 | BOOST_TEST(d->version == "0.1"); 28 | BOOST_TEST(std::static_pointer_cast(fmu)->fmilib_handle() != nullptr); 29 | BOOST_TEST(cosim::filesystem::exists( 30 | std::static_pointer_cast(fmu)->directory() / "modelDescription.xml")); 31 | 32 | auto instance = fmu->instantiate_slave("testSlave"); 33 | instance->setup( 34 | cosim::to_time_point(0.0), 35 | cosim::to_time_point(1.0), 36 | std::nullopt); 37 | 38 | bool foundInput0 = false; 39 | bool foundOutput1 = false; 40 | for (const auto& v : d->variables) { 41 | if (v.name == "input[0]") { 42 | foundInput0 = true; 43 | BOOST_TEST(v.type == variable_type::real); 44 | BOOST_TEST(v.variability == variable_variability::discrete); 45 | BOOST_TEST(v.causality == variable_causality::input); 46 | double start = std::get(*v.start); 47 | BOOST_TEST(start == 0.0); 48 | double varVal = -1.0; 49 | instance->get_real_variables( 50 | gsl::make_span(&v.reference, 1), gsl::make_span(&varVal, 1)); 51 | BOOST_TEST(varVal == 0.0); 52 | } else if (v.name == "output[1]") { 53 | foundOutput1 = true; 54 | BOOST_TEST(v.type == variable_type::real); 55 | BOOST_TEST(v.variability == variable_variability::discrete); 56 | BOOST_TEST(v.causality == variable_causality::output); 57 | BOOST_TEST(!v.start.has_value()); 58 | } 59 | } 60 | BOOST_TEST(foundInput0); 61 | BOOST_TEST(foundOutput1); 62 | } 63 | -------------------------------------------------------------------------------- /tests/monitor_modified_variables_test.cpp: -------------------------------------------------------------------------------- 1 | #include "mock_slave.hpp" 2 | 3 | #include "cosim/algorithm.hpp" 4 | #include "cosim/execution.hpp" 5 | #include "cosim/log/simple.hpp" 6 | #include "cosim/manipulator/override_manipulator.hpp" 7 | #include "cosim/observer/time_series_observer.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define REQUIRE(test) \ 14 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 15 | 16 | int main() 17 | { 18 | try { 19 | cosim::log::setup_simple_console_logging(); 20 | cosim::log::set_global_output_level(cosim::log::trace); 21 | 22 | constexpr cosim::time_point startTime = cosim::to_time_point(0.0); 23 | constexpr cosim::time_point endTime = cosim::to_time_point(1.0); 24 | constexpr cosim::duration stepSize = cosim::to_duration(0.1); 25 | 26 | auto execution = cosim::execution(startTime, std::make_unique(stepSize)); 27 | 28 | auto observer = std::make_shared(); 29 | auto manipulator = std::make_shared(); 30 | execution.add_observer(observer); 31 | execution.add_manipulator(manipulator); 32 | 33 | auto simIndex = execution.add_slave( 34 | std::make_unique( 35 | [](double x) { return x + 1.234; }, 36 | [](int y) { return y + 2; }), 37 | "Slave"); 38 | 39 | observer->start_observing(cosim::variable_id{simIndex, cosim::variable_type::integer, mock_slave::integer_out_reference}); 40 | 41 | manipulator->override_integer_variable(simIndex, mock_slave::integer_out_reference, 1); 42 | 43 | auto simResult = execution.simulate_until(endTime); 44 | REQUIRE(simResult); 45 | 46 | const int numSamples = 10; 47 | int intOutputValues[numSamples]; 48 | cosim::step_number steps[numSamples]; 49 | cosim::time_point times[numSamples]; 50 | 51 | size_t samplesRead = observer->get_integer_samples(simIndex, mock_slave::integer_out_reference, 1, gsl::make_span(intOutputValues, numSamples), gsl::make_span(steps, numSamples), gsl::make_span(times, numSamples)); 52 | REQUIRE(samplesRead == 10); 53 | 54 | for (size_t i = 0; i < samplesRead; i++) { 55 | REQUIRE(intOutputValues[i] == 1); 56 | } 57 | 58 | auto modifiedVariables = execution.get_modified_variables(); 59 | REQUIRE(modifiedVariables.size() == 1); 60 | 61 | for (auto var : modifiedVariables) { 62 | REQUIRE(var.type == cosim::variable_type::integer); 63 | REQUIRE(var.reference == 0); 64 | } 65 | 66 | } catch (const std::exception& e) { 67 | std::cerr << "Error: " << e.what() << std::endl; 68 | return 1; 69 | } 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /tests/orchestration_unittest.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE orchestration.hpp unittests 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | 9 | BOOST_AUTO_TEST_CASE(file_uri_sub_resolver_absolute_path_test) 10 | { 11 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 12 | BOOST_TEST_REQUIRE(!!testDataDir); 13 | const auto path = cosim::filesystem::path(testDataDir) / "fmi2" / "vector.fmu"; 14 | const auto uri = cosim::path_to_file_uri(path); 15 | 16 | cosim::model_uri_resolver resolver; 17 | resolver.add_sub_resolver(std::make_shared()); 18 | 19 | auto model = resolver.lookup_model(uri); 20 | BOOST_TEST(model->description()->name == "com.open-simulation-platform.vector"); 21 | } 22 | 23 | 24 | BOOST_AUTO_TEST_CASE(file_uri_sub_resolver_relative_path_test) 25 | { 26 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 27 | BOOST_TEST_REQUIRE(!!testDataDir); 28 | const auto basePath = cosim::filesystem::path(testDataDir) / "some_base"; 29 | const auto baseUri = cosim::path_to_file_uri(basePath); 30 | 31 | cosim::model_uri_resolver resolver; 32 | resolver.add_sub_resolver(std::make_shared()); 33 | 34 | auto model = resolver.lookup_model(baseUri, "fmi2/vector.fmu"); 35 | BOOST_TEST(model->description()->name == "com.open-simulation-platform.vector"); 36 | } 37 | -------------------------------------------------------------------------------- /tests/osp_config_parser_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | 12 | #define REQUIRE(test) \ 13 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 14 | 15 | void test(const cosim::filesystem::path& configPath, size_t expectedNumConnections) 16 | { 17 | auto resolver = cosim::default_model_uri_resolver(); 18 | const auto config = cosim::load_osp_config(configPath, *resolver); 19 | auto algorithm_cfg = std::get(config.algorithm_configuration); 20 | 21 | auto execution = cosim::execution( 22 | config.start_time, 23 | std::make_shared(algorithm_cfg)); 24 | 25 | const auto entityMaps = cosim::inject_system_structure( 26 | execution, config.system_structure, config.initial_values); 27 | 28 | REQUIRE(entityMaps.simulators.size() == 4); 29 | REQUIRE(boost::size(config.system_structure.connections()) == expectedNumConnections); 30 | 31 | auto obs = std::make_shared(); 32 | execution.add_observer(obs); 33 | 34 | auto result = execution.simulate_until(cosim::to_time_point(0.01)); 35 | REQUIRE(result); 36 | 37 | const auto simIndex = entityMaps.simulators.at("CraneController"); 38 | const auto varReference1 = config.system_structure.get_variable_description({"CraneController", "cl1_min"}).reference; 39 | double realValue = -1.0; 40 | 41 | obs->get_real(simIndex, gsl::make_span(&varReference1, 1), gsl::make_span(&realValue, 1)); 42 | 43 | double magicNumberFromConf = 2.2; 44 | REQUIRE(std::fabs(realValue - magicNumberFromConf) < 1e-9); 45 | } 46 | 47 | int main() 48 | { 49 | try { 50 | cosim::log::setup_simple_console_logging(); 51 | cosim::log::set_global_output_level(cosim::log::info); 52 | 53 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 54 | REQUIRE(testDataDir); 55 | test(cosim::filesystem::path(testDataDir) / "msmi", 7); 56 | test(cosim::filesystem::path(testDataDir) / "msmi" / "OspSystemStructure_Bond.xml", 9); 57 | } catch (const std::exception& e) { 58 | std::cerr << "Error: " << e.what(); 59 | return 1; 60 | } 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /tests/proxyfmu_library_unittest.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE proxyfmu_library unittests 2 | 3 | #include 4 | #include 5 | 6 | using namespace proxyfmu; 7 | using namespace proxyfmu::fmi; 8 | 9 | namespace 10 | { 11 | 12 | void test(fmu& fmu) 13 | { 14 | const auto d = fmu.get_model_description(); 15 | BOOST_TEST(d.modelName == "no.viproma.demo.identity"); 16 | BOOST_TEST(d.description == 17 | "Has one input and one output of each type, and outputs are always set equal to inputs"); 18 | BOOST_TEST(d.author == "Lars Tandle Kyllingstad"); 19 | 20 | auto slave = fmu.new_instance("instance"); 21 | BOOST_REQUIRE(slave->setup_experiment()); 22 | BOOST_REQUIRE(slave->enter_initialization_mode()); 23 | BOOST_REQUIRE(slave->exit_initialization_mode()); 24 | 25 | std::vector vr{0}; 26 | 27 | std::vector realVal{0.0}; 28 | std::vector integerVal{0}; 29 | std::vector booleanVal{false}; 30 | std::vector stringVal{""}; 31 | 32 | std::vector realRef(1); 33 | std::vector integerRef(1); 34 | std::vector booleanRef(1); 35 | std::vector stringRef(1); 36 | 37 | double t = 0.0; 38 | double tEnd = 1.0; 39 | double dt = 0.1; 40 | 41 | while (t <= tEnd) { 42 | 43 | slave->get_real(vr, realRef); 44 | slave->get_integer(vr, integerRef); 45 | slave->get_boolean(vr, booleanRef); 46 | slave->get_string(vr, stringRef); 47 | 48 | BOOST_TEST(realVal[0] == realRef[0]); 49 | BOOST_TEST(integerVal[0] == integerRef[0]); 50 | BOOST_TEST(booleanVal[0] == booleanRef[0]); 51 | BOOST_TEST(stringVal[0] == stringRef[0]); 52 | 53 | BOOST_REQUIRE(slave->step(t, dt)); 54 | 55 | realVal[0] += 1.0; 56 | integerVal[0] += 1; 57 | booleanVal[0] = !booleanVal[0]; 58 | stringVal[0] += 'a'; 59 | 60 | slave->set_real(vr, realVal); 61 | slave->set_integer(vr, integerVal); 62 | slave->set_boolean(vr, booleanVal); 63 | slave->set_string(vr, stringVal); 64 | 65 | t += dt; 66 | } 67 | 68 | BOOST_REQUIRE(slave->terminate()); 69 | slave->freeInstance(); 70 | } 71 | 72 | } // namespace 73 | 74 | BOOST_AUTO_TEST_CASE(proxyfmu_fmi_test_identity) 75 | { 76 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 77 | BOOST_TEST_REQUIRE(!!testDataDir); 78 | 79 | auto fmuPath = proxyfmu::filesystem::path(testDataDir) / "fmi1" / "identity.fmu"; 80 | auto fmu = loadFmu(fmuPath); 81 | test(*fmu); 82 | } 83 | 84 | BOOST_AUTO_TEST_CASE(proxyfmu_client_test_identity) 85 | { 86 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 87 | BOOST_TEST_REQUIRE(!!testDataDir); 88 | 89 | auto fmuPath = proxyfmu::filesystem::path(testDataDir) / "fmi1" / "identity.fmu"; 90 | auto fmu = client::proxy_fmu(fmuPath); 91 | test(fmu); 92 | } -------------------------------------------------------------------------------- /tests/proxyfmu_osp_config_parser_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | 12 | #define REQUIRE(test) \ 13 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 14 | 15 | void test(const cosim::filesystem::path& configPath, size_t expectedNumConnections) 16 | { 17 | auto resolver = cosim::default_model_uri_resolver(); 18 | const auto config = cosim::load_osp_config(configPath, *resolver); 19 | auto execution = cosim::execution( 20 | config.start_time, 21 | std::make_shared(std::get(config.algorithm_configuration))); 22 | 23 | const auto entityMaps = cosim::inject_system_structure( 24 | execution, config.system_structure, config.initial_values); 25 | REQUIRE(entityMaps.simulators.size() == 4); 26 | REQUIRE(boost::size(config.system_structure.connections()) == expectedNumConnections); 27 | 28 | auto obs = std::make_shared(); 29 | execution.add_observer(obs); 30 | 31 | auto result = execution.simulate_until(cosim::to_time_point(1e-3)); 32 | REQUIRE(result); 33 | 34 | const auto simIndex = entityMaps.simulators.at("CraneController"); 35 | const auto varReference = 36 | config.system_structure.get_variable_description({"CraneController", "cl1_min"}).reference; 37 | double realValue = -1.0; 38 | obs->get_real(simIndex, gsl::make_span(&varReference, 1), gsl::make_span(&realValue, 1)); 39 | 40 | double magicNumberFromConf = 2.2; 41 | REQUIRE(std::fabs(realValue - magicNumberFromConf) < 1e-9); 42 | } 43 | 44 | int main() 45 | { 46 | try { 47 | cosim::log::setup_simple_console_logging(); 48 | cosim::log::set_global_output_level(cosim::log::info); 49 | 50 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 51 | REQUIRE(testDataDir); 52 | test(cosim::filesystem::path(testDataDir) / "msmi" / "OspSystemStructure_proxyfmu.xml", 9); 53 | } catch (const std::exception& e) { 54 | std::cerr << "Error: " << e.what(); 55 | return 1; 56 | } 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /tests/proxyfmu_save_state_test.cpp: -------------------------------------------------------------------------------- 1 | #include "mock_slave.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // A helper macro to test various assertions 21 | #define REQUIRE(test) \ 22 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 23 | 24 | // Helper function to get observed values from multiple simulators. 25 | std::vector get_reals( 26 | cosim::last_value_observer& observer, 27 | const std::vector& simulators, 28 | cosim::value_reference valueRef) 29 | { 30 | auto values = std::vector( 31 | simulators.size(), std::numeric_limits::quiet_NaN()); 32 | for (std::size_t i = 0; i < simulators.size(); ++i) { 33 | observer.get_real( 34 | simulators[i], 35 | gsl::make_span(&valueRef, 1), 36 | gsl::make_span(values.data() + i, 1)); 37 | } 38 | return values; 39 | } 40 | 41 | int main() 42 | { 43 | try { 44 | cosim::log::setup_simple_console_logging(); 45 | cosim::log::set_global_output_level(cosim::log::debug); 46 | 47 | // ================================================================ 48 | // == Reference FMU test - Dahlquist (for byte vectors) 49 | // ================================================================ 50 | constexpr cosim::duration stepSize = cosim::to_duration(0.05); 51 | 52 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 53 | REQUIRE(testDataDir); 54 | auto configPath = cosim::filesystem::path(testDataDir) / "msmi" / "OspSystemStructure_Dahlquist_proxyfmu.xml"; 55 | 56 | auto resolver = cosim::default_model_uri_resolver(); 57 | const auto config = cosim::load_osp_config(configPath, *resolver); 58 | auto algorithm_cfg = std::get(config.algorithm_configuration); 59 | 60 | auto execution = cosim::execution( 61 | config.start_time, 62 | std::make_shared(algorithm_cfg)); 63 | 64 | const auto entityMaps = cosim::inject_system_structure( 65 | execution, config.system_structure, config.initial_values); 66 | 67 | auto obs = std::make_shared(); 68 | execution.add_observer(obs); 69 | 70 | const auto timeRef = 0; 71 | const auto xRef = 1; 72 | const auto velocityRef = 2; 73 | 74 | execution.simulate_until(cosim::to_time_point(0.5)); 75 | auto timeValues = get_reals(*obs, {0}, timeRef); 76 | auto xValues = get_reals(*obs, {0}, xRef); 77 | auto velocityValues = get_reals(*obs, {0}, velocityRef); 78 | 79 | const auto state_bb = execution.export_current_state(); 80 | 81 | // Export state 82 | std::ofstream outFile("state_bb.bin", std::ios::binary); 83 | outFile << cosim::serialization::format::cbor << state_bb; 84 | outFile.close(); 85 | 86 | execution.simulate_until(cosim::to_time_point(0.5)); 87 | 88 | // import state 89 | std::ifstream inFile("state_bb.bin", std::ios::binary); 90 | cosim::serialization::node state_bb_imported; 91 | inFile >> cosim::serialization::format::cbor >> state_bb_imported; 92 | inFile.close(); 93 | 94 | execution.import_state(state_bb_imported); 95 | auto state_bb_2 = execution.export_current_state(); 96 | 97 | auto timeValues2 = get_reals(*obs, {0}, timeRef); 98 | auto xValues2 = get_reals(*obs, {0}, xRef); 99 | auto velocityValues2 = get_reals(*obs, {0}, velocityRef); 100 | 101 | REQUIRE(timeValues == timeValues2); 102 | REQUIRE(xValues == xValues2); 103 | REQUIRE(velocityValues == velocityValues2); 104 | 105 | REQUIRE(state_bb_2 == state_bb_imported); 106 | REQUIRE(state_bb == state_bb_imported); 107 | 108 | } catch (const std::exception& e) { 109 | std::cerr << "Error: " << e.what() << std::endl; 110 | return 1; 111 | } 112 | return 0; 113 | } 114 | -------------------------------------------------------------------------------- /tests/slave_simulator_unittest.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE cosim::slave_simulator unittest 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | 10 | BOOST_AUTO_TEST_CASE(slave_simulator_save_state) 11 | { 12 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 13 | BOOST_TEST_REQUIRE(!!testDataDir); 14 | auto importer = cosim::fmi::importer::create(); 15 | const std::string modelName = "Dahlquist"; 16 | auto fmu = importer->import( 17 | cosim::filesystem::path(testDataDir) / "fmi2" / (modelName + ".fmu")); 18 | const auto modelDescription = fmu->model_description(); 19 | BOOST_TEST(modelDescription->uuid == "{221063D2-EF4A-45FE-B954-B5BFEEA9A59B}"); 20 | 21 | const auto xVar = cosim::find_variable(*modelDescription, "x")->reference; 22 | 23 | auto t = cosim::time_point(); 24 | const auto dt = cosim::to_duration(1.0); 25 | 26 | auto sim = cosim::slave_simulator( 27 | fmu->instantiate_slave("testSlave"), 28 | "testSlave"); 29 | sim.expose_for_getting(cosim::variable_type::real, xVar); 30 | 31 | sim.setup(t, {}, {}); 32 | const auto value0 = sim.get_real(xVar); 33 | BOOST_TEST(value0 == 1.0); 34 | const auto state0 = sim.save_state(); 35 | 36 | sim.start_simulation(); 37 | sim.do_step(t, dt); 38 | t += dt; 39 | const auto value1 = sim.get_real(xVar); 40 | BOOST_TEST((0.0 < value1 && value1 < value0)); 41 | const auto state1 = sim.save_state(); 42 | const auto exportedState1 = sim.export_state(state1); 43 | sim.release_state(state1); 44 | 45 | sim.do_step(t, dt); 46 | t += dt; 47 | const auto value2 = sim.get_real(xVar); 48 | BOOST_TEST((0.0 < value2 && value2 < value1)); 49 | const auto state2 = sim.save_state(); 50 | 51 | sim.do_step(t, dt); 52 | t += dt; 53 | const auto value3 = sim.get_real(xVar); 54 | BOOST_TEST((0.0 < value3 && value3 < value2)); 55 | const auto state3 = state2; 56 | sim.save_state(state3); // Overwrite the previously-saved state 57 | 58 | const auto state1New = sim.import_state(exportedState1); 59 | sim.restore_state(state1New); 60 | const auto value1Test = sim.get_real(xVar); 61 | BOOST_TEST(value1Test == value1); 62 | 63 | sim.restore_state(state3); 64 | const auto value3Test = sim.get_real(xVar); 65 | BOOST_TEST(value3Test == value3); 66 | 67 | sim.restore_state(state0); 68 | t = cosim::time_point(); 69 | sim.start_simulation(); 70 | sim.do_step(t, dt); 71 | t += dt; 72 | sim.do_step(t, dt); 73 | t += dt; 74 | const auto value0To2Test = sim.get_real(xVar); 75 | BOOST_TEST(value0To2Test == value2); 76 | 77 | sim.release_state(state0); 78 | sim.release_state(state1New); 79 | sim.release_state(state3); 80 | } 81 | -------------------------------------------------------------------------------- /tests/state_init_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #define REQUIRE(test) \ 14 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 15 | 16 | int main() 17 | { 18 | try { 19 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 20 | REQUIRE(!!testDataDir); 21 | 22 | cosim::filesystem::path configPath = testDataDir; 23 | 24 | auto resolver = cosim::default_model_uri_resolver(); 25 | const auto config = cosim::load_osp_config(configPath / "msmi" / "OspSystemStructure_StateInitExample.xml", *resolver); 26 | auto algorithm_cfg = std::get(config.algorithm_configuration); 27 | 28 | auto execution = cosim::execution( 29 | config.start_time, 30 | std::make_shared(algorithm_cfg)); 31 | 32 | const auto entityMaps = cosim::inject_system_structure( 33 | execution, config.system_structure, config.initial_values); 34 | auto lvObserver = std::make_shared(); 35 | 36 | execution.add_observer(lvObserver); 37 | execution.simulate_until(cosim::to_time_point(0.1)); 38 | 39 | auto sim = entityMaps.simulators.at("example"); 40 | const auto paramRef = config.system_structure.get_variable_description({"example", "Parameters.Integrator1_x0"}).reference; 41 | const auto outRef = config.system_structure.get_variable_description({"example", "Integrator_out1"}).reference; 42 | 43 | double initialValue = 0.0; 44 | double outputValue = 0.0; 45 | lvObserver->get_real(sim, gsl::make_span(¶mRef, 1), gsl::make_span(&initialValue, 1)); 46 | lvObserver->get_real(sim, gsl::make_span(&outRef, 1), gsl::make_span(&outputValue, 1)); 47 | 48 | REQUIRE(std::fabs(initialValue - 10.0) < 1.0e-9); 49 | REQUIRE(std::fabs(outputValue - 10.1) < 1.0e-9); 50 | 51 | } catch (const std::exception& e) { 52 | std::cerr << "Error: " << e.what() << std::endl; 53 | return 1; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/synchronized_xy_series_test.cpp: -------------------------------------------------------------------------------- 1 | #include "mock_slave.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define REQUIRE(test) \ 13 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 14 | 15 | int main() 16 | { 17 | try { 18 | cosim::log::setup_simple_console_logging(); 19 | cosim::log::set_global_output_level(cosim::log::debug); 20 | 21 | constexpr cosim::time_point startTime = cosim::to_time_point(0.0); 22 | constexpr cosim::time_point midTime = cosim::to_time_point(1.0); 23 | constexpr cosim::time_point endTime = cosim::to_time_point(2.0); 24 | constexpr cosim::duration stepSize = cosim::to_duration(0.1); 25 | 26 | auto algorithm = std::make_shared(stepSize); 27 | auto execution = cosim::execution(startTime, algorithm); 28 | 29 | auto observer = std::make_shared(); 30 | execution.add_observer(observer); 31 | 32 | double x1 = 0; 33 | auto simIndex1 = execution.add_slave( 34 | std::make_unique([&x1](double x) { return x + x1++; }), "slave uno"); 35 | 36 | double x2 = 0; 37 | auto simIndex2 = execution.add_slave( 38 | std::make_unique([&x2](double x) { return x + x2++; }), "slave dos"); 39 | 40 | algorithm->set_stepsize_decimation_factor(simIndex2, 2); 41 | 42 | auto variableId1 = cosim::variable_id{simIndex1, cosim::variable_type::real, 0}; 43 | auto variableId2 = cosim::variable_id{simIndex2, cosim::variable_type::real, 0}; 44 | 45 | observer->start_observing(variableId1); 46 | 47 | // Run the simulation 48 | auto simResult = execution.simulate_until(midTime); 49 | REQUIRE(simResult); 50 | 51 | observer->start_observing(variableId2); 52 | 53 | simResult = execution.simulate_until(endTime); 54 | REQUIRE(simResult); 55 | 56 | const int numSamples = 20; 57 | const cosim::value_reference varIndex = 0; 58 | double realValues1[numSamples]; 59 | double realValues2[numSamples]; 60 | 61 | size_t samplesRead = observer->get_synchronized_real_series(simIndex1, varIndex, simIndex2, varIndex, 5, realValues1, realValues2); 62 | REQUIRE(samplesRead == 5); 63 | 64 | double expectedReals1[5] = {12.0, 14.0, 16.0, 18.0, 20.0}; 65 | double expectedReals2[5] = {6.0, 7.0, 8.0, 9.0, 10.0}; 66 | 67 | for (size_t i = 0; i < samplesRead; i++) { 68 | REQUIRE(std::fabs(realValues1[i] - expectedReals1[i]) < 1.0e9); 69 | REQUIRE(std::fabs(realValues2[i] - expectedReals2[i]) < 1.0e9); 70 | } 71 | 72 | } catch (const std::exception& e) { 73 | std::cerr << "Error: " << e.what() << std::endl; 74 | return 1; 75 | } 76 | 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /tests/time_unittest.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE time.hpp unittests 2 | #include 3 | 4 | #include 5 | 6 | 7 | BOOST_AUTO_TEST_CASE(to_double_time_point_duration_addition) 8 | { 9 | constexpr cosim::duration time[] = { 10 | cosim::duration(0), 11 | std::chrono::nanoseconds(1), 12 | std::chrono::microseconds(1), 13 | std::chrono::milliseconds(1), 14 | std::chrono::seconds(1), 15 | std::chrono::minutes(1), 16 | std::chrono::hours(1), 17 | std::chrono::hours(1000), 18 | std::chrono::hours(100'000)}; 19 | 20 | for (const auto t : time) { 21 | for (const auto dt : time) { 22 | const auto t1 = cosim::time_point{t}; 23 | BOOST_TEST(cosim::to_double_time_point(t1) + cosim::to_double_duration(dt, t1) == cosim::to_double_time_point(t1 + dt)); 24 | } 25 | } 26 | } 27 | 28 | BOOST_TEST_DONT_PRINT_LOG_VALUE(cosim::duration) 29 | BOOST_TEST_DONT_PRINT_LOG_VALUE(cosim::time_point) 30 | 31 | BOOST_AUTO_TEST_CASE(to_time_point_duration_addition) 32 | { 33 | constexpr double time[] = {0.0, 1e-9, 1e-6, 1e-3, 1.0, 1e3, 1e6, 1e9}; 34 | 35 | for (const auto t1 : time) { 36 | for (const auto dt : time) { 37 | BOOST_TEST(cosim::to_time_point(t1) + cosim::to_duration(dt, t1) == cosim::to_time_point(t1 + dt)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/trend_buffer_test.cpp: -------------------------------------------------------------------------------- 1 | #include "mock_slave.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | // A helper macro to test various assertions 15 | #define REQUIRE(test) \ 16 | if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) 17 | 18 | 19 | int main() 20 | { 21 | try { 22 | cosim::log::setup_simple_console_logging(); 23 | cosim::log::set_global_output_level(cosim::log::debug); 24 | 25 | constexpr int numSlaves = 2; 26 | constexpr cosim::time_point startTime; 27 | constexpr cosim::time_point midTime = cosim::to_time_point(1.0); 28 | constexpr cosim::duration stepSize = cosim::to_duration(0.1); 29 | 30 | // Set up execution 31 | auto execution = cosim::execution( 32 | startTime, 33 | std::make_unique(stepSize)); 34 | 35 | auto observer = std::make_shared(); 36 | execution.add_observer(observer); 37 | 38 | const cosim::value_reference realOutIndex = 0; 39 | const cosim::value_reference realInIndex = 1; 40 | 41 | // Add slaves to it 42 | for (int i = 0; i < numSlaves; ++i) { 43 | execution.add_slave( 44 | std::make_unique([](double x) { return x + 1.234; }), 45 | "slave" + std::to_string(i)); 46 | if (i > 0) { 47 | execution.connect_variables( 48 | cosim::variable_id{i - 1, cosim::variable_type::real, realOutIndex}, 49 | cosim::variable_id{i, cosim::variable_type::real, realInIndex}); 50 | } 51 | } 52 | 53 | // Run simulation 54 | auto simResult = execution.simulate_until(midTime); 55 | REQUIRE(simResult); 56 | 57 | cosim::step_number stepNumbers1[2]; 58 | observer->get_step_numbers(0, cosim::to_duration(0.5), gsl::make_span(stepNumbers1, 2)); 59 | REQUIRE(stepNumbers1[0] == 5); 60 | REQUIRE(stepNumbers1[1] == 10); 61 | 62 | cosim::step_number stepNumbers2[2]; 63 | observer->get_step_numbers(0, cosim::to_time_point(0.4), cosim::to_time_point(0.9), gsl::make_span(stepNumbers2, 2)); 64 | REQUIRE(stepNumbers2[0] == 4); 65 | REQUIRE(stepNumbers2[1] == 9); 66 | 67 | } catch (const std::exception& e) { 68 | std::cerr << "Error: " << e.what() << std::endl; 69 | return 1; 70 | } 71 | 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /tests/utility_concurrency_unittest.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE cosim::utility unittests 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | /* 10 | * A generalised shared mutex testing function. 11 | * 12 | * `getMutexN` must be functions that return references to 3 separate mutexes. 13 | * These may be in the form of actual references (e.g. `mutex&`), or in the 14 | * form of mutex wrappers (e.g. `unique_lock`). 15 | */ 16 | template 17 | void test_locking(F1&& getMutex1, F2&& getMutex2, F3&& getMutex3) 18 | { 19 | auto&& mutex1 = getMutex1(); 20 | auto&& mutex2 = getMutex2(); 21 | auto&& mutex3 = getMutex3(); 22 | 23 | // Step 1 24 | mutex1.lock(); 25 | mutex2.lock_shared(); 26 | 27 | auto t = std::thread([&getMutex1, &getMutex2, &getMutex3] { 28 | auto&& mutex1 = getMutex1(); 29 | auto&& mutex2 = getMutex2(); 30 | auto&& mutex3 = getMutex3(); 31 | 32 | // Step 2 33 | BOOST_TEST_REQUIRE(mutex3.try_lock()); 34 | BOOST_TEST_REQUIRE(!mutex1.try_lock()); 35 | BOOST_TEST_REQUIRE(!mutex1.try_lock_shared()); 36 | BOOST_TEST_REQUIRE(!mutex2.try_lock()); 37 | BOOST_TEST_REQUIRE(mutex2.try_lock_shared()); 38 | mutex3.unlock(); 39 | 40 | // Wait for step 3 to complete 41 | mutex1.lock(); 42 | 43 | // Step 4 44 | mutex1.unlock(); 45 | mutex2.unlock_shared(); 46 | }); 47 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 48 | 49 | // Wait for step 2 to complete 50 | mutex3.lock(); 51 | 52 | // Step 3 53 | mutex2.unlock_shared(); 54 | mutex1.unlock(); 55 | 56 | // Wait for step 4 to complete 57 | mutex2.lock(); 58 | 59 | // Clean up 60 | t.join(); 61 | mutex2.unlock(); 62 | mutex3.unlock(); 63 | } 64 | 65 | BOOST_AUTO_TEST_CASE(file_lock) 66 | { 67 | const auto workDir = cosim::utility::temp_dir(); 68 | test_locking( 69 | [&] { return cosim::utility::file_lock(workDir.path() / "lockfile1"); }, 70 | [&] { return cosim::utility::file_lock(workDir.path() / "lockfile2"); }, 71 | [&] { return cosim::utility::file_lock(workDir.path() / "lockfile3"); }); 72 | } 73 | -------------------------------------------------------------------------------- /tests/utility_filesystem_unittest.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE cosim::utility filesystem unittests 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | 10 | BOOST_AUTO_TEST_CASE(temp_dir) 11 | { 12 | namespace fs = cosim::filesystem; 13 | fs::path d; 14 | { 15 | auto tmp = cosim::utility::temp_dir(); 16 | d = tmp.path(); 17 | BOOST_TEST_REQUIRE(!d.empty()); 18 | BOOST_TEST_REQUIRE(fs::exists(d)); 19 | BOOST_TEST(fs::is_directory(d)); 20 | BOOST_TEST(fs::is_empty(d)); 21 | 22 | auto tmp2 = std::move(tmp); 23 | BOOST_TEST(tmp.path().empty()); 24 | BOOST_TEST(tmp2.path() == d); 25 | BOOST_TEST(fs::exists(d)); 26 | 27 | auto tmp3 = cosim::utility::temp_dir(); 28 | const auto d3 = tmp3.path(); 29 | BOOST_TEST(fs::exists(d3)); 30 | BOOST_TEST_REQUIRE(!fs::equivalent(d, d3)); 31 | 32 | tmp3 = std::move(tmp2); 33 | BOOST_TEST(tmp2.path().empty()); 34 | BOOST_TEST(!fs::exists(d3)); 35 | BOOST_TEST(tmp3.path() == d); 36 | BOOST_TEST(fs::exists(d)); 37 | } 38 | BOOST_TEST(!fs::exists(d)); 39 | } 40 | -------------------------------------------------------------------------------- /tests/utility_uuid_unittest.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE cosim::utility uuid unittests 2 | #include 3 | 4 | #include 5 | 6 | 7 | BOOST_AUTO_TEST_CASE(random_uuid) 8 | { 9 | const auto u = cosim::utility::random_uuid(); 10 | BOOST_TEST(u.size() == 36); 11 | BOOST_TEST(cosim::utility::random_uuid() != u); 12 | } 13 | -------------------------------------------------------------------------------- /tests/utility_zip_unittest.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE cosim::utility::zip unittest 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | 12 | BOOST_AUTO_TEST_CASE(zip_archive) 13 | { 14 | namespace ut = cosim::utility; 15 | namespace uz = cosim::utility::zip; 16 | namespace fs = cosim::filesystem; 17 | 18 | // Info about the test archive file and its contents 19 | const std::uint64_t archiveEntryCount = 3; 20 | const std::string dirFilename = "images/"; 21 | const std::string binFilename = "smiley.png"; 22 | const std::string txtFilename = "a text file.txt"; 23 | const std::string dirName = dirFilename; 24 | const std::string binName = dirFilename + binFilename; 25 | const std::string txtName = txtFilename; 26 | const std::uint64_t binSize = 16489; 27 | const std::uint64_t txtSize = 13; 28 | 29 | // Test setup 30 | const auto testDataDir = std::getenv("TEST_DATA_DIR"); 31 | BOOST_TEST_REQUIRE(!!testDataDir); 32 | const auto archivePath = fs::path(testDataDir) / "ziptest.zip"; 33 | 34 | // Open archive 35 | auto archive = uz::archive(archivePath); 36 | BOOST_TEST_REQUIRE(archive.is_open()); 37 | 38 | // Get entry info 39 | BOOST_TEST(archive.entry_count() == archiveEntryCount); 40 | const auto dirIndex = archive.find_entry(dirName); 41 | const auto binIndex = archive.find_entry(binName); 42 | const auto txtIndex = archive.find_entry(txtName); 43 | const auto invIndex = archive.find_entry("no such entry"); 44 | BOOST_TEST_REQUIRE(dirIndex != uz::invalid_entry_index); 45 | BOOST_TEST_REQUIRE(binIndex != uz::invalid_entry_index); 46 | BOOST_TEST_REQUIRE(txtIndex != uz::invalid_entry_index); 47 | BOOST_TEST(invIndex == uz::invalid_entry_index); 48 | BOOST_TEST(binIndex != dirIndex); 49 | BOOST_TEST(txtIndex != dirIndex); 50 | BOOST_TEST(txtIndex != binIndex); 51 | BOOST_TEST(archive.entry_name(dirIndex) == dirName); 52 | BOOST_TEST(archive.entry_name(binIndex) == binName); 53 | BOOST_TEST(archive.entry_name(txtIndex) == txtName); 54 | BOOST_CHECK_THROW(archive.entry_name(invIndex), uz::error); 55 | BOOST_TEST(archive.is_dir_entry(dirIndex)); 56 | BOOST_TEST(!archive.is_dir_entry(binIndex)); 57 | BOOST_TEST(!archive.is_dir_entry(txtIndex)); 58 | BOOST_CHECK_THROW(archive.is_dir_entry(invIndex), uz::error); 59 | 60 | // Extract entire archive 61 | { 62 | ut::temp_dir tempDir; 63 | archive.extract_all(tempDir.path()); 64 | const auto dirExtracted = tempDir.path() / dirName; 65 | const auto binExtracted = tempDir.path() / binName; 66 | const auto txtExtracted = tempDir.path() / txtName; 67 | BOOST_TEST_REQUIRE(fs::exists(dirExtracted)); 68 | BOOST_TEST_REQUIRE(fs::exists(binExtracted)); 69 | BOOST_TEST_REQUIRE(fs::exists(txtExtracted)); 70 | BOOST_TEST(fs::is_directory(dirExtracted)); 71 | BOOST_TEST(fs::is_regular_file(binExtracted)); 72 | BOOST_TEST(fs::is_regular_file(txtExtracted)); 73 | BOOST_TEST(fs::file_size(binExtracted) == binSize); 74 | BOOST_TEST(fs::file_size(txtExtracted) == txtSize); 75 | BOOST_CHECK_THROW(archive.extract_file_to(binIndex, tempDir.path() / "nonexistent"), std::system_error); 76 | } 77 | 78 | // Extract individual entries 79 | { 80 | ut::temp_dir tempDir; 81 | const auto binExtracted = archive.extract_file_to(binIndex, tempDir.path()); 82 | const auto txtExtracted = archive.extract_file_to(txtIndex, tempDir.path()); 83 | BOOST_TEST(binExtracted.compare(tempDir.path() / binFilename) == 0); 84 | BOOST_TEST(txtExtracted.compare(tempDir.path() / txtFilename) == 0); 85 | BOOST_TEST(fs::file_size(binExtracted) == binSize); 86 | BOOST_TEST(fs::file_size(txtExtracted) == txtSize); 87 | BOOST_CHECK_THROW(archive.extract_file_to(invIndex, tempDir.path()), uz::error); 88 | BOOST_CHECK_THROW(archive.extract_file_to(binIndex, tempDir.path() / "nonexistent"), std::system_error); 89 | } 90 | 91 | archive.discard(); 92 | BOOST_TEST(!archive.is_open()); 93 | BOOST_CHECK_NO_THROW(archive.discard()); 94 | } 95 | -------------------------------------------------------------------------------- /tools/housekeeping: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script performs the following operations on version-tracked files within 4 | # the current working directory and its subdirectories: 5 | # 6 | # - Fixes file permissions 7 | # - Ensures that all text files end with a newline 8 | # - Runs clang-format on all source files 9 | # 10 | set -o errexit -o nounset -o pipefail 11 | 12 | # Regex to select files for clang-format 13 | readonly clangFormattableFiles='\.(h|c|hpp|cpp)$' 14 | 15 | # Per-file corrections 16 | for f in $(git ls-files); do 17 | # Ensure that only executable files have executable permissions 18 | if { file "$f" | grep -q " executable"; }; then 19 | chmod -c 0755 "$f" 20 | else 21 | chmod -c 0644 "$f" 22 | fi 23 | 24 | # Ensure that all text files end with a newline 25 | if { file "$f" | grep -q " text"; }; then 26 | { tail -c1 "$f" | read -r _; } || { echo >> "$f"; echo "appended newline to '$f'"; } 27 | fi 28 | done 29 | 30 | # Apply clang-format to all relevant source files 31 | echo "running clang-format" 32 | git ls-files \ 33 | | egrep "$clangFormattableFiles" \ 34 | | xargs -r clang-format -i -style=file 35 | -------------------------------------------------------------------------------- /tools/tmp_cleanup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script removes libcosim generated files from the temp directory, 4 | # which for some reason has not been disposed due to a program crash etc. 5 | # 6 | # Works on Windows and Linux 7 | # 8 | 9 | cd /tmp 10 | 11 | # libcosim and libcosim4j 12 | rm -rf libcosim_* 13 | 14 | # FMI4j (fmu-proxy) 15 | rm -rf fmi4j_* 16 | 17 | # proxy-fmu 18 | rm -rf proxy_fmu_* 19 | 20 | # Other known FMI related files 21 | 22 | # sintef vessel model FMU 23 | rm -rf vesselFmu* 24 | 25 | # JavaFMI & JNA 26 | rm -rf fmu_* 27 | rm -rf org.javafmi* 28 | rm -rf native-platform* 29 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 0.11.2 2 | --------------------------------------------------------------------------------