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
1 change: 1 addition & 0 deletions include/OMSimulator/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ typedef enum {
typedef enum {
oms_connection_single, ///< FMI conncection (directed & undelayed signal connection)
oms_connection_bus, ///< Bus connection
oms_connection_acausal, ///< acausal connection between terminal members (potentials equal, flows sum to zero)
} oms_connection_type_enu_t;

/**
Expand Down
67 changes: 67 additions & 0 deletions src/OMSimulatorLib/ComponentFMU3ME.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,25 @@ oms::Component* oms::ComponentFMU3ME::NewComponent(const oms::ComRef& cref, oms:
component->lsDae.getVersion() + "): " + std::to_string(component->lsDae.getResiduals().size()) +
" residuals over " + std::to_string(component->derivativeVrs.size()) + " states and " +
std::to_string(component->lsDae.getAlgebraicVariables().size()) + " algebraic variables");

// An acausal component: its terminals are connected here, and the
// residuals that stand for their flows are replaced by the equations of
// those connections.
if (component->lsDaeAcausal.load(tempDir.generic_string()))
{
if (!component->terminals.load(tempDir.generic_string()))
{
logWarning("FMU \"" + std::string(cref) + "\" carries an acausal manifest (" + LsDaeAcausalManifest::name() +
") but no terminals; it cannot be connected acausally");
component->lsDaeAcausal = LsDaeAcausalManifest();
}
else
logInfo("FMU \"" + std::string(cref) + "\" is an acausal component (" + LsDaeAcausalManifest::name() + " " +
component->lsDaeAcausal.getVersion() + "): " + std::to_string(component->terminals.getTerminals().size()) +
" terminal(s), " + std::to_string(component->lsDaeAcausal.getConnectorResiduals().size()) +
" connector residual(s)" + (component->lsDaeAcausal.getStreamResiduals().empty() ? std::string() :
", " + std::to_string(component->lsDaeAcausal.getStreamResiduals().size()) + " stream residual(s)"));
}
}

// update FMU info
Expand Down Expand Up @@ -288,6 +307,43 @@ oms::Component* oms::ComponentFMU3ME::NewComponent(const oms::ComRef& cref, oms:
component->connectors.push_back(new Connector(oms_causality_calculatedParameter, component->allVariables[i].getType(), component->allVariables[i].getCref(), component->getFullCref()));
component->connectors.back()->setNumericType(component->allVariables[i].getNumericType());
}
// The members of an acausal component's terminals that are not inputs or
// outputs, connected acausally (SSP kind "unspecified"). A stream member is
// connected by its stream variable; the variable that stands for its inStream
// is the importer's to compute.
if (component->lsDaeAcausal.isValid())
{
auto addConnector = [&](const Terminals::Terminal& terminal, const std::string& variableName) {
const std::string resolved = component->terminals.resolveAlias(variableName);
const Variable* v = component->getVariable(ComRef(resolved));
if (!v)
{
logWarning("FMU \"" + std::string(cref) + "\": terminal \"" + terminal.name + "\" names an unknown variable \"" + variableName + "\"");
return;
}
if (resolved == variableName && (v->isInput() || v->isOutput()))
return; // a causal signal, already a connector
// A (calculated) parameter of a terminal, e.g. the fixed pressure of a
// boundary, is connected acausally like the other members.
for (auto it = component->connectors.begin(); it != component->connectors.end(); ++it)
if ((*it)->getName() == ComRef(variableName))
{
delete *it;
component->connectors.erase(it);
break;
}
component->connectors.push_back(new Connector(oms_causality_bidir, v->getType(), ComRef(variableName), component->getFullCref()));
component->connectors.back()->setNumericType(v->getNumericType());
};
for (const Terminals::Terminal& terminal : component->terminals.getTerminals())
{
for (const Terminals::Member& member : terminal.members)
addConnector(terminal, member.variableName);
for (const Terminals::StreamMember& member : terminal.streamMembers)
addConnector(terminal, member.outStreamVariableName);
}
}

component->connectors.push_back(NULL);
component->element.setConnectors(&component->connectors[0]);

Expand Down Expand Up @@ -2206,6 +2262,17 @@ oms_status_enu_t oms::ComponentFMU3ME::getDaeResiduals(double* residuals)
return oms_status_ok;
}

oms_status_enu_t oms::ComponentFMU3ME::getFloat64(const std::vector<fmi3ValueReference>& vrs, double* values)
{
CallClock callClock(clock);
if (vrs.empty())
return oms_status_ok;
fmi3Status fmistatus = fmi3_getFloat64(instance, vrs.data(), vrs.size(), values, vrs.size());
if (fmi3OK != fmistatus)
return logError_FMUCall("fmi3_getFloat64", this);
return oms_status_ok;
}

oms_status_enu_t oms::ComponentFMU3ME::getAlgebraicVariables(double* values)
{
CallClock callClock(clock);
Expand Down
13 changes: 13 additions & 0 deletions src/OMSimulatorLib/ComponentFMU3ME.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ namespace oms
size_t getNumberOfDaeResiduals() const {return lsDae.getResiduals().size();}
size_t getNumberOfAlgebraicVariables() const {return lsDae.getAlgebraicVariables().size();}
oms_status_enu_t getDaeResiduals(double* residuals);
/**
* \brief Whether the FMU is an acausal component: one whose terminals are
* connected by the importer, which then replaces the residuals the
* acausal manifest names with the equations of its connections.
*/
bool isAcausalComponent() const {return lsDaeAcausal.isValid();}
const LsDaeAcausalManifest& getLsDaeAcausal() const {return lsDaeAcausal;}
const Terminals& getTerminals() const {return terminals;}
/// Read Float64 variables by value reference, in one call into the FMU.
oms_status_enu_t getFloat64(const std::vector<fmi3ValueReference>& vrs, double* values);
/// The value references of the continuous states, in the order
/// fmi3GetContinuousStates uses — that of <ContinuousStateDerivative>.
const std::vector<fmi3ValueReference>& getStateValueReferences() const {return stateVrs;}
Expand Down Expand Up @@ -196,6 +206,9 @@ namespace oms
/// fmi-ls-dae: the manifest, and whether this instance runs in DAE mode.
LsDaeManifest lsDae;
bool daeMode = false;
/// The acausal extension of the manifest, and the terminals it refers to.
LsDaeAcausalManifest lsDaeAcausal;
Terminals terminals;
/// The <ContinuousStateDerivative> value references of the model
/// description, in the order fmi3GetContinuousStates uses for the states.
std::vector<fmi3ValueReference> derivativeVrs;
Expand Down
4 changes: 3 additions & 1 deletion src/OMSimulatorLib/Connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ oms::Connection& oms::Connection::operator=(const oms::Connection& rhs)
oms_status_enu_t oms::Connection::exportToSSD(pugi::xml_node &root) const
{
pugi::xml_node node;
if(type == oms_connection_single)
// An acausal connection is an ordinary SSD connection between connectors of
// kind "unspecified".
if(type == oms_connection_single || type == oms_connection_acausal)
node = root.append_child(oms::ssp::Draft20180219::ssd::connection);
else if(type == oms_connection_bus)
node = root.append_child(oms::ssp::Draft20180219::bus_connection);
Expand Down
9 changes: 9 additions & 0 deletions src/OMSimulatorLib/Connector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ oms::Connector* oms::Connector::NewConnector(const pugi::xml_node& node, const s
causality = oms_causality_parameter;
else if (causalityString == "calculatedParameter")
causality = oms_causality_calculatedParameter;
else if (causalityString == "unspecified")
causality = oms_causality_bidir; // an acausal connector (SSP 2.0)
else
{
logError("Failed to import connector \"" + std::string(cref) + "\" with causality \"" + causalityString + "\"");
Expand Down Expand Up @@ -231,6 +233,10 @@ oms_status_enu_t oms::Connector::exportToSSD(pugi::xml_node &root) const
node.append_attribute("kind") = "calculatedParameter";
break;
case oms_causality_bidir:
// SSP 2.0: a connector whose direction is given by the connections, as
// for an acausal connection of Modelica models
node.append_attribute("kind") = "unspecified";
break;
case oms_causality_undefined:
break;
}
Expand Down Expand Up @@ -359,6 +365,9 @@ std::string oms::Connector::getCausalityString() const
case oms_causality_calculatedParameter:
return "calculatedParameter";

case oms_causality_bidir:
return "unspecified";

default:
return std::string("unknown");
}
Expand Down
84 changes: 84 additions & 0 deletions src/OMSimulatorLib/LsDae.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,87 @@ bool oms::LsDaeManifest::load(const std::string& unzippedFmuPath)
valid = true;
return true;
}

bool oms::LsDaeAcausalManifest::load(const std::string& unzippedFmuPath)
{
const filesystem::path manifest = filesystem::path(unzippedFmuPath) / filesystem::path(path());
if (!filesystem::exists(manifest))
return false; // not an acausal component

pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(manifest.string().c_str());
if (!result)
{
logWarning("fmi-ls-dae-acausal: could not read \"" + manifest.string() + "\": " + std::string(result.description()));
return false;
}

const pugi::xml_node root = doc.child("fmiDAEAcausalManifest");
if (!root)
{
logWarning("fmi-ls-dae-acausal: \"" + manifest.string() + "\" has no <fmiDAEAcausalManifest> element");
return false;
}

version = root.attribute("fmi-ls:fmi-ls-version").as_string();
for (const pugi::xml_node& node : root.child("ConnectorResiduals").children("ConnectorResidual"))
connectorResiduals.push_back({node.attribute("valueReference").as_uint(),
node.attribute("terminal").as_string(),
node.attribute("variableName").as_string()});
for (const pugi::xml_node& node : root.child("ConnectorResiduals").children("StreamResidual"))
streamResiduals.push_back({node.attribute("valueReference").as_uint(),
node.attribute("terminal").as_string(),
node.attribute("variableName").as_string()});

valid = true;
return true;
}

bool oms::Terminals::load(const std::string& unzippedFmuPath)
{
const filesystem::path file = filesystem::path(unzippedFmuPath) / filesystem::path(path());
if (!filesystem::exists(file))
return false;

pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(file.string().c_str());
if (!result)
{
logWarning("could not read \"" + file.string() + "\": " + std::string(result.description()));
return false;
}

// Only the top-level terminals: a nested terminal is not a connector an
// acausal connection is made between.
for (const pugi::xml_node& node : doc.child("fmiTerminalsAndIcons").child("Terminals").children("Terminal"))
{
Terminal terminal;
terminal.name = node.attribute("name").as_string();
terminal.terminalKind = node.attribute("terminalKind").as_string();
for (const pugi::xml_node& member : node.children("TerminalMemberVariable"))
terminal.members.push_back({member.attribute("variableName").as_string(),
member.attribute("memberName").as_string(),
member.attribute("variableKind").as_string()});
for (const pugi::xml_node& member : node.children("TerminalStreamMemberVariable"))
terminal.streamMembers.push_back({member.attribute("inStreamMemberName").as_string(),
member.attribute("outStreamMemberName").as_string(),
member.attribute("inStreamVariableName").as_string(),
member.attribute("outStreamVariableName").as_string()});
terminals.push_back(terminal);
}

// The aliases, which a member may be: e.g. b.p of a component with a.p = b.p.
pugi::xml_document md;
const filesystem::path modelDescription = filesystem::path(unzippedFmuPath) / filesystem::path("modelDescription.xml");
if (md.load_file(modelDescription.string().c_str()))
for (const pugi::xml_node& variable : md.child("fmiModelDescription").child("ModelVariables").children())
for (const pugi::xml_node& alias : variable.children("Alias"))
aliasOf[alias.attribute("name").as_string()] = variable.attribute("name").as_string();
return true;
}

std::string oms::Terminals::resolveAlias(const std::string& variableName) const
{
const auto it = aliasOf.find(variableName);
return it == aliasOf.end() ? variableName : it->second;
}
88 changes: 88 additions & 0 deletions src/OMSimulatorLib/LsDae.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#define _OMS_LSDAE_H_

#include <cstdint>
#include <map>
#include <string>
#include <vector>

Expand Down Expand Up @@ -109,6 +110,93 @@ namespace oms
std::vector<std::vector<uint32_t>> residualDependencies;
std::vector<uint32_t> continuousStateDerivatives;
};
/**
* \brief OpenModelica's extension of fmi-ls-dae for an acausal component: a
* model connected through its terminals after it was compiled.
*
* The component carries one residual per flow member of its terminals that,
* unconnected, reads flow = 0. An importer that connects the terminal drops
* those residuals and adds the equations of its connections instead:
* potentials equal, flows summing to zero. The manifest names them.
*/
class LsDaeAcausalManifest
{
public:
static const char* name() { return "org.openmodelica.fmi-ls-dae-acausal"; }
static const char* path() { return "extra/org.openmodelica.fmi-ls-dae-acausal/fmi-ls-manifest.xml"; }

/// A residual that stands for a flow of a terminal (ConnectorResidual), or
/// for inStream of a stream variable of a terminal (StreamResidual).
struct ConnectorResidual
{
uint32_t valueReference; ///< of the residual, one of LsDaeManifest::getResiduals()
std::string terminal; ///< the terminal the flow belongs to
std::string variableName; ///< the flow variable, or the stream variable
};

/// \return true if a manifest is there and could be read; a missing file is not an error.
bool load(const std::string& unzippedFmuPath);

bool isValid() const { return valid; }
const std::string& getVersion() const { return version; }
const std::vector<ConnectorResidual>& getConnectorResiduals() const { return connectorResiduals; }
const std::vector<ConnectorResidual>& getStreamResiduals() const { return streamResiduals; }

private:
bool valid = false;
std::string version;
std::vector<ConnectorResidual> connectorResiduals;
std::vector<ConnectorResidual> streamResiduals;
};

