diff --git a/include/cse.h b/include/cse.h index f942cfa58..b386a77b6 100644 --- a/include/cse.h +++ b/include/cse.h @@ -144,16 +144,46 @@ cse_execution* cse_execution_create( * * \param [in] sspDir * Path to the directory holding SystemStructure.ssd + * \param [in] startTimeDefined + * Defines whether or not the following startTime variable should be ignored or not. * \param [in] startTime * The (logical) time point at which the simulation should start. + * If startTimeDefined=false, this variable will be ignored and a default value will be used. * \returns * A pointer to an object which holds the execution state, * or NULL on error. */ cse_execution* cse_ssp_execution_create( const char* sspDir, + bool startTimeDefined, cse_time_point startTime); +/** + * Creates a new execution based on a SystemStructure.ssd file. + * + * \param [in] sspDir + * Path to the directory holding SystemStructure.ssd + * \param [in] startTimeDefined + * Defines whether or not the following startTime variable should be ignored or not. + * \param [in] startTime + * The (logical) time point at which the simulation should start. + * If startTimeDefined=false, this variable will be ignored and a default value will be used. + * \param [in] stepSizeDefined + * Defines whether or not the following stepSize variable should be ignored or not. + * Must evaluate to `true` when loaded SSP does not contain a osp:FixedStepMaster annotation providing a default step size. + * \param [in] stepSize + * If stepSizeDefined=true, this value will be used by the (fixed-step) co-simulation algorithm. + * \returns + * A pointer to an object which holds the execution state, + * or NULL on error. + */ +cse_execution* cse_ssp_fixed_step_execution_create( + const char* sspDir, + bool startTimeDefined, + cse_time_point startTime, + bool stepSizeDefined, + cse_duration stepSize); + /** * Destroys an execution. * diff --git a/include/cse/ssp_parser.hpp b/include/cse/ssp_parser.hpp index b246b35c9..0d64a164f 100644 --- a/include/cse/ssp_parser.hpp +++ b/include/cse/ssp_parser.hpp @@ -26,7 +26,17 @@ struct simulator_map_entry using simulator_map = std::map; -std::pair load_ssp(cse::model_uri_resolver&, const boost::filesystem::path& sspDir, std::optional overrideStartTime = {}); +std::pair load_ssp( + cse::model_uri_resolver&, + const boost::filesystem::path& sspDir, + std::optional overrideStartTime = std::nullopt); + + +std::pair load_ssp( + cse::model_uri_resolver&, + const boost::filesystem::path& sspDir, + std::shared_ptr overrideAlgorithm = nullptr, + std::optional overrideStartTime = std::nullopt); } // namespace cse diff --git a/src/c/cse.cpp b/src/c/cse.cpp index 4cd792baa..87a475496 100644 --- a/src/c/cse.cpp +++ b/src/c/cse.cpp @@ -157,13 +157,30 @@ cse_execution* cse_execution_create(cse_time_point startTime, cse_duration stepS } } -cse_execution* cse_ssp_execution_create(const char* sspDir, cse_time_point startTime) +cse_execution* cse_ssp_execution_create( + const char* sspDir, + bool startTimeDefined, + cse_time_point startTime) +{ + return cse_ssp_fixed_step_execution_create(sspDir, startTimeDefined, startTime, false, 0); +} + +cse_execution* cse_ssp_fixed_step_execution_create( + const char* sspDir, + bool startTimeDefined, + cse_time_point startTime, + bool stepSizeDefined, + cse_duration stepSize) { try { auto execution = std::make_unique(); auto resolver = cse::default_model_uri_resolver(); - auto sim = cse::load_ssp(*resolver, sspDir, to_time_point(startTime)); + auto sim = cse::load_ssp( + *resolver, + sspDir, + stepSizeDefined ? std::make_unique(to_duration(stepSize)) : nullptr, + startTimeDefined ? std::optional(to_time_point(startTime)) : std::nullopt); execution->cpp_execution = std::make_unique(std::move(sim.first)); execution->simulators = std::move(sim.second); diff --git a/src/cpp/ssp_parser.cpp b/src/cpp/ssp_parser.cpp index 98fa41def..acb362d9d 100644 --- a/src/cpp/ssp_parser.cpp +++ b/src/cpp/ssp_parser.cpp @@ -52,6 +52,7 @@ class ssp_parser double stepSize; }; + bool has_simulation_information() const; const SimulationInformation& get_simulation_information() const; struct SystemDescription @@ -97,6 +98,8 @@ class ssp_parser const std::vector& get_connections() const; private: + bool hasSimulationInformation_; + boost::filesystem::path xmlPath_; boost::property_tree::ptree pt_; @@ -108,7 +111,7 @@ class ssp_parser }; ssp_parser::ssp_parser(const boost::filesystem::path& xmlPath) - : xmlPath_(xmlPath) + : hasSimulationInformation_(false), xmlPath_(xmlPath) { // Root node std::string path = "ssd:SystemStructureDescription"; @@ -136,6 +139,7 @@ ssp_parser::ssp_parser(const boost::filesystem::path& xmlPath) const auto& annotationType = get_attribute(annotation.second, "type"); if (annotationType == "org.open-simulation-platform") { for (const auto& infos : annotation.second.get_child("osp:SimulationInformation")) { + hasSimulationInformation_ = true; if (infos.first == "osp:FixedStepMaster") { simulationInformation_.description = get_attribute(infos.second, "description"); simulationInformation_.stepSize = get_attribute(infos.second, "stepSize"); @@ -203,6 +207,11 @@ ssp_parser::ssp_parser(const boost::filesystem::path& xmlPath) ssp_parser::~ssp_parser() noexcept = default; +bool ssp_parser::has_simulation_information() const +{ + return hasSimulationInformation_; +} + const ssp_parser::SimulationInformation& ssp_parser::get_simulation_information() const { return simulationInformation_; @@ -252,6 +261,11 @@ std::ostream& operator<<(std::ostream& os, streamer> sv) return os; } +cse::time_point get_default_start_time(const ssp_parser& parser) +{ + return cse::to_time_point(parser.get_default_experiment().startTime); +} + cse::variable_id get_variable( const std::map& slaves, const std::string& element, @@ -281,22 +295,36 @@ std::pair load_ssp( cse::model_uri_resolver& resolver, const boost::filesystem::path& sspDir, std::optional overrideStartTime) +{ + return load_ssp(resolver, sspDir, nullptr, overrideStartTime); +} + +std::pair load_ssp( + cse::model_uri_resolver& resolver, + const boost::filesystem::path& sspDir, + std::shared_ptr overrideAlgorithm, + std::optional overrideStartTime) { simulator_map simulatorMap; const auto ssdPath = boost::filesystem::absolute(sspDir) / "SystemStructure.ssd"; const auto baseURI = path_to_file_uri(ssdPath); const auto parser = ssp_parser(ssdPath); - const auto& simInfo = parser.get_simulation_information(); - const cse::duration stepSize = cse::to_duration(simInfo.stepSize); - - auto elements = parser.get_elements(); + std::shared_ptr algorithm; + if (overrideAlgorithm != nullptr) { + algorithm = overrideAlgorithm; + } else if (parser.has_simulation_information()) { + const auto& simInfo = parser.get_simulation_information(); + const cse::duration stepSize = cse::to_duration(simInfo.stepSize); + algorithm = std::move(std::make_unique(stepSize)); + } else { + CSE_PANIC_M("No co-simulation algorithm specified!"); + } - const auto startTime = overrideStartTime ? *overrideStartTime : cse::to_time_point(parser.get_default_experiment().startTime); + const auto startTime = overrideStartTime ? *overrideStartTime : get_default_start_time(parser); + auto execution = cse::execution(startTime, algorithm); - auto execution = cse::execution( - startTime, - std::make_unique(stepSize)); + auto elements = parser.get_elements(); std::map slaves; for (const auto& component : elements) { diff --git a/test/c/CMakeLists.txt b/test/c/CMakeLists.txt index fe1e7552e..62a530a76 100644 --- a/test/c/CMakeLists.txt +++ b/test/c/CMakeLists.txt @@ -8,6 +8,7 @@ set(tests "real_time_test" "connections_test" "execution_from_ssp_test" + "execution_from_ssp_custom_algo_test" "time_series_observer_test" "variable_metadata_test" ) diff --git a/test/c/execution_from_ssp_custom_algo_test.c b/test/c/execution_from_ssp_custom_algo_test.c new file mode 100644 index 000000000..c1b067b03 --- /dev/null +++ b/test/c/execution_from_ssp_custom_algo_test.c @@ -0,0 +1,102 @@ +#include + +#include +#include +#include +#include + +#ifdef _WINDOWS +# include +#else +# include +# define Sleep(x) usleep((x)*1000) +#endif + +void print_last_error() +{ + fprintf( + stderr, + "Error code %d: %s\n", + cse_last_error_code(), cse_last_error_message()); +} + +int main() +{ + cse_log_setup_simple_console_logging(); + cse_log_set_output_level(CSE_LOG_SEVERITY_INFO); + + int exitCode = 0; + cse_execution* execution = NULL; + cse_observer* observer = NULL; + + const char* dataDir = getenv("TEST_DATA_DIR"); + if (!dataDir) { + fprintf(stderr, "Environment variable TEST_DATA_DIR not set\n"); + goto Lfailure; + } + + char sspDir[1024]; + int rc = snprintf(sspDir, sizeof sspDir, "%s/ssp/demo/no_algorithm_element", dataDir); + if (rc < 0) { + perror(NULL); + goto Lfailure; + } + + int64_t nanoStepSize = (int64_t)(0.1 * 1.0e9); + execution = cse_ssp_fixed_step_execution_create(sspDir, true, 0, true, nanoStepSize); // override ssp startTime + if (!execution) { goto Lerror; } + + cse_execution_status status; + cse_execution_get_status(execution, &status); + + if (status.current_time != 0.0) { + fprintf(stderr, "Expected value 0.0, got %f\n", (double)(status.current_time / 1.0e9)); + goto Lfailure; + } + + observer = cse_last_value_observer_create(); + if (!observer) { goto Lerror; } + cse_execution_add_observer(execution, observer); + + rc = cse_execution_step(execution, 3); + if (rc < 0) { goto Lerror; } + + size_t numSlaves = cse_execution_get_num_slaves(execution); + + cse_slave_info infos[2]; + rc = cse_execution_get_slave_infos(execution, &infos[0], numSlaves); + if (rc < 0) { goto Lerror; } + + for (size_t i = 0; i < numSlaves; i++) { + if (0 == strncmp(infos[i].name, "KnuckleBoomCrane", SLAVE_NAME_MAX_SIZE)) { + double value = -1; + cse_slave_index slaveIndex = infos[i].index; + cse_value_reference varIndex = 2; + rc = cse_observer_slave_get_real(observer, slaveIndex, &varIndex, 1, &value); + if (rc < 0) { + goto Lerror; + } + if (value != 0.05) { + fprintf(stderr, "Expected value 0.05, got %f\n", value); + goto Lfailure; + } + } + } + + cse_execution_start(execution); + Sleep(100); + cse_execution_stop(execution); + + goto Lcleanup; + +Lerror: + print_last_error(); + +Lfailure: + exitCode = 1; + +Lcleanup: + cse_observer_destroy(observer); + cse_execution_destroy(execution); + return exitCode; +} diff --git a/test/c/execution_from_ssp_test.c b/test/c/execution_from_ssp_test.c index 134d510f0..419ff7378 100644 --- a/test/c/execution_from_ssp_test.c +++ b/test/c/execution_from_ssp_test.c @@ -42,7 +42,7 @@ int main() goto Lfailure; } - execution = cse_ssp_execution_create(sspDir, 0); + execution = cse_ssp_execution_create(sspDir, false, 0); if (!execution) { goto Lerror; } observer = cse_last_value_observer_create(); diff --git a/test/c/load_config_and_teardown_test.c b/test/c/load_config_and_teardown_test.c index bea376a16..621d096bb 100644 --- a/test/c/load_config_and_teardown_test.c +++ b/test/c/load_config_and_teardown_test.c @@ -38,7 +38,7 @@ int main() return 1; } - cse_execution* execution = cse_ssp_execution_create(sspDir, 0); + cse_execution* execution = cse_ssp_execution_create(sspDir, false, 0); if (!execution) { print_last_error(); return 1; diff --git a/test/cpp/CMakeLists.txt b/test/cpp/CMakeLists.txt index faeeb762c..bb1905d24 100644 --- a/test/cpp/CMakeLists.txt +++ b/test/cpp/CMakeLists.txt @@ -7,6 +7,7 @@ set(tests "multi_connections_test" "multi_fixed_step_algorithm_test" "ssp_parser_test" + "ssp_parser_no_algorithm_elem_test" "time_series_observer_test" "trend_buffer_test" "monitor_modified_variables_test" diff --git a/test/cpp/ssp_parser_no_algorithm_elem_test.cpp b/test/cpp/ssp_parser_no_algorithm_elem_test.cpp new file mode 100644 index 000000000..c2b02e48d --- /dev/null +++ b/test/cpp/ssp_parser_no_algorithm_elem_test.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +#include + +#include +#include + +#define REQUIRE(test) \ + if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) + +int main() +{ + try { + cse::log::setup_simple_console_logging(); + cse::log::set_global_output_level(cse::log::info); + + const auto testDataDir = std::getenv("TEST_DATA_DIR"); + REQUIRE(testDataDir); + boost::filesystem::path xmlPath = boost::filesystem::path(testDataDir) / "ssp" / "demo" / "no_algorithm_element"; + + auto resolver = cse::default_model_uri_resolver(); + auto simulation = cse::load_ssp( + *resolver, + xmlPath, + std::make_unique(cse::to_duration(1e-4))); + auto& execution = simulation.first; + + auto& simulator_map = simulation.second; + REQUIRE(simulator_map.size() == 2); + + // ssp defaultExperiment startTime=5 + REQUIRE(cse::to_double_time_point(execution.current_time()) == 5.0); + + auto obs = std::make_shared(); + execution.add_observer(obs); + auto result = execution.simulate_until(cse::to_time_point(1e-3)); + REQUIRE(result.get()); + + cse::simulator_index i = simulator_map.at("KnuckleBoomCrane").index; + double realValue = -1.0; + cse::value_reference reference = cse::find_variable(simulator_map.at("KnuckleBoomCrane").description, "Spring_Joint.k").reference; + obs->get_real(i, gsl::make_span(&reference, 1), gsl::make_span(&realValue, 1)); + + double magicNumberFromSsdFile = 0.005; + REQUIRE(std::fabs(realValue - magicNumberFromSsdFile) < 1e-9); + + cse::value_reference reference2 = cse::find_variable(simulator_map.at("KnuckleBoomCrane").description, "mt0_init").reference; + obs->get_real(i, gsl::make_span(&reference2, 1), gsl::make_span(&realValue, 1)); + + magicNumberFromSsdFile = 69.0; + REQUIRE(std::fabs(realValue - magicNumberFromSsdFile) < 1e-9); + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what(); + return 1; + } + return 0; +} diff --git a/test/data/ssp/demo/no_algorithm_element/SystemStructure.ssd b/test/data/ssp/demo/no_algorithm_element/SystemStructure.ssd new file mode 100644 index 000000000..5c165f333 --- /dev/null +++ b/test/data/ssp/demo/no_algorithm_element/SystemStructure.ssd @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +