Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run fmi2SetXXX function to initialize FMU start values #778

Merged
merged 16 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
5 changes: 5 additions & 0 deletions include/cosim/algorithm/simulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ class simulator : public manipulable
*/
virtual void start_simulation() = 0;

/**
* Runs fmiXSetINI (4.2.4) functions to initialize variables' start values.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't made reference to the FMI spec anywhere else in this interface, nor in any other of our general interfaces, because they are supposed to be agnostic with respect to the underlying binary interface. All FMI-specific stuff is hidden away in the cosim::fmi module. (I admit that we're still leaking FMI details like a sieve, but we haven't been this explicit about it before.) I suggest writing a more general and extensive description of what the function is supposed to do instead.

Also, as commented elsewhere, the functions are generally in call order, so this should go before setup().

*/
virtual void initialize_start_values() = 0;

/**
* Performs a single time step.
*
Expand Down
11 changes: 11 additions & 0 deletions src/cosim/algorithm/fixed_step_algorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,16 @@ class fixed_step_algorithm::impl

void initialize()
{
// FMU's start values are set in "instantiated" state (4.2.4)
// Some of the FMUs do not see start values if this is not
// done before fmiXEnterInitializationMode
for (auto& s : simulators_) {
pool_.submit([&] {
s.second.sim->initialize_start_values();
});
}
pool_.wait_for_tasks_to_finish();

for (auto& s : simulators_) {
pool_.submit([&] {
s.second.sim->setup(startTime_, stopTime_, std::nullopt);
Expand All @@ -171,6 +181,7 @@ class fixed_step_algorithm::impl
calculate_and_transfer();
}


for (auto& s : simulators_) {
pool_.submit([&] {
s.second.sim->start_simulation();
Expand Down
63 changes: 62 additions & 1 deletion src/cosim/slave_simulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <sstream>
#include <stdexcept>
#include <unordered_map>
#include <iostream>
#include <cosim/utility/utility.hpp>


namespace cosim
Expand Down Expand Up @@ -216,7 +218,9 @@ class set_variable_cache
}
}

std::pair<gsl::span<value_reference>, gsl::span<const T>> modify_and_get(duration deltaT)
std::pair<gsl::span<value_reference>, gsl::span<const T>> modify_and_get(
duration deltaT,
std::optional<std::function<bool(const value_reference&, const T&)>> filter = std::nullopt)
{
if (!hasRunModifiers_) {
for (const auto& entry : modifiers_) {
Expand All @@ -233,6 +237,24 @@ class set_variable_cache
assert(references_.size() == values_.size());
hasRunModifiers_ = true;
}

if (filter) {
references_filtered_.clear();
values_filtered_.clear();

for (size_t i = 0; i < references_.size(); i++) {
auto& ref = references_.at(i);
auto& value = values_.at(i);

if ((*filter)(ref, value)) {
references_filtered_.push_back(ref);
values_filtered_.push_back(value);
}
}

return std::pair(gsl::make_span(references_filtered_), gsl::make_span(values_filtered_));
}

return std::pair(gsl::make_span(references_), gsl::make_span(values_));
}

Expand All @@ -243,6 +265,8 @@ class set_variable_cache
}
references_.clear();
values_.clear();
references_filtered_.clear();
values_filtered_.clear();
hasRunModifiers_ = false;
}

Expand All @@ -268,6 +292,10 @@ class set_variable_cache
// The references and values of the variables that will be set next.
std::vector<value_reference> references_;
boost::container::vector<T> values_;

// Filtered references and values to be set next (if a filter is applied).
std::vector<value_reference> references_filtered_;
boost::container::vector<T> values_filtered_;
};


Expand Down Expand Up @@ -518,6 +546,34 @@ class slave_simulator::impl
return result;
}

void initialize_start_values()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions are generally listed in the order they're supposed to be called, so this should come before setup().

{
auto deltaT = duration::zero();
auto filter = [this](const variable_type& vt) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variable_type and value_reference are just integers in disguise, and should therefore be passed by value rather than by reference.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we have the scalar_value alias which you can use instead of that variant.

return [this, &vt](const value_reference& vr, const std::variant<double, int, bool, std::string>&) {
const auto& vd = this->find_variable_description(vr, vt);
/// FMI Specification 2.0.4 - Section 4.2.4
return vd.variability != variable_variability::constant &&
vd.causality != variable_causality::input;
};
};

const auto [realRefs, realValues] = realSetCache_.modify_and_get(deltaT, filter(variable_type::real));
const auto [integerRefs, integerValues] = integerSetCache_.modify_and_get(deltaT, filter(variable_type::integer));
const auto [booleanRefs, booleanValues] = booleanSetCache_.modify_and_get(deltaT, filter(variable_type::boolean));
const auto [stringRefs, stringValues] = stringSetCache_.modify_and_get(deltaT, filter(variable_type::string));

slave_->set_variables(
gsl::make_span(realRefs),
gsl::make_span(realValues),
gsl::make_span(integerRefs),
gsl::make_span(integerValues),
gsl::make_span(booleanRefs),
gsl::make_span(booleanValues),
gsl::make_span(stringRefs),
gsl::make_span(stringValues));
}

private:
void set_variables(duration deltaT)
{
Expand Down Expand Up @@ -803,4 +859,9 @@ step_result slave_simulator::do_step(
}


void slave_simulator::initialize_start_values()
{
return pimpl_->initialize_start_values();
}

} // namespace cosim
2 changes: 2 additions & 0 deletions src/cosim/slave_simulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class slave_simulator : public simulator

void do_iteration() override;

void initialize_start_values() override;

void start_simulation() override;

step_result do_step(
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ set(tests
"scenario_manager_test"
"synchronized_xy_series_test"
"config_end_time_test"
"state_init_test"
)

set(unittests
Expand Down
Binary file added tests/data/fmi2/StateInitExample.fmu
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding an FMU, you should update tests/data/fmi2/README.md and possibly include a licence file, cf. #764.

Binary file not shown.
17 changes: 17 additions & 0 deletions tests/data/msmi/OspSystemStructure_StateInitExample.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<OspSystemStructure xmlns="http://opensimulationplatform.com/MSMI/OSPSystemStructure" version="0.1">
<StartTime>0.0</StartTime>
<BaseStepSize>0.01</BaseStepSize>
<Algorithm>fixedStep</Algorithm>
<Simulators>
<Simulator name="example" source="../fmi2/StateInitExample.fmu">
<InitialValues>
<InitialValue variable="Parameters.Integrator1_x0">
<Real value="10.0" />
</InitialValue>
</InitialValues>
</Simulator>
</Simulators>
<Connections>
</Connections>
</OspSystemStructure>
53 changes: 53 additions & 0 deletions tests/state_init_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <cosim/algorithm/fixed_step_algorithm.hpp>
#include <cosim/osp_config_parser.hpp>
#include <cosim/observer/file_observer.hpp>
#include <cosim/exception.hpp>
#include <cosim/execution.hpp>
#include <cosim/function/linear_transformation.hpp>
#include <cosim/observer/last_value_observer.hpp>
#include <cosim/system_structure.hpp>
#include <algorithm>
#include <iostream>

#define REQUIRE(test) \
if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test)

int main()
{
try {
const auto testDataDir = std::getenv("TEST_DATA_DIR");
REQUIRE(!!testDataDir);

cosim::filesystem::path configPath = testDataDir;

auto resolver = cosim::default_model_uri_resolver();
const auto config = cosim::load_osp_config(configPath / "msmi" / "OspSystemStructure_StateInitExample.xml", *resolver);

auto execution = cosim::execution(
config.start_time,
std::make_shared<cosim::fixed_step_algorithm>(config.step_size));

const auto entityMaps = cosim::inject_system_structure(
execution, config.system_structure, config.initial_values);
auto lvObserver = std::make_shared<cosim::last_value_observer>();

execution.add_observer(lvObserver);
execution.simulate_until(cosim::to_time_point(0.1));

auto sim = entityMaps.simulators.at("example");
const auto paramRef = config.system_structure.get_variable_description({"example", "Parameters.Integrator1_x0"}).reference;
const auto outRef = config.system_structure.get_variable_description({"example", "Integrator_out1"}).reference;

double initialValue = 0.0;
double outputValue = 0.0;
lvObserver->get_real(sim, gsl::make_span(&paramRef, 1), gsl::make_span(&initialValue, 1));
lvObserver->get_real(sim, gsl::make_span(&outRef, 1), gsl::make_span(&outputValue, 1));

REQUIRE(std::fabs(initialValue - 10.0) < 1.0e-9);
REQUIRE(std::fabs(outputValue - 10.1) < 1.0e-9);

} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
}