/**
* \brief The terminals of an FMI 3.0 FMU (terminalsAndIcons/terminalsAndIcons.xml),
* as far as an acausal connection needs them.
*/
class Terminals
{
public:
static const char* path() { return "terminalsAndIcons/terminalsAndIcons.xml"; }

struct Member
{
std::string variableName;
std::string memberName;
std::string variableKind; ///< "signal" (a potential), "inflow" or "outflow" (a flow)
bool isFlow() const { return variableKind == "inflow" || variableKind == "outflow"; }
};

/// A stream variable and the variable that stands for its inStream
/// (TerminalStreamMemberVariable).
struct StreamMember
{
std::string inStreamMemberName;
std::string outStreamMemberName;
std::string inStreamVariableName;
std::string outStreamVariableName;
};

struct Terminal
{
std::string name;
std::string terminalKind;
std::vector<Member> members;
std::vector<StreamMember> streamMembers;
};

/// \return true if the file is there and could be read.
bool load(const std::string& unzippedFmuPath);

const std::vector<Terminal>& getTerminals() const { return terminals; }

/// The variable a terminal member names: itself, or the variable it is an
/// FMI 3.0 <Alias> of, which the FMI library does not list as a variable.
std::string resolveAlias(const std::string& variableName) const;

private:
std::vector<Terminal> terminals;
std::map<std::string, std::string> aliasOf;
};
}

