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

Add support for .ssp files #493

Merged
merged 9 commits into from
Dec 10, 2019
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
112 changes: 69 additions & 43 deletions src/cpp/ssp_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
#include "cse/exception.hpp"
#include "cse/fmi/fmu.hpp"
#include "cse/log/logger.hpp"
#include <cse/utility/filesystem.hpp>
#include <cse/utility/zip.hpp>

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

#include <string>
#include <variant>
#include <optional>

namespace cse
{
Expand Down Expand Up @@ -49,11 +52,12 @@ variable_type parse_connector_type(const boost::property_tree::ptree& tree)
}
}


class ssp_parser
{

public:
explicit ssp_parser(const boost::filesystem::path& xmlPath);
explicit ssp_parser(const boost::filesystem::path& ssdPath);
~ssp_parser() noexcept;

struct DefaultExperiment
Expand All @@ -65,12 +69,16 @@ class ssp_parser

const DefaultExperiment& get_default_experiment() const;

struct System {
std::string name;
std::optional<std::string> description;
};

struct SystemDescription
{
std::string name;
std::string version;
std::string systemName;
std::string systemDescription;
System system;
};

struct Connector
Expand Down Expand Up @@ -115,31 +123,46 @@ class ssp_parser
const std::vector<Connection>& get_connections() const;

private:
boost::filesystem::path xmlPath_;
boost::property_tree::ptree pt_;

SystemDescription systemDescription_;
DefaultExperiment defaultExperiment_;

std::vector<Component> elements_;
std::vector<Connection> connections_;

static void parse_parameter_set(Component& e, const boost::property_tree::ptree& parameterSet)
{
for (const auto& parameter : parameterSet.get_child("ssv:Parameters")) {
const auto name = get_attribute<std::string>(parameter.second, "name");
if (const auto realParameter = parameter.second.get_child_optional("ssv:Real")) {
const auto value = get_attribute<double>(*realParameter, "value");
e.parameters.push_back({name, variable_type::real, value});
} else if (const auto intParameter = parameter.second.get_child_optional("ssv:Integer")) {
const auto value = get_attribute<int>(*intParameter, "value");
e.parameters.push_back({name, variable_type::integer, value});
} else if (const auto boolParameter = parameter.second.get_child_optional("ssv:Boolean")) {
const auto value = get_attribute<bool>(*boolParameter, "value");
e.parameters.push_back({name, variable_type::boolean, value});
} else if (const auto stringParameter = parameter.second.get_child_optional("ssv:String")) {
const auto value = get_attribute<std::string>(*stringParameter, "value");
e.parameters.push_back({name, variable_type::string, value});
} else {
CSE_PANIC();
}
}
}
};

