Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,49 @@ file(STRINGS "${CMAKE_SOURCE_DIR}/version.txt" projectVersion)
project("cppfmu-library" VERSION ${projectVersion})

option(CPPFMU_FMI_1 "Use FMI 1.0" OFF)
option(CPPFMU_FMI_3 "Use FMI 3.0" OFF)

if(CPPFMU_FMI_1 AND CPPFMU_FMI_3)
message(FATAL_ERROR "CPPFMU_FMI_1 and CPPFMU_FMI_3 are mutually exclusive; enable at most one.")
endif()

if(CPPFMU_FMI_1)
find_package(fmi1 CONFIG REQUIRED)
set(FMI fmi1::cosim)
set(FMI_SOURCES ${CMAKE_SOURCE_DIR}/cppfmu_cs.cpp)
elseif(CPPFMU_FMI_3)
find_package(fmi3 CONFIG REQUIRED)
set(FMI fmi3::fmi3)
set(FMI_SOURCES ${CMAKE_SOURCE_DIR}/cppfmu_cs_fmi3.cpp)
Comment thread
joakimono marked this conversation as resolved.
else()
find_package(fmi2 CONFIG REQUIRED)
set(FMI fmi2::fmi2)
set(FMI_SOURCES ${CMAKE_SOURCE_DIR}/cppfmu_cs.cpp)
endif()

set(sources ${CMAKE_SOURCE_DIR}/cppfmu_cs.cpp)
# fmi_functions.cpp must be compiled by end user
# fmi_functions.cpp / fmi3_functions.cpp must be compiled by end user

add_library(cppfmu STATIC ${sources})
add_library(cppfmu STATIC ${FMI_SOURCES})
target_include_directories(cppfmu PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(cppfmu PUBLIC ${FMI})

if(CPPFMU_FMI_1)
target_compile_definitions(cppfmu PUBLIC CPPFMU_USE_FMI_1_0)
elseif(CPPFMU_FMI_3)
target_compile_definitions(cppfmu PUBLIC CPPFMU_USE_FMI_3_0)
endif()

install(TARGETS cppfmu ARCHIVE DESTINATION lib RUNTIME DESTINATION bin LIBRARY DESTINATION lib)
install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_common.hpp ${CMAKE_SOURCE_DIR}/cppfmu_cs.hpp DESTINATION ${CMAKE_INSTALL_PREFIX}/include)
install(FILES ${CMAKE_SOURCE_DIR}/fmi_functions.cpp DESTINATION ${CMAKE_INSTALL_PREFIX}/src)
install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_common.hpp DESTINATION include)
if(CPPFMU_FMI_3)
install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_cs_fmi3.hpp DESTINATION include)
install(FILES ${CMAKE_SOURCE_DIR}/fmi3_functions.cpp DESTINATION src)
else()
install(FILES ${CMAKE_SOURCE_DIR}/cppfmu_cs.hpp DESTINATION include)
install(FILES ${CMAKE_SOURCE_DIR}/fmi_functions.cpp DESTINATION src)
endif()

