Skip to content

Commit f10e98b

Browse files
authored
add prefix to nested systems to avoid name conflicts (#1574)
* add connector path to mapped_crefs * add component connectors to mapped_cref to resolve connections * use full cref to map * use full cref to map and setValue * validate_cref and raise error * use more robust approach to search for mapped crefs * add connectors_info from componenttable * use exportName on top level connectors * check for signals not present in connectors * allow "-" to valid ident * set alias name for top level system and subsystem connectors
1 parent 6c2bc65 commit f10e98b

18 files changed

Lines changed: 774 additions & 52 deletions

File tree

include/OMSimulator/OMSimulator.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ OMSAPI oms_status_enu_t OMSCALL oms_addResources(const char* cref, const char* p
6969
OMSAPI oms_status_enu_t OMSCALL oms_addSignalsToResults(const char* cref, const char* regex);
7070
OMSAPI oms_status_enu_t OMSCALL oms_addStaticValueIndicator(const char* signal, double lower, double upper, double stepSize);
7171
OMSAPI oms_status_enu_t OMSCALL oms_addSubModel(const char* cref, const char* fmuPath);
72-
OMSAPI oms_status_enu_t OMSCALL oms_setExportName(const char* cref, const char* exportName); // set export name for a submodel
72+
OMSAPI oms_status_enu_t OMSCALL oms_setExportName(const char* cref, const char* exportName); // set export name for a submodel, system and connectors
73+
OMSAPI oms_status_enu_t OMSCALL oms_setAliasName(const char* cref, const char* aliasName); // set alias name for top level system and sub-system connectors
7374
OMSAPI oms_status_enu_t OMSCALL oms_addSystem(const char* cref, oms_system_enu_t type);
7475
OMSAPI oms_status_enu_t OMSCALL oms_addTimeIndicator(const char* signal);
7576
OMSAPI int OMSCALL oms_compareSimulationResults(const char* filenameA, const char* filenameB, const char* varA, const char* varB, double relTol, double absTol);

src/OMSimulatorLib/ComRef.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
#include <regex>
3838
#include <cstring>
3939

40-
const std::regex re_ident("^[a-zA-Z][a-zA-Z0-9_]*$");
40+
const std::regex re_ident("^[a-zA-Z][a-zA-Z0-9_-]*$");
4141

4242
oms::ComRef::ComRef()
4343
{

src/OMSimulatorLib/Connector.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ namespace oms
7171
void setOwner(const oms::ComRef& owner);
7272
void setGeometry(const oms::ssd::ConnectorGeometry* newGeometry);
7373
oms_status_enu_t setExportName(const std::string & exportName) { this->exportName = exportName; return oms_status_ok;};
74+
oms_status_enu_t setAliasName(const std::string & aliasName) { this->aliasName = aliasName; return oms_status_ok;}; // to be used in the result file to avoid name conflicts
7475
oms_status_enu_t setNumericType(oms_signal_numeric_type_enu_t numericType) { this->numericType = numericType; return oms_status_ok; }
7576
oms_signal_numeric_type_enu_t getNumericType() const { return this->numericType; }
7677
std::string getExportName() const { return this->exportName; }
78+
std::string getAliasName() const { return this->aliasName; }
7779

7880
std::map<std::string, std::map<std::string, std::string>> connectorUnits; ///< single entry map which contains unit as key and BaseUnits as value for a connector
7981
std::map<std::string, std::string> enumerationName; ///< single entry map which contains connector name as key and enumerationName as value for a connector of type ssc:Enumeration
@@ -99,7 +101,8 @@ namespace oms
99101
private:
100102
friend bool operator==(const Connector& v1, const Connector& v2);
101103
friend bool operator!=(const Connector& v1, const Connector& v2);
102-
std::string exportName; ///< name to be used in result file
104+
std::string exportName = ""; ///< name to be used in result file
105+
std::string aliasName = ""; ///< connector name defined in the ssp file
103106
oms_signal_numeric_type_enu_t numericType = oms_signal_numeric_type_none; ///< numeric type for the connector for FMI 3.0
104107
};
105108

src/OMSimulatorLib/OMSimulator.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,23 @@ oms_status_enu_t oms_setExportName(const char* cref, const char* exportName)
796796
return system->setExportName(tail, exportName);
797797
}
798798

799+
oms_status_enu_t oms_setAliasName(const char* cref, const char* aliasName)
800+
{
801+
oms::ComRef tail(cref);
802+
oms::ComRef front = tail.pop_front();
803+
804+
oms::Model* model = oms::Scope::GetInstance().getModel(front);
805+
if (!model)
806+
return logError_ModelNotInScope(front);
807+
808+
front = tail.pop_front();
809+
oms::System* system = model->getSystem(front);
810+
if (!system)
811+
return logError_SystemNotInModel(model->getCref(), front);
812+
813+
return system->setAliasName(tail, aliasName);
814+
}
815+
799816
oms_status_enu_t oms_activateVariant(const char* crefA, const char* crefB)
800817
{
801818
oms::ComRef tail(crefA);

src/OMSimulatorLib/System.cpp

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,20 @@ oms_status_enu_t oms::System::setExportName(const oms::ComRef& cref, const std::
360360
return oms_status_ok;
361361
}
362362

363+
oms_status_enu_t oms::System::setAliasName(const oms::ComRef& cref, const std::string& aliasName)
364+
{
365+
auto component = getComponent(cref);
366+
if (component)
367+
return component->setExportName(aliasName);
368+
369+
// set export name for top level system and subsystem connectors
370+
auto connector = getConnector(cref);
371+
if (connector)
372+
return connector->setAliasName(aliasName);
373+
374+
return oms_status_ok;
375+
}
376+
363377
std::string oms::System::getFmiVersion(const std::string& path)
364378
{
365379
// unpack the modelDescription.xml in memory to detect the fmiVersion
@@ -2397,13 +2411,6 @@ oms_status_enu_t oms::System::registerSignalsForResultFile(ResultWriter& resultF
23972411

23982412
resultFileMapping.clear();
23992413

2400-
// check for exportName, to be used in result file to map the variable to the correct signal in ssp
2401-
std::string name;
2402-
if (!exportName.empty())
2403-
name = this->exportName;
2404-
else
2405-
name = getFullCref();
2406-
24072414
for (unsigned int i=0; i<connectors.size(); ++i)
24082415
{
24092416
if (!connectors[i])
@@ -2416,23 +2423,23 @@ oms_status_enu_t oms::System::registerSignalsForResultFile(ResultWriter& resultF
24162423
// check for exportName, to be used in result file to map the variable to the correct signal in ssp
24172424
std::string name;
24182425
if (!connector->getExportName().empty())
2419-
name = connector->getExportName().c_str();
2426+
name = std::string(ComRef(connector->getExportName()) + connector->getAliasName());
24202427
else
2421-
name = getFullCref().c_str();
2428+
name = std::string(getFullCref() + connector->getName());
24222429

24232430
if (oms_signal_type_real == connector->getType())
24242431
{
2425-
unsigned int ID = resultFile.addSignal(std::string(ComRef(name) + connector->getName()), "connector", SignalType_REAL);
2432+
unsigned int ID = resultFile.addSignal(name, "connector", SignalType_REAL);
24262433
resultFileMapping[ID] = i;
24272434
}
24282435
else if (oms_signal_type_integer == connector->getType())
24292436
{
2430-
unsigned int ID = resultFile.addSignal(std::string(ComRef(name) + connector->getName()), "connector", SignalType_INT);
2437+
unsigned int ID = resultFile.addSignal(name, "connector", SignalType_INT);
24312438
resultFileMapping[ID] = i;
24322439
}
24332440
else if (oms_signal_type_boolean == connector->getType())
24342441
{
2435-
unsigned int ID = resultFile.addSignal(std::string(ComRef(name) + connector->getName()), "connector", SignalType_BOOL);
2442+
unsigned int ID = resultFile.addSignal(name, "connector", SignalType_BOOL);
24362443
resultFileMapping[ID] = i;
24372444
}
24382445
}

src/OMSimulatorLib/System.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ namespace oms
8888
oms_status_enu_t addSubSystem(const ComRef& cref, oms_system_enu_t type);
8989
oms_status_enu_t addSubModel(const ComRef& cref, const std::string& fmuPath);
9090
oms_status_enu_t setExportName(const ComRef& cref, const std::string& exportName);
91+
oms_status_enu_t setAliasName(const ComRef& cref, const std::string& aliasName);
9192
oms_status_enu_t replaceSubModel(const ComRef& cref, const std::string& fmuPath, bool dryRun, int& warningCount);
9293
bool isValidScalarVariable(Component* referenceComponent, Component* replacingComponent, Connection* connection, const ComRef& crefA, const ComRef& crefB, const ComRef& signalName, const std::string& path, std::vector<std::string>& warningList);
9394
bool validCref(const ComRef& cref);

src/OMSimulatorLib/SystemWC.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -939,23 +939,23 @@ oms_status_enu_t oms::SystemWC::registerSignalsForResultFile(ResultWriter& resul
939939
// check for exportName, to be used in result file to map the variable to the correct signal in ssp
940940
std::string name;
941941
if (!connector->getExportName().empty())
942-
name = connector->getExportName().c_str();
942+
name = std::string(ComRef(connector->getExportName()) + connector->getAliasName());
943943
else
944-
name = getFullCref().c_str();
944+
name = std::string(ComRef(getFullCref()) + connector->getName());
945945

946-
if (oms_signal_type_real == connector->getType())
947-
{
948-
unsigned int ID = resultFile.addSignal(std::string(ComRef(name) + connector->getName()), "connector", SignalType_REAL);
949-
resultFileMapping[ID] = i;
950-
}
946+
if (oms_signal_type_real == connector->getType())
947+
{
948+
unsigned int ID = resultFile.addSignal(name, "connector", SignalType_REAL);
949+
resultFileMapping[ID] = i;
950+
}
951951
else if (oms_signal_type_integer == connector->getType())
952952
{
953-
unsigned int ID = resultFile.addSignal(std::string(ComRef(name) + connector->getName()), "connector", SignalType_INT);
953+
unsigned int ID = resultFile.addSignal(name, "connector", SignalType_INT);
954954
resultFileMapping[ID] = i;
955955
}
956956
else if (oms_signal_type_boolean == connector->getType())
957957
{
958-
unsigned int ID = resultFile.addSignal(std::string(ComRef(name) + connector->getName()), "connector", SignalType_BOOL);
958+
unsigned int ID = resultFile.addSignal(name, "connector", SignalType_BOOL);
959959
resultFileMapping[ID] = i;
960960
}
961961
}

src/OMSimulatorPython/capi.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ def __init__(self):
114114
self.obj.oms_setTempDirectory.restype = ctypes.c_int
115115
self.obj.oms_setExportName.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
116116
self.obj.oms_setExportName.restype = ctypes.c_int
117+
self.obj.oms_setAliasName.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
118+
self.obj.oms_setAliasName.restype = ctypes.c_int
117119
self.obj.oms_setBoolean.argtypes = [ctypes.c_char_p, ctypes.c_bool]
118120
self.obj.oms_setBoolean.restype = ctypes.c_int
119121
self.obj.oms_setInteger.argtypes = [ctypes.c_char_p, ctypes.c_int]
@@ -285,6 +287,12 @@ def setExportName(self, cref, exportName):
285287
status = self.obj.oms_setExportName(cref.encode(), exportName.encode())
286288
return Status(status)
287289

290+
def setAliasName(self, cref, aliasName):
291+
'''Set the export name for top level system and sub-system connectors
292+
This is used to specify the name under which the model will be exported.'''
293+
status = self.obj.oms_setAliasName(cref.encode(), aliasName.encode())
294+
return Status(status)
295+
288296
def setReal(self, cref, value):
289297
'''Set a real value for a model or system.'''
290298
status = self.obj.oms_setReal(cref.encode(), value)

src/OMSimulatorPython/instantiated_model.py

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,12 @@ def __init__(self, json_description, system: System, resources: dict):
110110
listofsystems = []
111111
## add components
112112
for comp in unit["components"]:
113-
comp_path = ".".join([solver_path] + [comp["name"][-1]])
113+
if len(comp["name"]) <= 2:
114+
comp_path = ".".join([solver_path] + [comp["name"][-1]])
115+
else:
116+
## add prefix to nested systems to avoid name conflicts while flattening the system during instantiation, e.g. sub-system1_Add1, sub-system1_Gain1
117+
comp_name = f"{comp['name'][-2]}_{comp['name'][-1]}"
118+
comp_path = ".".join([solver_path] + [comp_name])
114119
currentSystem = ".".join(comp["name"][:-1])
115120
if currentSystem not in listofsystems:
116121
listofsystems.append(currentSystem)
@@ -124,6 +129,10 @@ def __init__(self, json_description, system: System, resources: dict):
124129
## parse connector geometry for the component if exist and set it to capi after adding the component, this is needed for proper mapping of connector geometry
125130
if "connectors" in comp:
126131
for connector in comp["connectors"]:
132+
comp_name = ".".join(comp["name"])
133+
connector_name =".".join([comp_name]+[connector["name"]])
134+
if not connector_name in self.mappedCrefs:
135+
self.mappedCrefs[connector_name] = f"{comp_path}.{connector['name']}"
127136
if "geometry" in connector:
128137
connector_path = ".".join([comp_path, connector["name"]])
129138
geometry = connector["geometry"]
@@ -168,10 +177,16 @@ def __init__(self, json_description, system: System, resources: dict):
168177

169178
## add connections
170179
for connection in unit["connections"]:
171-
start_element = ".".join(connection['start element'])
172-
end_element = ".".join(connection['end element'])
173-
start = self.mappedCrefs[start_element] + f".{connection['start connector']}"
174-
end = self.mappedCrefs[end_element] + f".{connection['end connector']}"
180+
start_element = ".".join(connection['start element'] + [connection['start connector']])
181+
end_element = ".".join(connection['end element'] + [connection['end connector']])
182+
183+
if start_element not in self.mappedCrefs:
184+
raise KeyError(f"No mapping found for {start_element}")
185+
if end_element not in self.mappedCrefs:
186+
raise KeyError(f"No mapping found for {end_element}")
187+
188+
start = self.mappedCrefs[start_element]
189+
end = self.mappedCrefs[end_element]
175190
self.apiCall.append(f'oms_addConnection("{start}", "{end}")')
176191
status = Capi.addConnection(start, end)
177192
if status != Status.ok:
@@ -265,6 +280,27 @@ def validatePath(self, mapped_prefix : list, suffix : list):
265280

266281
return ".".join(mapped_prefix + result)
267282

283+
def validate_cref(self, systemName: str, suffix: str) -> str:
284+
## remove common path from suffix
285+
## e.g. systemName = model.root.solver2.gain1
286+
## suffix = gain1.R1.T
287+
## result = model.root.solver2.gain1.R1.T
288+
289+
a_parts = systemName.split(".")
290+
b_parts = suffix.split(".")
291+
292+
# detect overlap
293+
overlap = 0
294+
for i in range(1, min(len(a_parts), len(b_parts)) + 1):
295+
if a_parts[-i:] == b_parts[:i]:
296+
overlap = i
297+
cref = ".".join(a_parts + b_parts[overlap:])
298+
if cref in self.mappedCrefs:
299+
return self.mappedCrefs[cref]
300+
else:
301+
## it is possible some variables are not listed in connectors but it is still valid signal
302+
return self.map_cref(systemName, suffix)
303+
268304
def setStartValues(self, value: Values, systemName: str, ssm: SSM | None):
269305
if value.empty():
270306
return
@@ -280,13 +316,15 @@ def setStartValues(self, value: Values, systemName: str, ssm: SSM | None):
280316
for entry in targets:
281317
target = entry["target"]
282318
linearTransformation = entry["linearTransformation"]
283-
value_path = self.map_cref(systemName, str(target))
319+
cref = f"{systemName}.{str(target)}"
320+
value_path = self.validate_cref(systemName, str(target))
284321
if linearTransformation:
285322
source_value = source_value * float(linearTransformation.factor) + float(linearTransformation.offset)
286323
self.apply_start_value(value_path, source_value, type)
287324
else:
288325
for key, (source_value, type, _, _) in value.start_values.items():
289-
value_path = self.map_cref(systemName, str(key))
326+
cref = f"{systemName}.{str(key)}"
327+
value_path = self.validate_cref(systemName, str(key))
290328
self.apply_start_value(value_path, source_value, type)
291329

292330
def apply_start_value(self, value_path:str, value, type):
@@ -314,8 +352,14 @@ def apply_start_value(self, value_path:str, value, type):
314352

315353
def _addConnector(self, connectors, systemName):
316354
for connector in connectors:
317-
name = [systemName] + [str(connector.name)]
318-
connector_path = ".".join([self.mappedCrefs[systemName], str(connector.name)])
355+
connector_name =".".join([systemName]+[str(connector.name)])
356+
system_name = systemName.split(".")
357+
if len(system_name) == 1:
358+
connector_path = ".".join([self.mappedCrefs[systemName], str(connector.name)])
359+
else:
360+
## add prefix to nested systems to avoid name conflicts while flattening the system during instantiation, e.g. sub-system1_input1, sub-system1_param1
361+
connector_name_prefix = f"{system_name[-1]}_{str(connector.name)}" # replace '-' with '_' for sub-system name in connector path
362+
connector_path = ".".join([self.mappedCrefs[systemName], connector_name_prefix])
319363
self.apiCall.append(f'oms_addConnector("{connector_path}", "{connector.causality}", {connector.signal_type})')
320364
status = Capi.addConnector(connector_path, connector.causality.value, connector.c_signal_type.value)
321365
if status != Status.ok:
@@ -336,11 +380,14 @@ def _addConnector(self, connectors, systemName):
336380
raise RuntimeError(f"Failed to set connector geometry for {connector_path}: {status}")
337381

338382
export_name = systemName
339-
if not export_name in self.mappedCrefs:
340-
self.mappedCrefs[export_name] = connector_path
383+
if not connector_name in self.mappedCrefs:
384+
self.mappedCrefs[connector_name] = connector_path
341385
status = Capi.setExportName(connector_path, export_name) # Set export name if provided
342386
if status != Status.ok:
343387
raise RuntimeError(f"Failed to set export name: {status}")
388+
status = Capi.setAliasName(connector_path, str(connector.name)) # Set alias name for connector
389+
if status != Status.ok:
390+
raise RuntimeError(f"Failed to set alias name: {status}")
344391

345392
def addConnectorFromElements(self, elements, currentSystem):
346393
## add connectors mapped with currentSystem
@@ -359,11 +406,18 @@ def dumpApiCalls(self):
359406

360407
def setValue(self, cref: CRef, value):
361408
"""Sets a value for a specific CRef in the model."""
362-
name = ".".join(cref.names[:-1])
363-
if name not in self.mappedCrefs:
364-
raise KeyError(f"Missing required key: '{name}'")
409+
name = ".".join(cref.names)
365410

366-
value_path = ".".join([self.mappedCrefs[name], cref.names[-1]])
411+
## check for top level connectors with alias names first
412+
## e.g model.root.sub_system_input which will be in mapped crefs
413+
if name in self.mappedCrefs:
414+
value_path = self.mappedCrefs[name]
415+
else:
416+
## check for other crefs by splitting with suffixes
417+
name = ".".join(cref.names[:-1])
418+
if name not in self.mappedCrefs:
419+
raise KeyError(f"Missing required key: '{cref.names}'")
420+
value_path = ".".join([self.mappedCrefs[name], cref.names[-1]])
367421

368422
# Determine the variable type
369423
type, status = Capi.getVariableType(value_path)
@@ -387,35 +441,41 @@ def setValue(self, cref: CRef, value):
387441
raise TypeError(f"Unsupported type: {type}")
388442

389443
def _setReal(self, mapped_cref: str, value: float):
390-
self.apiCall.append(f'oms_setReal("{mapped_cref}, {value})')
444+
self.apiCall.append(f'oms_setReal("{mapped_cref}", {value})')
391445
status = Capi.setReal(mapped_cref, value) # Get the value from the CAPI
392446
if status != Status.ok:
393447
raise RuntimeError(f"Failed to set value for {mapped_cref}: {status}")
394448

395449
def _setInteger(self, mapped_cref: str, value: int):
396-
self.apiCall.append(f'oms_setInteger("{mapped_cref}, {value})')
450+
self.apiCall.append(f'oms_setInteger("{mapped_cref}", {value})')
397451
status = Capi.setInteger(mapped_cref, value) # Get the value from the CAPI
398452
if status != Status.ok:
399453
raise RuntimeError(f"Failed to set value for {mapped_cref}: {status}")
400454

401455
def _setBoolean(self, mapped_cref: str, value: bool):
402-
self.apiCall.append(f'oms_setBoolean("{mapped_cref}, {value})')
456+
self.apiCall.append(f'oms_setBoolean("{mapped_cref}", {value})')
403457
status = Capi.setBoolean(mapped_cref, value) # Get the value from the CAPI
404458
if status != Status.ok:
405459
raise RuntimeError(f"Failed to set value for {mapped_cref}: {status}")
406460

407461
def _setString(self, mapped_cref: str, value: str):
408-
self.apiCall.append(f'oms_setString("{mapped_cref}, {value})')
462+
self.apiCall.append(f'oms_setString("{mapped_cref}", {value})')
409463
status = Capi.setString(mapped_cref, value) # Get the value from the CAPI
410464
if status != Status.ok:
411465
raise RuntimeError(f"Failed to set value for {mapped_cref}: {status}")
412466

413467
def getValue(self, cref: CRef):
414-
name = ".".join(cref.names[:-1])
415-
if name not in self.mappedCrefs:
416-
raise KeyError(f"Missing required key: '{name}'")
417-
418-
value_path = ".".join([self.mappedCrefs[name], cref.names[-1]])
468+
name = ".".join(cref.names)
469+
## check for top level connectors with alias names first
470+
## e.g model.root.sub_system_input which will be in mapped crefs
471+
if name in self.mappedCrefs:
472+
value_path = self.mappedCrefs[name]
473+
else:
474+
## check for other crefs by splitting with suffixes
475+
name = ".".join(cref.names[:-1])
476+
if name not in self.mappedCrefs:
477+
raise KeyError(f"Missing required key: '{cref.names}'")
478+
value_path = ".".join([self.mappedCrefs[name], cref.names[-1]])
419479

420480
# Determine the variable type
421481
type, status = Capi.getVariableType(value_path)

0 commit comments

Comments
 (0)