ssp_parser::ssp_parser(const boost::filesystem::path& xmlPath)
: xmlPath_(xmlPath)
ssp_parser::ssp_parser(const boost::filesystem::path& ssdPath)
{
// Root node
std::string path = "ssd:SystemStructureDescription";

boost::property_tree::ptree tmpTree;
boost::property_tree::read_xml(xmlPath.string(), pt_,
boost::property_tree::ptree root;
boost::property_tree::read_xml(ssdPath.string(), root,
boost::property_tree::xml_parser::no_comments | boost::property_tree::xml_parser::trim_whitespace);

tmpTree = pt_.get_child(path);
systemDescription_.name = get_attribute<std::string>(tmpTree, "name");
systemDescription_.version = get_attribute<std::string>(tmpTree, "version");
boost::property_tree::ptree ssd = root.get_child("ssd:SystemStructureDescription");
systemDescription_.name = get_attribute<std::string>(ssd, "name");
systemDescription_.version = get_attribute<std::string>(ssd, "version");

if (const auto defaultExperiment = tmpTree.get_child_optional("ssd:DefaultExperiment")) {
if (const auto defaultExperiment = ssd.get_child_optional("ssd:DefaultExperiment")) {
defaultExperiment_.startTime = get_attribute<double>(*defaultExperiment, "startTime", 0.0);
defaultExperiment_.stopTime = get_optional_attribute<double>(*defaultExperiment, "stopTime");

Expand All @@ -160,11 +183,11 @@ ssp_parser::ssp_parser(const boost::filesystem::path& xmlPath)
}
}

tmpTree = pt_.get_child(path + ".ssd:System");
systemDescription_.systemName = get_attribute<std::string>(tmpTree, "name");
systemDescription_.systemDescription = get_attribute<std::string>(tmpTree, "description");
boost::property_tree::ptree system = ssd.get_child("ssd:System");
systemDescription_.system.name = get_attribute<std::string>(system, "name");
systemDescription_.system.description = get_optional_attribute<std::string>(system, "description");

for (const auto& component : tmpTree.get_child("ssd:Elements")) {
for (const auto& component : system.get_child("ssd:Elements")) {

auto& e = elements_.emplace_back();
e.name = get_attribute<std::string>(component.second, "name");
Expand All @@ -183,33 +206,27 @@ ssp_parser::ssp_parser(const boost::filesystem::path& xmlPath)

if (const auto parameterBindings = component.second.get_child_optional("ssd:ParameterBindings")) {
for (const auto& parameterBinding : *parameterBindings) {
if (const auto parameterValues = parameterBinding.second.get_child_optional("ssd:ParameterValues")) {
const auto parameterSet = parameterValues->get_child("ssv:ParameterSet");
const auto parameters = parameterSet.get_child("ssv:Parameters");
for (const auto& parameter : parameters) {
const auto name = get_attribute<std::string>(parameter.second, "name");
if (const auto realParameter = parameter.second.get_child_optional("ssv:Real")) {
const auto value = get_attribute<double>(*realParameter, "value");
e.parameters.push_back({name, variable_type::real, value});
} else if (const auto intParameter = parameter.second.get_child_optional("ssv:Integer")) {
const auto value = get_attribute<int>(*intParameter, "value");
e.parameters.push_back({name, variable_type::integer, value});
} else if (const auto boolParameter = parameter.second.get_child_optional("ssv:Boolean")) {
const auto value = get_attribute<bool>(*boolParameter, "value");
e.parameters.push_back({name, variable_type::boolean, value});
} else if (const auto stringParameter = parameter.second.get_child_optional("ssv:String")) {
const auto value = get_attribute<std::string>(*stringParameter, "value");
e.parameters.push_back({name, variable_type::string, value});
} else {
CSE_PANIC();
}
if (const auto& source = get_optional_attribute<std::string>(parameterBinding.second, "source")) {
boost::property_tree::ptree binding;
boost::filesystem::path ssvPath = ssdPath.parent_path() / *source;
if (!boost::filesystem::exists(ssvPath)) {
std::ostringstream oss;
oss << "File '" << ssvPath << "' does not exists!";
throw std::runtime_error(oss.str());
}
boost::property_tree::read_xml(ssvPath.string(), binding,
boost::property_tree::xml_parser::no_comments | boost::property_tree::xml_parser::trim_whitespace);
parse_parameter_set(e, binding.get_child("ssv:ParameterSet"));
} else {
if (const auto parameterValues = parameterBinding.second.get_child_optional("ssd:ParameterValues")) {
parse_parameter_set(e, parameterValues->get_child("ssv:ParameterSet"));
}
}
}
}
}

if (const auto connections = tmpTree.get_child_optional("ssd:Connections")) {
if (const auto connections = system.get_child_optional("ssd:Connections")) {
for (const auto& connection : *connections) {
auto& c = connections_.emplace_back();
c.startElement = get_attribute<std::string>(connection.second, "startElement");
Expand Down Expand Up @@ -315,8 +332,17 @@ std::pair<execution, simulator_map> load_ssp(
std::shared_ptr<cse::algorithm> overrideAlgorithm,
std::optional<cse::time_point> overrideStartTime)
{
auto sspFile = configPath;
std::optional<cse::utility::temp_dir> temp_ssp_dir;
if (sspFile.extension() == ".ssp") {
temp_ssp_dir = cse::utility::temp_dir();
auto archive = cse::utility::zip::archive(sspFile);
archive.extract_all(temp_ssp_dir->path());
sspFile = temp_ssp_dir->path();
}

simulator_map simulatorMap;
const auto absolutePath = boost::filesystem::absolute(configPath);
const auto absolutePath = boost::filesystem::absolute(sspFile);
const auto configFile = boost::filesystem::is_regular_file(absolutePath)
? absolutePath
: absolutePath / "SystemStructure.ssd";
Expand Down
85 changes: 43 additions & 42 deletions test/cpp/ssp_parser_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,11 @@ namespace
// See https://www.boost.org/doc/libs/1_65_0/libs/test/doc/html/boost_test/utf_reference/testing_tool_ref/assertion_boost_level_close.html
constexpr double tolerance = 0.0001;

}

BOOST_AUTO_TEST_CASE(basic_test)
void common_demo_case_tests(cse::execution& execution, cse::simulator_map simulatorMap)
{
cse::log::setup_simple_console_logging();
cse::log::set_global_output_level(cse::log::info);

const auto testDataDir = std::getenv("TEST_DATA_DIR");
BOOST_REQUIRE(testDataDir != nullptr);
boost::filesystem::path xmlPath = boost::filesystem::path(testDataDir) / "ssp" / "demo";

auto resolver = cse::default_model_uri_resolver();
auto [execution, simulator_map] = cse::load_ssp(*resolver, xmlPath);

BOOST_REQUIRE(simulator_map.size() == 2);

auto craneController = simulator_map.at("CraneController");
auto knuckleBoomCrane = simulator_map.at("KnuckleBoomCrane");

BOOST_REQUIRE(craneController.source == "CraneController.fmu");
BOOST_REQUIRE(knuckleBoomCrane.source == "KnuckleBoomCrane.fmu");
BOOST_REQUIRE(simulatorMap.size() == 2);
auto craneController = simulatorMap.at("CraneController");
auto knuckleBoomCrane = simulatorMap.at("KnuckleBoomCrane");

auto obs = std::make_shared<cse::last_value_observer>();
execution.add_observer(obs);
Expand All @@ -56,6 +40,29 @@ BOOST_AUTO_TEST_CASE(basic_test)
BOOST_CHECK_CLOSE(realValue, magicNumberFromSsdFile, tolerance);
}

} // namespace

BOOST_AUTO_TEST_CASE(basic_test)
{
cse::log::setup_simple_console_logging();
cse::log::set_global_output_level(cse::log::info);

const auto testDataDir = std::getenv("TEST_DATA_DIR");
BOOST_REQUIRE(testDataDir != nullptr);
boost::filesystem::path xmlPath = boost::filesystem::path(testDataDir) / "ssp" / "demo";

auto resolver = cse::default_model_uri_resolver();
auto [execution, simulatorMap] = cse::load_ssp(*resolver, xmlPath);

auto craneController = simulatorMap.at("CraneController");
auto knuckleBoomCrane = simulatorMap.at("KnuckleBoomCrane");

BOOST_REQUIRE(craneController.source == "CraneController.fmu");
BOOST_REQUIRE(knuckleBoomCrane.source == "KnuckleBoomCrane.fmu");

common_demo_case_tests(execution, simulatorMap);
}

BOOST_AUTO_TEST_CASE(no_algorithm_test)
{
cse::log::setup_simple_console_logging();
Expand All @@ -67,32 +74,26 @@ BOOST_AUTO_TEST_CASE(no_algorithm_test)

auto resolver = cse::default_model_uri_resolver();
auto algorithm = std::make_unique<cse::fixed_step_algorithm>(cse::to_duration(1e-4));
auto [execution, simulator_map] = cse::load_ssp(*resolver, xmlPath, std::move(algorithm));

BOOST_REQUIRE(simulator_map.size() == 2);

auto knuckleBoomCrane = simulator_map.at("KnuckleBoomCrane");
auto [execution, simulatorMap] = cse::load_ssp(*resolver, xmlPath, std::move(algorithm));

double startTimeDefinedInSsp = 5.0;
BOOST_CHECK_CLOSE(cse::to_double_time_point(execution.current_time()), startTimeDefinedInSsp, tolerance);

auto obs = std::make_shared<cse::last_value_observer>();
execution.add_observer(obs);
auto result = execution.simulate_until(cse::to_time_point(1e-3));
BOOST_REQUIRE(result.get());

double realValue = -1.0;
cse::value_reference reference = cse::find_variable(knuckleBoomCrane.description, "Spring_Joint.k").reference;
obs->get_real(knuckleBoomCrane.index, gsl::make_span(&reference, 1), gsl::make_span(&realValue, 1));
common_demo_case_tests(execution, simulatorMap);
}

double magicNumberFromSsdFile = 0.005;
BOOST_CHECK_CLOSE(realValue, magicNumberFromSsdFile, tolerance);
BOOST_AUTO_TEST_CASE(ssp_archive)
{
cse::log::setup_simple_console_logging();
cse::log::set_global_output_level(cse::log::info);

cse::value_reference reference2 = cse::find_variable(knuckleBoomCrane.description, "mt0_init").reference;
obs->get_real(knuckleBoomCrane.index, gsl::make_span(&reference2, 1), gsl::make_span(&realValue, 1));
const auto testDataDir = std::getenv("TEST_DATA_DIR");
BOOST_TEST_REQUIRE(testDataDir != nullptr);
const auto sspFile = boost::filesystem::path(testDataDir) / "ssp" / "demo" / "demo.ssp";

magicNumberFromSsdFile = 69.0;
BOOST_CHECK_CLOSE(realValue, magicNumberFromSsdFile, tolerance);
auto resolver = cse::default_model_uri_resolver();
auto [execution, simulatorMap] = cse::load_ssp(*resolver, sspFile);
common_demo_case_tests(execution, simulatorMap);
}

BOOST_AUTO_TEST_CASE(ssp_linear_transformation_test)
Expand All @@ -103,21 +104,21 @@ BOOST_AUTO_TEST_CASE(ssp_linear_transformation_test)

auto resolver = cse::default_model_uri_resolver();
auto algorithm = std::make_shared<cse::fixed_step_algorithm>(cse::to_duration(1e-3));
auto [exec, simulator_map] = cse::load_ssp(*resolver, sspDir, algorithm);
auto [exec, simulatorMap] = cse::load_ssp(*resolver, sspDir, algorithm);

auto observer = std::make_shared<cse::last_value_observer>();
exec.add_observer(observer);

exec.step();

double initialValue;
auto slave1 = simulator_map.at("identity1");
auto slave1 = simulatorMap.at("identity1");
cse::value_reference v1Ref = cse::find_variable(slave1.description, "realOut").reference;
observer->get_real(slave1.index, gsl::make_span(&v1Ref, 1), gsl::make_span(&initialValue, 1));
BOOST_REQUIRE_CLOSE(initialValue, 2.0, tolerance);

double transformedValue;
auto slave2 = simulator_map.at("identity2");
auto slave2 = simulatorMap.at("identity2");
cse::value_reference v2Ref = cse::find_variable(slave2.description, "realIn").reference;
observer->get_real(slave2.index, gsl::make_span(&v2Ref, 1), gsl::make_span(&transformedValue, 1));

Expand Down
Binary file added test/data/ssp/demo/demo.ssp
Binary file not shown.
15 changes: 2 additions & 13 deletions test/data/ssp/linear_transformation/SystemStructure.ssd
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<ssd:SystemStructureDescription name="simple-cse-example" version="1.0"
xmlns:ssd="http://ssp-standard.org/SSP1/SystemStructureDescription"
xmlns:ssc="http://ssp-standard.org/SSP1/SystemStructureCommon"
xmlns:ssv="http://ssp-standard.org/SSP1/SystemStructureParameterValues">
xmlns:ssc="http://ssp-standard.org/SSP1/SystemStructureCommon">

<ssd:System name="test-linear-transformation" description="An example of how to use Core Simulation Environment">

<ssd:Elements>
<ssd:Component name="identity1" source="../../fmi1/identity.fmu">
<ssd:ParameterBindings>
<ssd:ParameterBinding>
<ssd:ParameterValues>
<ssv:ParameterSet version="1.0" name="initialValues">
<ssv:Parameters>
<ssv:Parameter name="realOut">
<ssv:Real value="2" />
</ssv:Parameter>
</ssv:Parameters>
</ssv:ParameterSet>
</ssd:ParameterValues>
</ssd:ParameterBinding>
<ssd:ParameterBinding source="initial_values.ssv"/>
</ssd:ParameterBindings>
</ssd:Component>
<ssd:Component name="identity2" source="../../fmi1/identity.fmu"/>
Expand Down
9 changes: 9 additions & 0 deletions test/data/ssp/linear_transformation/initial_values.ssv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<ssv:ParameterSet version="1.0" name="initialValues"
xmlns:ssv="http://ssp-standard.org/SSP1/SystemStructureParameterValues">
<ssv:Parameters>
<ssv:Parameter name="realOut">
<ssv:Real value="2" />
</ssv:Parameter>
</ssv:Parameters>
</ssv:ParameterSet>