if(NOT CPPFMU_FMI_1)
if(CPPFMU_FMI_1)
add_executable(cs_test
"tests/cs_test.cpp"
"tests/cs_slave.cpp"
Expand All @@ -39,14 +57,22 @@ if(NOT CPPFMU_FMI_1)
target_compile_features(cs_test PRIVATE cxx_std_11)
target_link_libraries(cs_test PRIVATE cppfmu)
add_test(NAME "cs_test" COMMAND cs_test)
elseif(CPPFMU_FMI_3)
add_executable(cs_test
"tests/cs_test_fmi3.cpp"
"tests/cs_slave_fmi3.cpp"
"fmi3_functions.cpp"
)
target_compile_features(cs_test PRIVATE cxx_std_11)
target_link_libraries(cs_test PRIVATE cppfmu)
add_test(NAME "cs_test" COMMAND cs_test)
else()
add_executable(cs_test_fmi1
add_executable(cs_test
"tests/cs_test.cpp"
"tests/cs_slave.cpp"
"fmi_functions.cpp"
)
target_compile_features(cs_test_fmi1 PRIVATE cxx_std_11)
target_link_libraries(cs_test_fmi1 PRIVATE cppfmu)
target_compile_definitions(cs_test_fmi1 PRIVATE CPPFMU_USE_FMI_1_0 "MODEL_IDENTIFIER=")
add_test(NAME "cs_test_fmi1" COMMAND cs_test_fmi1)
target_compile_features(cs_test PRIVATE cxx_std_11)
target_link_libraries(cs_test PRIVATE cppfmu)
add_test(NAME "cs_test" COMMAND cs_test)
endif()
118 changes: 105 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,72 @@ CPPFMU was developed as part of the R&D project [Virtual Prototyping
of Maritime Systems and Operations](http://viproma.no) (ViProMa), and
is currently maintained by [SINTEF Ocean](http://www.sintef.no/en/ocean/).

Supported FMI Versions
----------------------

| Version | Status | Header | Library Source | C API Wrapper |
|---------|--------|--------|----------------|---------------|
| FMI 1.0 | Stable | `cppfmu_cs.hpp` | `cppfmu_cs.cpp` | `fmi_functions.cpp` |
| FMI 2.0 | Stable | `cppfmu_cs.hpp` | `cppfmu_cs.cpp` | `fmi_functions.cpp` |
| FMI 3.0 | Partial | `cppfmu_cs_fmi3.hpp` | `cppfmu_cs_fmi3.cpp` | `fmi3_functions.cpp` |

### FMI 3.0 Support Status

FMI 3.0 co-simulation is supported. The `SlaveInstance3` class provides virtual
methods for all Co-Simulation API functions. Functions without a corresponding
virtual method are hardcoded stubs that always return `fmi3Error`.

**Fully implemented (virtual methods on `SlaveInstance3`):**
- All Get/Set type functions (Float32, Float64, Int8–64, UInt8–64, Boolean, String, Binary)
- Directional derivatives, adjoint derivatives, and output derivatives
- Variable dependencies and number of dependencies
- FMU state management (get/set/serialize/deserialize)
- Enhanced `DoStep` with early return, event handling, and termination output parameters
- Event Mode (`EnterEventMode`, `EvaluateDiscreteStates`, `UpdateDiscreteStates`, `EnterStepMode`)
- `GetNumberOfEventIndicators` / `GetNumberOfContinuousStates`
- Debug logging with categories

**Genuine stubs (no virtual method — cannot be overridden by users):**
- **Configuration Mode** — `fmi3EnterConfigurationMode` / `fmi3ExitConfigurationMode`
(optional feature for structural parameter tuning)
- **Clock interval/shift** — `Get/SetIntervalDecimal/Fraction`, `Get/SetShiftDecimal/Fraction`
(optional feature for variable-interval and shifted clocks)
- **Intermediate Update Callback** — parameter accepted during instantiation but ignored

**Note:** `GetClock` and `SetClock` have virtual methods on `SlaveInstance3` and can be
overridden by users; the default implementation throws `std::logic_error` for non-zero
variable references, consistent with other type-specific Get/Set methods.

### Why `SlaveInstance3` Instead of Extending `SlaveInstance`?

FMI 3.0 introduces fundamental changes that make extending the existing
`SlaveInstance` class impractical:

1. **Different type system** — FMI 3.0 replaces `fmi2Real`/`fmi2Integer` with
typed variants (Float32, Float64, Int8–64, UInt8–64, Binary, Clock) and
adds an `nValues` parameter to every Get/Set function for array-aware access.

2. **Different memory model** — FMI 3.0 removes the FMI memory allocator
callbacks entirely, requiring standard C++ `new`/`delete` instead of the
`cppfmu::Memory`/`cppfmu::Allocator` infrastructure used by FMI 1.0/2.0.

3. **Different logger signature** — FMI 3.0 replaces the varargs-based logger
with a fixed-signature callback, requiring a separate `Logger3` class.

4. **Different instantiation** — `fmi3InstantiateCoSimulation` has a different
parameter set (eventModeUsed, earlyReturnAllowed, intermediateUpdate callback)
that doesn't map to the existing `CppfmuInstantiateSlave` signature.

5. **Enhanced DoStep** — FMI 3.0's `DoStep` returns four additional output
parameters (`eventHandlingNeeded`, `terminateSimulation`, `earlyReturn`,
`lastSuccessfulTime`) that have no analogue in FMI 2.0.

Maintaining backward compatibility for FMI 1.0/2.0 users while accommodating
these changes would have required either extensive conditional compilation
within a single class or a proliferation of overloaded virtual methods.
A separate `SlaveInstance3` class keeps each FMI version's interface clean
and self-documenting.

Getting involved
----------------
If you have a question, a bug report or an enhancement request,
Expand Down Expand Up @@ -53,27 +119,27 @@ work as well):

We also provide a [Conan](https://conan.io) recipe. This recipe builds a static library
for CPPFMU that you can use in your code, but you still need to compile
`fmi_functions.cpp`. The package can be created with `conan create . --user sintef
--channel stable`. The recipe and some precompiled binaries are available on Sintef
Ocean's public artifactory], which can be added with `conan remote add sintef-public
https://gitlab.sintef.no/api/v4/projects/22218/packages/conan`. Note that when using the
conan recipe, FMI 1 or 2 is added as a dependency, so you do not need to fetch them
yourself. To use CPPFMU with conan, add the following lines to your `conanfile.py`
and `CMakeLists.txt`:
`fmi_functions.cpp` (or `fmi3_functions.cpp` for FMI 3.0). The package can be created
with `conan create . --user sintef --channel stable`. The recipe and some precompiled
binaries are available on Sintef Ocean's public artifactory, which can be added with
`conan remote add sintef-public https://gitlab.sintef.no/api/v4/projects/22218/packages/conan`.
Note that when using the conan recipe, FMI 1, 2, or 3 is added as a dependency, so you
do not need to fetch them yourself. To use CPPFMU with conan, add the following lines
Comment thread
joakimono marked this conversation as resolved.
to your `conanfile.py` and `CMakeLists.txt`, showing how to do it with FMI 3:

`conanfile.py`:
```python
...
def requirements(self):
self.requires("cppfmu/1.0@sintef/stable")
self.requires("cppfmu/1.0@sintef/stable", options={"use_fmi_version": 3})

def generate(self):
# Copy fmi_function.cpp to your binary directory
for require, dep in self.dependencies.items():
if require.build or require.test:
continue
if dep.ref.name == "cppfmu":
copy(self, "fmi_functions.cpp",
copy(self, "fmi3_functions.cpp", # or "fmi_function.cpp" for FMI 1 or 2
dep.cpp_info.srcdirs[0],
path.join(self.build_folder, dep.ref.name),
keep_path=False)
Expand All @@ -92,12 +158,16 @@ and `CMakeLists.txt`:
How it works
------------
It's simple: We have already implemented all the FMI C functions
for you in `fmi_functions.cpp`. These forward to the C++ functions
defined by you. They also ensure that exceptions are caught,
logged and turned into the appropriate error codes.
for you in `fmi_functions.cpp` (or `fmi3_functions.cpp` for FMI 3.0).
These forward to the C++ functions defined by you. They also ensure
that exceptions are caught, logged and turned into the appropriate
error codes.

Usage
-----

### FMI 1.0 and FMI 2.0

To implement a *co-simulation slave*, this is what you have to do:

1. Include the `cppfmu_cs.hpp` header in your sources.
Expand All @@ -113,6 +183,19 @@ To implement a *co-simulation slave*, this is what you have to do:
must define it with the exact same signature. You'll find more
information about this in the header too.

### FMI 3.0

1. Include the `cppfmu_cs_fmi3.hpp` header in your sources.

2. Create a class that publicly derives from `cppfmu::SlaveInstance3`,
and override its virtual member functions as required. Note that
all Get/Set methods include an `nValues` parameter for array-aware
access, and memory management uses standard C++ `new`/`delete`
(use `cppfmu::AllocateUnique3` instead of `cppfmu::AllocateUnique`).

3. Define the function `CppfmuInstantiateSlave()` with the FMI 3.0
signature (see `cppfmu_cs_fmi3.hpp` for details).

That's more or less it. Read on below to learn how to deal with errors,
memory management, and logging.

Expand All @@ -130,7 +213,8 @@ will be made to any of its member functions except the destructor.
The message associated with the exception will be logged using the
mechanism provided by the simulation environment (the `logger` callback
in the C API), and the currently executing FMI function will return an
error code. For most exception types this will be `fmiError`.
error code. For most exception types this will be `fmiError` (or
`fmi3Error` for FMI 3.0).

If the nature of the error is such that all other instances have also
become unusable, an exception of type `cppfmu::FatalError` (or a derived
Expand Down Expand Up @@ -180,6 +264,10 @@ higher-level C++ interface. These are:
with a custom deleter, and `cppfmu::AllocateUnique`, which
allocates and constructs an object managed by a `UniquePtr`.

**FMI 3.0** does not provide memory allocator callbacks. Use standard
C++ `new`/`delete` directly, or the convenience function
`cppfmu::AllocateUnique3` defined in `cppfmu_cs_fmi3.hpp`.

### Logging

FMI includes a logging mechanism which model/slave code can use to
Expand All @@ -193,6 +281,10 @@ must be passed on to any code that is to perform logging.

The `Logger` class is defined and documented in `cppfmu_common.hpp`.

**FMI 3.0** uses a different logger signature (no varargs, no instance
name parameter). A `Logger3` class is provided in `cppfmu_common.hpp`
for this purpose.

Licence
-------
CPPFMU is subject to the terms of the [Mozilla Public License, v.
Expand Down
18 changes: 11 additions & 7 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ class CppFmuConan(ConanFile):
name = "cppfmu"
license = "MPL-2.0"
author = "Lars T. Kyllingstad"
description = "C++ wrapper for FMI co-simulation, v1 and v2"
topics = ("Co-simulation")
description = "C++ wrapper for FMI co-simulation, v1, v2, and v3"
topics = ("Co-simulation",)
url = "https://github.com/viproma/cppfmu"
settings = "os", "compiler", "build_type", "arch"
package_type = "static-library"
options = {
"fPIC": [True, False],
"use_fmi_version": [1, 2]
"use_fmi_version": [1, 2, 3]
}
default_options = {
"fPIC": True,
Expand Down Expand Up @@ -66,6 +66,8 @@ def layout(self):
def requirements(self):
if self.options.use_fmi_version == 1:
self.requires("fmi1/1.0.1", transitive_headers=True)
elif self.options.use_fmi_version == 3:
self.requires("fmi3/3.0.2", transitive_headers=True)
else:
self.requires("fmi2/2.0.4", transitive_headers=True)

Expand All @@ -86,10 +88,8 @@ def source(self):

def generate(self):
tc = CMakeToolchain(self)
if self.options.use_fmi_version == 1:
tc.variables["CPPFMU_FMI_1"] = True
else:
tc.variables["CPPFMU_FMI_1"] = False
tc.variables["CPPFMU_FMI_1"] = self.options.use_fmi_version == 1
tc.variables["CPPFMU_FMI_3"] = self.options.use_fmi_version == 3
tc.generate()
deps = CMakeDeps(self)
deps.generate()
Expand All @@ -113,5 +113,9 @@ def package_info(self):
self.output.info("Define fmi1")
self.cpp_info.defines = ["CPPFMU_USE_FMI_1_0=1"]
self.cpp_info.requires = ["fmi1::cosim"]
elif self.options.use_fmi_version == 3:
self.output.info("Define fmi3")
self.cpp_info.defines = ["CPPFMU_USE_FMI_3_0=1"]
self.cpp_info.requires = ["fmi3::fmi3"]
else:
self.cpp_info.requires = ["fmi2::fmi2"]
Loading
Loading