Skip to content

Commit fc23820

Browse files
committed
Merge branch 'fmi2-directional-derivatives' into libcosim/fmi3-types-metadata
# Conflicts: # .github/workflows/ci-cmake.yml # include/cosim/fmi/v2/fmu.hpp # src/cosim/fmi/v2/fmu.cpp # tests/CMakeLists.txt # tests/fmi_v2_fmu_unittest.cpp
2 parents d5811d2 + fc1ec8d commit fc23820

19 files changed

Lines changed: 164 additions & 109 deletions

‎.github/workflows/ci-cmake.yml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ jobs:
3838
zipcmp \
3939
zipmerge \
4040
ziptool
41-
rm -rf /var/lib/apt/lists/*
4241
- uses: actions/checkout@v4
4342
- name: Install FMI Library and libcbor
4443
shell: bash

‎cmake/AddTestExecutable.cmake‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@
77
# SOURCES <sources...>
88
# DEPENDENCIES <targets...>
99
# DATA_DIR <directory>
10+
# REFERENCE_FMU_V2_DIR <directory>
11+
# GEN_TEST_DATA_DIR <directory>
1012
# )
1113
#
1214
# "target folder" is just a human-readable name for the IDE folder that the
1315
# target will be grouped under. If omitted, it will be a top-level target.
1416
function(add_test_executable name)
1517
# Parse argument list
16-
set(params "FOLDER" "SOURCES" "DEPENDENCIES" "DATA_DIR")
18+
set(params
19+
"FOLDER"
20+
"SOURCES"
21+
"DEPENDENCIES"
22+
"DATA_DIR"
23+
"REFERENCE_FMU_V2_DIR"
24+
"GEN_TEST_DATA_DIR")
1725
foreach(p IN LISTS params)
1826
set(arg_${p})
1927
endforeach()
@@ -37,7 +45,18 @@ function(add_test_executable name)
3745

3846
# Add test
3947
add_test(NAME "${name}" COMMAND "${name}")
48+
set(environment)
4049
if(arg_DATA_DIR)
41-
set_property(TEST "${name}" PROPERTY ENVIRONMENT "TEST_DATA_DIR=${arg_DATA_DIR}")
50+
list(APPEND environment "TEST_DATA_DIR=${arg_DATA_DIR}")
51+
endif()
52+
if(arg_REFERENCE_FMU_V2_DIR)
53+
list(APPEND environment
54+
"REFERENCE_FMU_V2_DIR=${arg_REFERENCE_FMU_V2_DIR}")
55+
endif()
56+
if(arg_GEN_TEST_DATA_DIR)
57+
list(APPEND environment "GEN_TEST_DATA_DIR=${arg_GEN_TEST_DATA_DIR}")
58+
endif()
59+
if(environment)
60+
set_property(TEST "${name}" PROPERTY ENVIRONMENT ${environment})
4261
endif()
4362
endfunction()

‎include/cosim/exception.hpp‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ enum class errc
5151
invalid_system_structure,
5252

5353
/// ZIP file error
54-
zip_error
54+
zip_error,
55+
56+
/// Invalid operation (e.g. calling a function at the invalid time in the simulation lifecycle)
57+
invalid_operation
5558
};
5659

5760

‎include/cosim/fmi/v2/fmu.hpp‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,6 @@ class slave_instance : public fmi::slave_instance
211211
struct saved_state
212212
{
213213
fmi2_FMU_state_t fmuState = nullptr;
214-
bool setupComplete = false;
215-
bool simStarted = false;
216214
lifecycle_state lifecycleState = lifecycle_state::instantiated;
217215
};
218216
void copy_current_state(saved_state& state);
@@ -222,8 +220,6 @@ class slave_instance : public fmi::slave_instance
222220
std::shared_ptr<v2::fmu> fmu_;
223221
fmi2_import_t* handle_;
224222

225-
bool setupComplete_ = false;
226-
bool simStarted_ = false;
227223
lifecycle_state lifecycleState_ = lifecycle_state::instantiated;
228224
bool providesDirectionalDerivatives_ = false;
229225
std::vector<value_reference> outputReferences_;

‎src/cosim/exception.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class my_error_category : public std::error_category
4444
return "Invalid system structure";
4545
case errc::zip_error:
4646
return "ZIP file error";
47+
case errc::invalid_operation:
48+
return "Invalid operation";
4749
default:
4850
COSIM_PANIC();
4951
}

‎src/cosim/fmi/v2/fmu.cpp‎

Lines changed: 30 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ slave_instance::slave_instance(
329329

330330
slave_instance::~slave_instance() noexcept
331331
{
332-
if (simStarted_) {
332+
if (lifecycleState_ == lifecycle_state::step) {
333333
fmi2_import_terminate(handle_);
334334
}
335335
fmi2_import_free_instance(handle_);
@@ -343,7 +343,7 @@ void slave_instance::setup(
343343
std::optional<time_point> stopTime,
344344
std::optional<double> relativeTolerance)
345345
{
346-
assert(!setupComplete_);
346+
assert(lifecycleState_ == lifecycle_state::instantiated);
347347
const auto rcs = fmi2_import_setup_experiment(
348348
handle_,
349349
relativeTolerance ? fmi2_true : fmi2_false,
@@ -364,31 +364,27 @@ void slave_instance::setup(
364364
last_log_record(instanceName_).message);
365365
}
366366

367-
setupComplete_ = true;
368367
lifecycleState_ = lifecycle_state::initialization;
369368
}
370369

371370

372371
void slave_instance::start_simulation()
373372
{
374-
assert(setupComplete_);
375-
assert(!simStarted_);
373+
assert(lifecycleState_ == lifecycle_state::initialization);
376374
const auto rc = fmi2_import_exit_initialization_mode(handle_);
377375
if (rc != fmi2_status_ok && rc != fmi2_status_warning) {
378376
throw error(
379377
make_error_code(errc::model_error),
380378
last_log_record(instanceName_).message);
381379
}
382-
simStarted_ = true;
383380
lifecycleState_ = lifecycle_state::step;
384381
}
385382

386383

387384
void slave_instance::end_simulation()
388385
{
389-
assert(simStarted_);
386+
assert(lifecycleState_ == lifecycle_state::step);
390387
const auto rc = fmi2_import_terminate(handle_);
391-
simStarted_ = false;
392388
if (rc != fmi2_status_ok && rc != fmi2_status_warning) {
393389
throw error(
394390
make_error_code(errc::model_error),
@@ -400,7 +396,7 @@ void slave_instance::end_simulation()
400396

401397
step_result slave_instance::do_step(time_point currentT, duration deltaT)
402398
{
403-
assert(simStarted_);
399+
assert(lifecycleState_ == lifecycle_state::step);
404400
const auto rc = fmi2_import_do_step(
405401
handle_,
406402
to_double_time_point(currentT),
@@ -607,8 +603,6 @@ void slave_instance::restore_state(state_index stateIndex)
607603
make_error_code(errc::model_error),
608604
last_log_record(instanceName_).message);
609605
}
610-
setupComplete_ = state.setupComplete;
611-
simStarted_ = state.simStarted;
612606
lifecycleState_ = state.lifecycleState;
613607
}
614608

@@ -672,8 +666,6 @@ serialization::node slave_instance::export_state(state_index stateIndex) const
672666
exportedState.put("scheme_version", export_scheme_version);
673667
exportedState.put("fmu_uuid", fmu_->model_description()->uuid);
674668
exportedState.put("serialized_fmu_state", serializedFMUState);
675-
exportedState.put("setup_complete", savedState.setupComplete);
676-
exportedState.put("simulation_started", savedState.simStarted);
677669
exportedState.put(
678670
"lifecycle_state",
679671
static_cast<std::int32_t>(savedState.lifecycleState));
@@ -715,56 +707,26 @@ slave::state_index slave_instance::import_state(
715707
}
716708

717709
// Validate wrapper state before asking the FMU to allocate a state.
718-
savedState.setupComplete = exportedState.get<bool>("setup_complete");
719-
savedState.simStarted = exportedState.get<bool>("simulation_started");
720-
if (schemeVersion == 0) {
721-
if (savedState.setupComplete && !savedState.simStarted) {
710+
const auto serializedLifecycleState =
711+
exportedState.get<std::int32_t>("lifecycle_state");
712+
switch (serializedLifecycleState) {
713+
case static_cast<std::int32_t>(lifecycle_state::instantiated):
714+
savedState.lifecycleState = lifecycle_state::instantiated;
715+
break;
716+
case static_cast<std::int32_t>(lifecycle_state::initialization):
717+
savedState.lifecycleState = lifecycle_state::initialization;
718+
break;
719+
case static_cast<std::int32_t>(lifecycle_state::step):
720+
savedState.lifecycleState = lifecycle_state::step;
721+
break;
722+
case static_cast<std::int32_t>(lifecycle_state::terminated):
723+
savedState.lifecycleState = lifecycle_state::terminated;
724+
break;
725+
default:
722726
throw error(
723727
make_error_code(errc::bad_file),
724728
"The serialized state of subsimulator '" + instanceName_
725-
+ "' uses scheme 0 lifecycle data that is ambiguous "
726-
"between Initialization Mode and Terminated");
727-
}
728-
savedState.lifecycleState = savedState.simStarted
729-
? lifecycle_state::step
730-
: lifecycle_state::instantiated;
731-
} else {
732-
const auto serializedLifecycleState =
733-
exportedState.get<std::int32_t>("lifecycle_state");
734-
switch (serializedLifecycleState) {
735-
case static_cast<std::int32_t>(lifecycle_state::instantiated):
736-
savedState.lifecycleState = lifecycle_state::instantiated;
737-
break;
738-
case static_cast<std::int32_t>(lifecycle_state::initialization):
739-
savedState.lifecycleState = lifecycle_state::initialization;
740-
break;
741-
case static_cast<std::int32_t>(lifecycle_state::step):
742-
savedState.lifecycleState = lifecycle_state::step;
743-
break;
744-
case static_cast<std::int32_t>(lifecycle_state::terminated):
745-
savedState.lifecycleState = lifecycle_state::terminated;
746-
break;
747-
default:
748-
throw error(
749-
make_error_code(errc::bad_file),
750-
"The serialized state of subsimulator '" + instanceName_
751-
+ "' contains an invalid lifecycle state");
752-
}
753-
const bool lifecycleStateConsistent =
754-
(savedState.lifecycleState == lifecycle_state::instantiated &&
755-
!savedState.setupComplete && !savedState.simStarted) ||
756-
(savedState.lifecycleState == lifecycle_state::initialization &&
757-
savedState.setupComplete && !savedState.simStarted) ||
758-
(savedState.lifecycleState == lifecycle_state::step &&
759-
savedState.setupComplete && savedState.simStarted) ||
760-
(savedState.lifecycleState == lifecycle_state::terminated &&
761-
savedState.setupComplete && !savedState.simStarted);
762-
if (!lifecycleStateConsistent) {
763-
throw error(
764-
make_error_code(errc::bad_file),
765-
"The serialized state of subsimulator '" + instanceName_
766-
+ "' contains inconsistent lifecycle data");
767-
}
729+
+ "' contains an invalid lifecycle state");
768730
}
769731

770732
const auto& serializedFMUState = std::get<std::vector<std::byte>>(
@@ -829,9 +791,12 @@ void slave_instance::get_directional_derivative(
829791
lifecycleState_ != lifecycle_state::step &&
830792
lifecycleState_ != lifecycle_state::terminated) {
831793
throw error(
832-
make_error_code(errc::model_error),
794+
make_error_code(errc::invalid_operation),
833795
instanceName_ +
834-
": invalid lifecycle operation: GetDirectionalDerivative");
796+
": GetDirectionalDerivative can only be called in lifecycle states "
797+
"initialization (1), step (2), or terminated (3); current lifecycle "
798+
"state: " +
799+
std::to_string(static_cast<std::int32_t>(lifecycleState_)));
835800
}
836801
COSIM_INPUT_CHECK(!unknowns.empty());
837802
COSIM_INPUT_CHECK(!knowns.empty());
@@ -844,9 +809,6 @@ void slave_instance::get_directional_derivative(
844809
references.end(),
845810
reference) != references.end();
846811
};
847-
const bool initializing =
848-
lifecycleState_ == lifecycle_state::initialization;
849-
850812
for (const auto reference : unknowns) {
851813
const auto variable = fmi2_import_get_variable_by_vr(
852814
handle_, fmi2_base_type_real, reference);
@@ -857,7 +819,8 @@ void slave_instance::get_directional_derivative(
857819
contains(outputReferences_, reference) &&
858820
(variability == fmi2_variability_enu_continuous ||
859821
variability == fmi2_variability_enu_discrete);
860-
const bool validUnknown = initializing
822+
const bool validUnknown =
823+
lifecycleState_ == lifecycle_state::initialization
861824
? contains(initialUnknownReferences_, reference)
862825
: eligibleOutput ||
863826
contains(derivativeReferences_, reference);
@@ -879,7 +842,7 @@ void slave_instance::get_directional_derivative(
879842
validKnown =
880843
causality == fmi2_causality_enu_input ||
881844
causality == fmi2_causality_enu_independent;
882-
if (initializing) {
845+
if (lifecycleState_ == lifecycle_state::initialization) {
883846
validKnown =
884847
validKnown ||
885848
fmi2_import_get_initial(variable) ==
@@ -940,8 +903,6 @@ void slave_instance::copy_current_state(saved_state& state)
940903
make_error_code(errc::model_error),
941904
last_log_record(instanceName_).message);
942905
}
943-
state.setupComplete = setupComplete_;
944-
state.simStarted = simStarted_;
945906
state.lifecycleState = lifecycleState_;
946907
}
947908

‎src/cosim/observer/file_observer.cpp‎

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -568,8 +568,16 @@ file_observer_config file_observer_config::parse(const filesystem::path& configP
568568
boost::property_tree::read_xml(configPath.string(), ptree,
569569
boost::property_tree::xml_parser::no_comments | boost::property_tree::xml_parser::trim_whitespace);
570570

571+
const auto& simulators = ptree.get_child("simulators");
572+
571573
file_observer_config config;
572-
for (const auto& simulator : ptree.get_child("simulators")) {
574+
if (const auto timestamps = get_optional_attribute<bool>(simulators, "timestampedFilenames")) {
575+
config.set_timestamped_filenames(*timestamps);
576+
}
577+
if (const auto precision = get_optional_attribute<int>(simulators, "floatingPointPrecision")) {
578+
config.fixed_precision(*precision);
579+
}
580+
for (const auto& simulator : simulators) {
573581
if (simulator.first == "simulator") {
574582
const auto modelName = get_attribute<std::string>(simulator.second, "name");
575583
const auto decimationFactor = get_optional_attribute<size_t>(simulator.second, "decimationFactor");
@@ -583,15 +591,6 @@ file_observer_config file_observer_config::parse(const filesystem::path& configP
583591
config.log_simulator_variables(modelName, variableNames, decimationFactor);
584592
}
585593
}
586-
if (const auto configuration = ptree.get_child_optional("configuration")) {
587-
if (const auto timestamps = get_optional_attribute<bool>(*configuration, "timestampedFilenames")) {
588-
config.set_timestamped_filenames(*timestamps);
589-
}
590-
if (const auto precision = get_optional_attribute<int>(*configuration, "floatingPointPrecision")) {
591-
config.fixed_precision(*precision);
592-
}
593-
}
594-
595594
return config;
596595
}
597596

‎tests/CMakeLists.txt‎

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ ExternalProject_Add(
4949
INSTALL_COMMAND ""
5050
TEST_COMMAND "")
5151

52+
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
53+
54+
if(NOT DEFINED GEN_TEST_DATA_DIR)
55+
set(GEN_TEST_DATA_DIR
56+
"${CMAKE_CURRENT_BINARY_DIR}/generated-test-data")
57+
endif()
58+
include(PrepareDahlquistTestData)
59+
prepare_dahlquist_test_data()
60+
5261
if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 8)
5362
set(FMI2_TEST_PLATFORM "win64")
5463
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND
@@ -109,7 +118,7 @@ add_custom_target(
109118
fmi2_derivative_fixture
110119
DEPENDS "${FMI2_DERIVATIVE_FIXTURE}")
111120

112-
set(tests
121+
set(tests
113122
"ecco_algorithm_from_system_structure_test"
114123
"ecco_algorithm_multi_bond_test"
115124
"ecco_algorithm_test"
@@ -167,6 +176,8 @@ foreach(test IN LISTS tests)
167176
SOURCES "${test}.cpp"
168177
DEPENDENCIES "cosim"
169178
DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data"
179+
REFERENCE_FMU_V2_DIR "${REFERENCE_FMUS_V2_BINARY_DIR}/fmus"
180+
GEN_TEST_DATA_DIR "${GEN_TEST_DATA_DIR}"
170181
)
171182
endforeach()
172183

@@ -183,15 +194,23 @@ foreach(test IN LISTS unittests)
183194
SOURCES "${test}.cpp"
184195
DEPENDENCIES ${dependencies}
185196
DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data"
197+
REFERENCE_FMU_V2_DIR "${REFERENCE_FMUS_V2_BINARY_DIR}/fmus"
198+
GEN_TEST_DATA_DIR "${GEN_TEST_DATA_DIR}"
186199
)
187200
target_include_directories("cpp_${test}" PRIVATE "$<TARGET_PROPERTY:cosim,SOURCE_DIR>")
188201
endforeach()
189202

190203
add_dependencies(
191204
cpp_fmi_v2_fmu_unittest
192205
fmi2_derivative_fixture)
193-
set_property(
194-
TEST cpp_fmi_v2_fmu_unittest
195-
APPEND
196-
PROPERTY ENVIRONMENT
197-
"REFERENCE_FMU_V2_DIR=${REFERENCE_FMUS_V2_BINARY_DIR}/fmus")
206+
207+
set(reference_fmu_test_targets
208+
cpp_save_state_test
209+
cpp_slave_simulator_unittest)
210+
if(LIBCOSIM_WITH_PROXYFMU)
211+
list(APPEND reference_fmu_test_targets cpp_proxyfmu_save_state_test)
212+
endif()
213+
214+
foreach(reference_fmu_test IN LISTS reference_fmu_test_targets)
215+
add_dependencies("${reference_fmu_test}" reference_fmus_v2_build)
216+
endforeach()

0 commit comments

Comments
 (0)