Skip to content

Commit 923e806

Browse files
authored
implement connection table check in python (#1535)
1 parent e0eb51e commit 923e806

10 files changed

Lines changed: 79 additions & 56 deletions

File tree

src/OMSimulatorLib/System.cpp

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,42 +1200,9 @@ oms_status_enu_t oms::System::addConnection(const oms::ComRef& crefA, const oms:
12001200
{
12011201
return logError("Unit mismatch in connection: " + std::string(crefA) + " -> " + std::string(crefB));
12021202
}
1203-
1204-
// check if the connections are valid, according to the SSP-1.0 allowed connection table
1205-
if (oms::Connection::isValid(crefA, crefB, *conA, *conB))
1206-
{
1207-
connections.back() = new oms::Connection(crefA, crefB, suppressUnitConversion);
1208-
connections.push_back(NULL);
1209-
}
1210-
// flipped causality check
1211-
else if (oms::Connection::isValid(crefB, crefA, *conB, *conA))
1212-
{
1213-
// ! Flipped connection checks !
1214-
// Do not allow multiple connections to same 'input' connector
1215-
// (signal A!). The 'input' connector (signal A!) can actually be
1216-
// an output connector, e.g. if connecting a component to a system
1217-
// connector.
1218-
for (auto &connection : connections)
1219-
{
1220-
if (connection && connection->containsSignalB(crefA))
1221-
return logError("Connector " + std::string(crefA) + " is already connected to " + std::string(connection->getSignalA()));
1222-
}
1223-
connections.back() = new oms::Connection(crefB, crefA, suppressUnitConversion);
1224-
connections.push_back(NULL);
1225-
}
1226-
else if (oms::Connection::isValidExportConnectorName(*conA, *conB))
1227-
{
1228-
connections.back() = new oms::Connection(crefA, crefB, suppressUnitConversion);
1229-
connections.push_back(NULL);
1230-
}
1231-
// flipped causality check
1232-
else if (oms::Connection::isValidExportConnectorName(*conB, *conA))
1233-
{
1234-
connections.back() = new oms::Connection(crefB, crefA, suppressUnitConversion);
1235-
connections.push_back(NULL);
1236-
}
1237-
else
1238-
return logError("Causality mismatch in connection: " + std::string(crefA) + " -> " + std::string(crefB));
1203+
// connection are checked in the python side, directly add the connection
1204+
connections.back() = new oms::Connection(crefA, crefB, suppressUnitConversion);
1205+
connections.push_back(NULL);
12391206

12401207
return oms_status_ok;
12411208
}

src/OMSimulatorPython/connection.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from lxml import etree as ET
22

33
from OMSimulator import namespace
4+
from OMSimulator.variable import Causality
45

56
class ConnectionGeometry:
67
def __init__(self, pointsX: list , pointsY: list):
@@ -63,3 +64,22 @@ def importFromNode(node, root):
6364
if pointsX and pointsY:
6465
connectionGeometry = ConnectionGeometry([float(x) for x in pointsX.split()], [float(y) for y in pointsY.split()])
6566
root.connections[-1].connectionGeometry = connectionGeometry
67+
68+
@staticmethod
69+
def is_validConnection(source_owner: str, source_kind: Causality, dest_owner: str, dest_kind: Causality) -> bool:
70+
"""Return True if the connection is allowed according to SSP 2.0 table."""
71+
72+
# Allowed causality by owner type
73+
allowed_source_by_owner = {
74+
"System": {Causality.input, Causality.parameter},
75+
"Element": {Causality.output, Causality.calculatedParameter},
76+
}
77+
78+
allowed_dest_by_owner = {
79+
"System": {Causality.output, Causality.calculatedParameter},
80+
"Element": {Causality.input, Causality.parameter},
81+
}
82+
83+
result = (source_kind in allowed_source_by_owner.get(source_owner, set()) and dest_kind in allowed_dest_by_owner.get(dest_owner, set()))
84+
85+
return result

src/OMSimulatorPython/system.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -434,15 +434,44 @@ def _addConnection(self, cref1: CRef, cref2: CRef) -> None:
434434
# Add the connections to top level system
435435
self.addConnection(start_element, start_connector, end_element, end_connector)
436436

437-
def addConnection(self, startElement : str, startConnector : str, endElement : str, endConnector : str):
438-
#TODO: Fix this check for Connection class
439-
#if (startElement, startConnector, endElement, endConnector) in self.connections:
440-
# raise ValueError(f"Connection '{startElement}.{startConnector}' to '{endElement}.{endConnector}' already exists")
437+
def isConnectorAlreadyConnected(self, startElement : str, startConnector : str, endElement : str, endConnector : str):
438+
"""Check if a connection is valid in the system."""
439+
for conn in self.connections:
440+
if (conn.startElement == startElement and conn.startConnector == startConnector and
441+
conn.endElement == endElement and conn.endConnector == endConnector):
442+
raise ValueError(f"Connection from '{startElement}.{startConnector}' to '{endElement}.{endConnector}' already exists")
443+
444+
def _findConnector(self, element_name, connector_name):
445+
"""Returns (owner_string, causality) or (None, None) if not found."""
446+
connectors = (
447+
self.connectors # System level
448+
if not element_name else
449+
self.elements[CRef(element_name)].connectors # Element level
450+
)
451+
for con in connectors:
452+
if str(con.name) == str(connector_name):
453+
owner_str = "System" if not element_name else "Element"
454+
return (owner_str, con.causality)
455+
456+
raise ValueError(f"Connector '{connector_name}' not found in element '{element_name or self.name}'")
441457

442-
#if (endElement, endConnector, startElement, startConnector) in self.connections:
443-
# raise ValueError(f"Connection '{startElement}.{startConnector}' to '{endElement}.{endConnector}' already exists")
458+
def addConnection(self, startElement : str, startConnector : str, endElement : str, endConnector : str):
459+
"""Adds a connection to the system."""
460+
# Resolve source connector owner and causality
461+
(source_owner, source_kind) = self._findConnector(startElement, startConnector)
462+
# Resolve destination connector owner and causality
463+
(dest_owner, dest_kind) = self._findConnector(endElement, endConnector)
464+
465+
if Connection.is_validConnection(source_owner, source_kind, dest_owner, dest_kind):
466+
self.isConnectorAlreadyConnected(startElement, startConnector, endElement, endConnector)
467+
self.connections.append(Connection(startElement, startConnector, endElement, endConnector))
468+
# flipped connection
469+
elif Connection.is_validConnection(dest_owner, dest_kind, source_owner, source_kind):
470+
self.isConnectorAlreadyConnected(endElement, endConnector, startElement, startConnector)
471+
self.connections.append(Connection(endElement, endConnector, startElement, startConnector))
472+
else:
473+
raise ValueError(f"info: Invalid connection from '{startElement}.{startConnector}'->'{endElement}.{endConnector}' as causality are violated with '{source_owner}.{source_kind.name}' -> '{dest_owner}.{dest_kind.name}'")
444474

445-
self.connections.append(Connection(startElement, startConnector, endElement, endConnector))
446475

447476
def _connectorExists(self, cref: CRef, delete = False) -> bool:
448477
"""Check if a connector exists in the system."""

testsuite/AircraftVehicleDemonstrator/embrace1.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
model.addConnection(CRef('root', 'ECS_HW', 'Mach'), CRef('root', 'BC', 'Mach'))
3333

3434
#BC -> ECS_SW
35-
model.addConnection(CRef('root', 'BC', 'Aircraft_state'), CRef('root', 'AdaptionUnit.Aircraft_State'))
35+
model.addConnection(CRef('root', 'BC', 'Aircraft_state'), CRef('root', 'AdaptionUnit', 'Aircraft_State'))
3636

3737
#BC -> Atmos
3838
model.addConnection(CRef('root', 'Atmos', 'Alt'), CRef('root', 'BC', 'Alt'))

testsuite/AircraftVehicleDemonstrator/embrace2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
model.addConnection(CRef('root', 'ECS_HW', 'Mach'), CRef('root', 'BC', 'Mach'))
3737

3838
#BC -> ECS_SW
39-
model.addConnection(CRef('root', 'BC', 'Aircraft_state'), CRef('root', 'AdaptionUnit.Aircraft_State'))
39+
model.addConnection(CRef('root', 'BC', 'Aircraft_state'), CRef('root', 'AdaptionUnit', 'Aircraft_State'))
4040

4141
#BC -> Atmos
4242
model.addConnection(CRef('root', 'Atmos', 'Alt'), CRef('root', 'BC', 'Alt'))

testsuite/api/deleteSystem1.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
## |-- |-- |-- |-- |-- |-- |-- Solver Settings:
9090
## |-- |-- |-- |-- |-- |-- |-- |-- name: solver1
9191
## |-- |-- |-- |-- |-- Connections:
92-
## |-- |-- |-- |-- |-- |-- Add2.u1 -> .input
92+
## |-- |-- |-- |-- |-- |-- .input -> Add2.u1
9393
## |-- |-- |-- |-- FMU: Add1 'None'
9494
## |-- |-- |-- |-- |-- path: resources/Add.fmu
9595
## |-- |-- |-- |-- |-- Connectors:

testsuite/dcmotor/dcmotor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@
166166
## |-- |-- |-- SuT.I -> .I
167167
## |-- |-- |-- stimuli_model.M_load -> SuT.M_load
168168
## |-- |-- |-- stimuli_model.U -> SuT.U
169-
## |-- |-- |-- .M_load -> stimuli_model.M_load
169+
## |-- |-- |-- stimuli_model.M_load -> .M_load
170170
## |-- |-- |-- |-- ConnectionGeometry: (pointsX: [131.0, 131.0], pointsY: [-245.534819, -105.0])
171171
## |-- |-- SystemGeometry:
172172
## |-- |-- |-- (x1:-359.5, y1:-383.0, x2:710.0, y2:198.0)

testsuite/simulation/SimpleSimulation15.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,18 @@
2424
subsystem.addConnector(Connector(CRef("u"), Causality.input, SignalType.Real))
2525
subsystem.addConnector(Connector(CRef("y"), Causality.output, SignalType.Real))
2626

27+
## System.input -> Element.input
2728
subsystem.addConnection("", "u", "Add2", "u1")
29+
## System.input -> Element.input
2830
subsystem.addConnection("", "u", "Add2", "u2")
31+
## Element.output -> System.output
2932
subsystem.addConnection("", "y", "Add2", "y")
3033

31-
34+
## Element.output -> Element.input
3235
model.addConnection(CRef("default", "Add1", "y"), CRef("default", "sub-system", "u"))
33-
model.addConnection(CRef("default", "sub-system", "y"), CRef("default", "Add3", "u1"))
36+
## flip connection to test Element.output -> Element.input
37+
model.addConnection(CRef("default", "Add3", "u1"), CRef("default", "sub-system", "y"))
38+
## Element.output -> Element.input
3439
model.addConnection(CRef("default", "sub-system", "y"), CRef("default", "Add3", "u2"))
3540

3641
## check failing connection and fix it
@@ -97,7 +102,7 @@
97102
## |-- |-- |-- |-- |-- Connections:
98103
## |-- |-- |-- |-- |-- |-- .u -> Add2.u1
99104
## |-- |-- |-- |-- |-- |-- .u -> Add2.u2
100-
## |-- |-- |-- |-- |-- |-- .y -> Add2.y
105+
## |-- |-- |-- |-- |-- |-- Add2.y -> .y
101106
## |-- |-- |-- Connections:
102107
## |-- |-- |-- |-- Add1.y -> sub-system.u
103108
## |-- |-- |-- |-- sub-system.y -> Add3.u1

testsuite/simulation/exportJson2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@
155155
## |-- |-- |-- SuT.I -> .I
156156
## |-- |-- |-- stimuli_model.M_load -> SuT.M_load
157157
## |-- |-- |-- stimuli_model.U -> SuT.U
158-
## |-- |-- |-- .M_load -> stimuli_model.M_load
158+
## |-- |-- |-- stimuli_model.M_load -> .M_load
159159
## |-- |-- |-- |-- ConnectionGeometry: (pointsX: [131.0, 131.0], pointsY: [-245.534819, -105.0])
160160
## |-- |-- SystemGeometry:
161161
## |-- |-- |-- (x1:-359.5, y1:-383.0, x2:710.0, y2:198.0)

testsuite/simulation/exportJson4.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
model.setSolver(CRef('default', 'sub-system', 'Add2'), 'solver1')
3131

3232
model.addConnection(CRef('default', 'param1'), CRef('default', 'Add1', 'u1'))
33-
model.addConnection(CRef('default', 'Add1', 'y'), CRef('default', 'sub-system', 'output'))
33+
## Element.output -> System.output
34+
model.addConnection(CRef('default', 'sub-system', 'Add2', 'y'), CRef('default', 'sub-system', 'output'))
3435
model.addConnection(CRef('default', 'sub-system', 'Add2', 'u1'), CRef('default', 'sub-system', 'input'))
3536

3637
model.export('exportJson4.ssp')
@@ -42,6 +43,7 @@
4243
# print(instantiated_model.dumpApiCalls(), flush=True)
4344
instantiated_model.setResultFile("export_json4_res.mat")
4445
instantiated_model.setValue(CRef('default', 'param1'), 2.0)
46+
instantiated_model.setValue(CRef('default', 'sub-system', 'input'), 5.0)
4547
instantiated_model.setValue(CRef('default', 'Add1', 'u2'), 3.0)
4648

4749
print(f"info: After instantiation:")
@@ -94,7 +96,8 @@
9496
## |-- |-- |-- |-- |-- |-- |-- Solver Settings:
9597
## |-- |-- |-- |-- |-- |-- |-- |-- name: solver1
9698
## |-- |-- |-- |-- |-- Connections:
97-
## |-- |-- |-- |-- |-- |-- Add2.u1 -> .input
99+
## |-- |-- |-- |-- |-- |-- Add2.y -> .output
100+
## |-- |-- |-- |-- |-- |-- .input -> Add2.u1
98101
## |-- |-- |-- |-- FMU: Add1 'None'
99102
## |-- |-- |-- |-- |-- path: resources/Add.fmu
100103
## |-- |-- |-- |-- |-- Connectors:
@@ -107,23 +110,22 @@
107110
## |-- |-- |-- |-- |-- |-- name: solver1
108111
## |-- |-- |-- Connections:
109112
## |-- |-- |-- |-- .param1 -> Add1.u1
110-
## |-- |-- |-- |-- Add1.y -> sub-system.output
111113
## |-- |-- |-- Solver Settings:
112114
## |-- |-- |-- |-- (name=solver1, method=euler, tolerance=1e-06)
113115
## |-- DefaultExperiment
114116
## |-- |-- startTime: 0.0
115117
## |-- |-- stopTime: 1.0
116118
## info: After instantiation:
117119
## info: default.param1 : 2.0
118-
## info: default.sub-system.input : 0.0
120+
## info: default.sub-system.input : 5.0
119121
## info: default.sub-system.output: 0.0
120122
## info: default.Add1.u1 : 0.0
121123
## info: default.Add1.u2 : 3.0
122124
## info: default.Add1.y : 3.0
123125
## info: Result file: export_json4_res.mat (bufferSize=1)
124126
## info: After simulation:
125127
## info: default.param1 : 2.0
126-
## info: default.sub-system.input : 0.0
128+
## info: default.sub-system.input : 5.0
127129
## info: default.sub-system.output: 5.0
128130
## info: default.Add1.u1 : 2.0
129131
## info: default.Add1.u2 : 3.0

0 commit comments

Comments
 (0)