#endif
16 changes: 15 additions & 1 deletion src/OMSimulatorLib/System.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ oms_status_enu_t oms::System::exportToSSD(pugi::xml_node& node, Snapshot& snapsh

for (const auto& connection : connections)
{
if (connection && connection->getType() == oms_connection_single)
if (connection && (connection->getType() == oms_connection_single || connection->getType() == oms_connection_acausal))
ssdconnections.push_back(connection);
else if (connection)
busconnections.push_back(connection);
Expand Down Expand Up @@ -1203,6 +1203,20 @@ oms_status_enu_t oms::System::addConnection(const oms::ComRef& crefA, const oms:
if (getConnection(crefA, crefB))
return logError_ConnectionExistsAlready(crefA, crefB, this);

// An acausal connection: two members of terminals, neither one driving the
// other. A member may be connected any number of times; the system builds
// the connection sets from these and adds their equations itself.
if (conA->getCausality() == oms_causality_bidir || conB->getCausality() == oms_causality_bidir)
{
if (conA->getCausality() != conB->getCausality())
return logError("Acausal connection between an acausal and a causal connector: " + std::string(crefA) + " -> " + std::string(crefB));
if (conA->getType() != oms_signal_type_real || conB->getType() != oms_signal_type_real)
return logError("Acausal connection of a non-real connector: " + std::string(crefA) + " -> " + std::string(crefB));
connections.back() = new oms::Connection(crefA, crefB, suppressUnitConversion, oms_connection_acausal);
connections.push_back(NULL);
return oms_status_ok;
}

auto componentA = getComponent(headA);
if(componentA && componentA->getType() == oms_component_table)
{
Expand Down
Loading