Skip to content

Commit db63301

Browse files
authored
add FMI-3.0 SSP support (#1571)
* fix variable types * pass fmi3 types to values * fix signal types when importing from ssp * allow type=None to support parameter mapping * check connector signaltype
1 parent 0b655a2 commit db63301

16 files changed

Lines changed: 260 additions & 87 deletions

src/OMSimulatorPython/component.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,12 @@ def exportToSSD(self, node):
145145
utils.exportAnnotations(component_node, self.solver)
146146

147147
def setValue(self, cref:str, value, unit=None, description = None):
148-
self.value.setValue(cref, value, unit, description)
148+
for connector in self.connectors:
149+
if str(connector.name) == str(cref):
150+
self.value.setValue(cref, value, connector.signal_type, unit, description)
151+
return
152+
## it is possible that the parameter is not defined as connector but only in the ssv file or ssm mapping, so we allow setting values without types
153+
self.value.setValue(cref, value, None, unit, description)
149154

150155
def getValue(self, cref:str):
151156
return self.value.getValue(cref)

src/OMSimulatorPython/instantiated_model.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -276,31 +276,32 @@ def setStartValues(self, value: Values, systemName: str, ssm: SSM | None):
276276
if ssm and ssm.mappingEntry:
277277
for source, targets in ssm.mappingEntry.items():
278278
if CRef(source) in value.start_values:
279-
source_value, _, _ = value.start_values[CRef(source)]
279+
(source_value, type, _, _) = value.start_values[CRef(source)]
280280
for entry in targets:
281281
target = entry["target"]
282282
linearTransformation = entry["linearTransformation"]
283283
value_path = self.map_cref(systemName, str(target))
284284
if linearTransformation:
285285
source_value = source_value * float(linearTransformation.factor) + float(linearTransformation.offset)
286-
self.apply_start_value(value_path, source_value)
286+
self.apply_start_value(value_path, source_value, type)
287287
else:
288-
for key, (source_value, _, _) in value.start_values.items():
288+
for key, (source_value, type, _, _) in value.start_values.items():
289289
value_path = self.map_cref(systemName, str(key))
290-
self.apply_start_value(value_path, source_value)
290+
self.apply_start_value(value_path, source_value, type)
291291

292-
def apply_start_value(self, value_path:str, value):
292+
def apply_start_value(self, value_path:str, value, type):
293293
# Determine the variable type
294-
type, status = Capi.getVariableType(value_path)
295-
if status != Status.ok:
296-
raise RuntimeError(f"Failed to get variable type for {value_path}: {status}")
294+
if type is None:
295+
type, status = Capi.getVariableType(value_path)
296+
if status != Status.ok:
297+
raise RuntimeError(f"Failed to get variable type for {value_path}: {status}")
297298
## TODO: handle FMi3 data types directly, like Float64, Int32,
298299
value_ = value.value if hasattr(value, 'value') else value
299300

300301
match SignalType(type):
301-
case SignalType.Real: # oms_signal_type_real
302+
case SignalType.Real | SignalType.Float32 | SignalType.Float64: # oms_signal_type_real
302303
self._setReal(value_path, float(value_))
303-
case SignalType.Integer: # oms_signal_type_integer
304+
case SignalType.Integer | SignalType.Int32 | SignalType.Int8 | SignalType.UInt8 | SignalType.Int16 | SignalType.UInt16 | SignalType.Int32 |SignalType.UInt32 | SignalType.Int64 | SignalType.UInt64: # oms_signal_type_integer
304305
self._setInteger(value_path, int(value_))
305306
case SignalType.Boolean: # oms_signal_type_boolean
306307
self._setBoolean(value_path, bool(value_))

src/OMSimulatorPython/ssv.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from lxml import etree as ET
44
from OMSimulator.values import Values
5-
5+
from OMSimulator.variable import SignalType
66
from OMSimulator import namespace, utils
77

88

@@ -14,8 +14,8 @@ def __init__(self, ssv_path : str | None = None):
1414
self.filename = Path(ssv_path)
1515
self.importFromSSV(self.filename)
1616

17-
def setValue(self, cref:str, value, unit = None, description = None):
18-
self.value.setValue(cref, value, unit, description)
17+
def setValue(self, cref:str, value, type : SignalType | None = None, unit = None, description = None):
18+
self.value.setValue(cref, value, type, unit, description)
1919

2020
def list(self, prefix = ""):
2121
self.value.list(prefix)
@@ -40,5 +40,5 @@ def export(self, filename : str):
4040

4141
def importFromSSV(self, filename):
4242
parameterValues = utils.parseSSV(filename)
43-
for key, (value, unit, description) in parameterValues.items():
44-
self.setValue(key, value, unit, description)
43+
for key, (value, type, unit, description) in parameterValues.items():
44+
self.setValue(key, value, type, unit, description)

src/OMSimulatorPython/system.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def delete(self, cref: CRef):
304304
first = cref.first()
305305

306306
## Check if the cref is a top level system connector
307-
if self._connectorExists(first, delete = True):
307+
if self._deleteConnector(first):
308308
return
309309

310310
match self.elements.get(first):
@@ -472,15 +472,19 @@ def addConnection(self, startElement : str, startConnector : str, endElement : s
472472
else:
473473
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}'")
474474

475-
476-
def _connectorExists(self, cref: CRef, delete = False) -> bool:
475+
def _connectorExists(self, cref: CRef) -> Connector | None:
477476
"""Check if a connector exists in the system."""
477+
for connector in self.connectors:
478+
if connector.name == cref:
479+
return connector
480+
return None
481+
482+
def _deleteConnector(self, cref: CRef) -> bool:
483+
"""Delete connector if it exists."""
478484
for i, connector in enumerate(self.connectors):
479485
if connector.name == cref:
480-
if delete:
481-
del self.connectors[i]
482-
# Remove connections associated with this connector
483-
self.deleteAllConnection(cref)
486+
self.deleteAllConnection(cref)
487+
del self.connectors[i]
484488
return True
485489
return False
486490

@@ -490,7 +494,8 @@ def _getComponentResourcePath(self, cref):
490494

491495
## check if element is a top level system connectors
492496
## or allow non existing connectors to support parameter mapping throgh SSM inline or ssm file by checking if cref
493-
if self._connectorExists(cref) or cref.is_root():
497+
connector = self._connectorExists(cref)
498+
if connector or cref.is_root():
494499
return (None, None)
495500

496501
if element is None:
@@ -508,8 +513,10 @@ def setValue(self, cref: CRef, value, unit = None, description = None):
508513
first = cref.first()
509514
## Check if the cref is a top level system connector
510515
## or allow non existing connectors to support parameter mapping throgh SSM inline or ssm file by checking if cref
511-
if self._connectorExists(first) or cref.is_root():
512-
self.value.setValue(cref, value, unit)
516+
connector = self._connectorExists(first)
517+
if connector or cref.is_root():
518+
signal_type = connector.signal_type if connector else None
519+
self.value.setValue(cref, value, signal_type, unit, description)
513520
return
514521

515522
match self.elements.get(first):
@@ -524,7 +531,8 @@ def getValue(self, cref: CRef):
524531
first = cref.first()
525532
## Check if the cref is a top level system connector
526533
## or allow non existing connectors to support parameter mapping throgh SSM inline or ssm file by checking if cref
527-
if self._connectorExists(first) or cref.is_root():
534+
connector = self._connectorExists(first)
535+
if connector or cref.is_root():
528536
self.value.getValue(cref)
529537
return
530538

src/OMSimulatorPython/utils.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ def _simple_warning(message, category, filename, lineno, line=None):
2020

2121
def _setParameters(parameterValues: dict, obj):
2222
if len(parameterValues) > 0:
23-
for key, (value, unit, description) in parameterValues.items():
24-
obj.value.setValue(key, value, unit, description)
23+
for key, (value, type, unit, description) in parameterValues.items():
24+
obj.value.setValue(key, value, type, unit, description)
2525

2626
def parseDefaultExperiment(node, root):
2727
default_experiment = node.find("ssd:DefaultExperiment", namespaces=namespace.ns)
@@ -163,6 +163,7 @@ def parseParameterBindingHelper(parameters):
163163
"ssv:Integer": int,
164164
"ssv:Boolean": lambda v: v.lower() == "true", # Convert "true"/"false" to bool
165165
"ssv:String": str,
166+
"ssv:Enumeration": int,
166167
## FMI-3.0 types
167168
"ssv:Float64": Float64,
168169
"ssv:Float32": Float32,
@@ -180,7 +181,11 @@ def parseParameterBindingHelper(parameters):
180181
if value_element is not None:
181182
value = value_element.get("value")
182183
unit = value_element.get("unit")
183-
parameterValues[name] = (cast_func(value), unit, description) # Convert to correct type
184+
# Extract type name (remove namespace prefix)
185+
type_name = value_type.split(":")[1]
186+
if type_name == "Enumeration":
187+
type_name = "Integer" # Treat Enumeration as Integer for type purposes
188+
parameterValues[name] = (cast_func(value), SignalType[type_name], unit, description) # Convert to correct type
184189
break # Stop after first found type
185190
return parameterValues
186191

src/OMSimulatorPython/values.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
from OMSimulator.unit import Unit
33
from OMSimulator.ssm import SSM
44
from OMSimulator import namespace
5-
from OMSimulator.variable import Float32, Float64, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64
5+
from OMSimulator.variable import SignalType, Float32, Float64, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64
66

77
class Values:
88
def __init__(self):
99
self.start_values = {}
1010

11-
def setValue(self, name, value, unit=None, description=None):
11+
def setValue(self, name, value, type : SignalType | None, unit=None, description=None):
1212
if unit is not None and not isinstance(value, float):
1313
raise TypeError("Unit can only be set for Real values.")
14-
self.start_values[name] = (value, unit, description)
14+
self.start_values[name] = (value, type, unit, description)
1515

1616
def getValue(self, name):
1717
return self.start_values.get(name)
@@ -26,8 +26,11 @@ def list(self, prefix = ""):
2626
if self.empty():
2727
return
2828

29-
for key, (value, unit, description) in self.start_values.items():
30-
type_tag = self._getVariableType(value)
29+
for key, (value, type, unit, description) in self.start_values.items():
30+
if type:
31+
type_tag = type.name
32+
else:
33+
type_tag = self._getVariableType(value)
3134
print(f"{prefix} ({type_tag} {key}, {str(value.value if hasattr(value, 'value') else value)}, {unit}, '{description}')")
3235

3336
def exportToSSD(self, node, parameterMapping : SSM | None = None, unitDefinitions = None):
@@ -64,7 +67,7 @@ def exportToSSV(self, node):
6467

6568
def add_parameters(self, parameters_node, prefix = None):
6669
"""Generic function to add XML parameters based on the value type."""
67-
for key, (value, unit, description) in self.start_values.items():
70+
for key, (value, type, unit, description) in self.start_values.items():
6871
parameter_node = ET.SubElement(parameters_node, namespace.tag("ssv", "Parameter"))
6972
if prefix:
7073
parameter_node.set("name", str(prefix) + "." + str(key))
@@ -73,7 +76,10 @@ def add_parameters(self, parameters_node, prefix = None):
7376
if description:
7477
parameter_node.set("description", description)
7578

76-
type_tag = self._getVariableType(value)
79+
if type:
80+
type_tag = type.name
81+
else:
82+
type_tag = self._getVariableType(value)
7783

7884
parameter_type = ET.SubElement(parameter_node, namespace.tag("ssv", type_tag))
7985
value_ = str(value.value if hasattr(value, 'value') else value)

testsuite/api/deleteResources1.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
## win: yes
66
## mac: yes
77

8-
from OMSimulator import SSP, CRef, Settings, SSV
8+
from OMSimulator import SSP, CRef, Settings, SSV, SignalType
99

1010
Settings.suppressPath = True
1111

@@ -21,15 +21,15 @@
2121
component2 = model.addComponent(CRef('default', 'Add2'), 'resources/Add.fmu')
2222

2323
ssv1 = SSV()
24-
ssv1.setValue("k1", 2.0, "m")
25-
ssv1.setValue("k2", 3.0, "kg")
26-
ssv1.setValue("param3", "hello")
24+
ssv1.setValue("k1", 2.0, SignalType.Real, "m")
25+
ssv1.setValue("k2", 3.0, SignalType.Real, "kg")
26+
ssv1.setValue("param3", "hello", SignalType.String)
2727
ssv1.export("delete3.ssv")
2828

2929
ssv2 = SSV()
30-
ssv2.setValue("k1", 200.0, "m")
31-
ssv2.setValue("k2", 300.0, "kg")
32-
ssv2.setValue("param3", "ssp")
30+
ssv2.setValue("k1", 200.0, SignalType.Real, "m")
31+
ssv2.setValue("k2", 300.0, SignalType.Real, "kg")
32+
ssv2.setValue("param3", "ssp", SignalType.String)
3333
ssv2.export("delete4.ssv")
3434

3535
## add delete1.ssv to to ssp resources

testsuite/api/listSSVReference1.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
## win: yes
66
## mac: yes
77

8-
from OMSimulator import SSP, CRef, Settings, SSV
8+
from OMSimulator import SSP, CRef, Settings, SSV, SignalType
99

1010
Settings.suppressPath = True
1111

@@ -26,15 +26,15 @@
2626
model.addComponent(CRef('default', 'sub-system', 'Add3'), 'resources/Add.fmu')
2727

2828
ssv1 = SSV()
29-
ssv1.setValue("k1", 2.0, "m")
30-
ssv1.setValue("k2", 3.0, "kg")
31-
ssv1.setValue("param3", "hello")
29+
ssv1.setValue("k1", 2.0, SignalType.Real, "m")
30+
ssv1.setValue("k2", 3.0, SignalType.Real, "kg")
31+
ssv1.setValue("param3", "hello", SignalType.String)
3232
ssv1.export("list1.ssv")
3333

3434
ssv2 = SSV()
35-
ssv2.setValue("k1", 200.0, "m")
36-
ssv2.setValue("k2", 300.0, "kg")
37-
ssv2.setValue("param3", "ssp")
35+
ssv2.setValue("k1", 200.0, SignalType.Real, "m")
36+
ssv2.setValue("k2", 300.0, SignalType.Real, "kg")
37+
ssv2.setValue("param3", "ssp", SignalType.String)
3838
ssv2.export("list2.ssv")
3939

4040
## add list1.ssv to to ssp resources

testsuite/api/setValueSSV1.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,25 @@
55
## win: yes
66
## mac: yes
77

8-
from OMSimulator import SSP, CRef, Settings, SSV
8+
from OMSimulator import SSP, CRef, Settings, SSV, SignalType
99

1010
Settings.suppressPath = True
1111

1212
# This example creates a new SSV file and set some parameter values and export it to filesystem
1313

1414
ssv1 = SSV()
15-
ssv1.setValue("k1", 2.0, "m")
16-
ssv1.setValue("k2", 3.0)
17-
ssv1.setValue("k3", 3)
18-
ssv1.setValue("k4", False)
19-
ssv1.setValue("param3", "hello")
15+
ssv1.setValue("k1", 2.0, SignalType.Real, "m")
16+
ssv1.setValue("k2", 3.0, SignalType.Real)
17+
ssv1.setValue("k3", 3, SignalType.Integer)
18+
ssv1.setValue("k4", False, SignalType.Boolean)
19+
ssv1.setValue("param3", "hello", SignalType.String)
2020
ssv1.export("myfile1.ssv")
2121

2222

2323
ssv2 = SSV()
24-
ssv2.setValue("k1", 20.0, "m")
25-
ssv2.setValue("k2", 30.0, "kg")
26-
ssv2.setValue("param3", "helloSSP")
24+
ssv2.setValue("k1", 20.0, SignalType.Real, "m")
25+
ssv2.setValue("k2", 30.0, SignalType.Real, "kg")
26+
ssv2.setValue("param3", "helloSSP", SignalType.String)
2727
ssv2.export("myfile2.ssv")
2828

2929
files = ["myfile1.ssv", "myfile2.ssv"]

testsuite/api/setValueSSV2.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
## win: yes
66
## mac: yes
77

8-
from OMSimulator import SSP, CRef, Settings, SSV
8+
from OMSimulator import SSP, CRef, Settings, SSV, SignalType
99

1010
Settings.suppressPath = True
1111

@@ -19,15 +19,15 @@
1919
component2 = model.addComponent(CRef('default', 'Add2'), 'resources/Add.fmu')
2020

2121
ssv1 = SSV()
22-
ssv1.setValue("k1", 2.0, "m")
23-
ssv1.setValue("k2", 3.0, "kg")
24-
ssv1.setValue("param3", "hello")
22+
ssv1.setValue("k1", 2.0, SignalType.Real, "m")
23+
ssv1.setValue("k2", 3.0, SignalType.Real, "kg")
24+
ssv1.setValue("param3", "hello", SignalType.String)
2525
ssv1.export("myfile3.ssv")
2626

2727
ssv2 = SSV()
28-
ssv2.setValue("k1", 200.0, "m")
29-
ssv2.setValue("k2", 300.0, "kg")
30-
ssv2.setValue("param3", "ssp")
28+
ssv2.setValue("k1", 200.0, SignalType.Real, "m")
29+
ssv2.setValue("k2", 300.0, SignalType.Real, "kg")
30+
ssv2.setValue("param3", "ssp", SignalType.String)
3131
ssv2.export("myfile4.ssv")
3232

3333
## add myfile1.ssv to to ssp resources

0 commit comments

Comments
 (0)