diff --git a/src/sim_explorer/case.py b/src/sim_explorer/case.py index a154c13..1ddb245 100644 --- a/src/sim_explorer/case.py +++ b/src/sim_explorer/case.py @@ -548,6 +548,7 @@ def do_actions(_t: float, _a, _iter, time: int, record: bool = True): self.res.add(time / self.cases.timefac, a.args[0], a.args[1], a.args[2], a()) self.cases.simulator.reset() + if dump is not None: self.res.save(dump) diff --git a/src/sim_explorer/json5.py b/src/sim_explorer/json5.py index f1a6c07..4f32cbb 100644 --- a/src/sim_explorer/json5.py +++ b/src/sim_explorer/json5.py @@ -421,6 +421,8 @@ def _value(self): elif isinstance(v, str) and not len(v): # might be empty due to trailing ',' return "" + if q2 >= 0: # explicitly quoted values are treated as strings! + return str(v) try: return int(v) # type: ignore except Exception: diff --git a/src/sim_explorer/simulator_interface.py b/src/sim_explorer/simulator_interface.py index 8b87827..9de7e30 100644 --- a/src/sim_explorer/simulator_interface.py +++ b/src/sim_explorer/simulator_interface.py @@ -66,8 +66,11 @@ class SimulatorInterface: description (str)="": Optional possibility to provide a system description simulator (CosimExecution)=None: Optional possibility to insert an existing simulator object. Otherwise this is generated through CosimExecution.from_osp_config_file(). - log_level (CosimLogLevel): Per default the level is set to FATAL, - but it can be set to TRACE, DEBUG, INFO, WARNING, ERROR or FATAL (e.g. for debugging purposes) + log_level (str) = 'fatal': Per default the level is set to 'fatal', + but it can be set to 'trace', 'debug', 'info', 'warning', 'error' or 'fatal' (e.g. for debugging purposes) + fmus_available (bool) = False: Optional possibility to state that all FMUs shall be explicitly available, + and the interface object is thus able to attain all interface information from the modelDescription files. + The OSP interface has such information pre-loaded and the FMUs are thus not explicitly necessary. """ def __init__( @@ -76,12 +79,13 @@ def __init__( name: str | None = None, description: str = "", simulator: CosimExecution | None = None, - log_level: CosimLogLevel = CosimLogLevel.FATAL, + log_level: str = 'fatal', + fmus_available: bool = False, ): self.name = name # overwrite if the system includes that self.description = description # overwrite if the system includes that self.sysconfig: Path | None = None - log_output_level(log_level) + log_output_level(CosimLogLevel[log_level.upper()]) self.simulator: CosimExecution if simulator is None: # instantiate the simulator through the system config file self.sysconfig = Path(system) diff --git a/src/sim_explorer/utils/osp.py b/src/sim_explorer/utils/osp.py index f42f778..1b4acb4 100644 --- a/src/sim_explorer/utils/osp.py +++ b/src/sim_explorer/utils/osp.py @@ -2,6 +2,7 @@ from pathlib import Path from sim_explorer.json5 import Json5 +from component_model.utils.xml import read_xml # ========================================== @@ -179,7 +180,6 @@ def make_connection(main: str, sub1: str, attr1: dict, sub2: str, attr2: dict): tree.write(file, encoding="utf-8") return file - def osp_system_structure_from_js5(file: Path, dest: Path | None = None): """Make a OspSystemStructure file from a js5 specification. The js5 specification is closely related to the make_osp_systemStructure() function (and uses it). @@ -206,3 +206,47 @@ def osp_system_structure_from_js5(file: Path, dest: Path | None = None): ) return ss + +def read_system_structure_xml( file: Path): + """Read the system structure in xml format and return as js5 dict, similar to ..._from_js5.""" + def type_value( typ:str, value:str): + return {'Real': float, 'Integer': int, 'Boolean': bool, 'String': str}[typ]( value) + el = read_xml( file) + assert el.tag.endswith("OspSystemStructure"), f" expected. Found {el.tag}" + ns = el.tag.split("{")[1].split("}")[0] + bss = el.find(".//BaseStepSize") or 0.01 + header = { + "xmlns" : ns, + "version" : el.get( "version", "'0.1'"), + "StartTime": el.find(".//StartTime") or 0.0, + "Algorithm": el.find(".//Algorithm") or 'fixedStep', + "BaseStepSize": bss, + } + + simulators : dict = {} + for sim in el.findall(".//{*}Simulator"): + props = { + "source" : sim.get('source'), + "stepSize" : sim.get('stepSize', bss), + } + for initial in sim.findall(".//{*}InitialValue"): + props.update( { initial.get('variable') : type_value( initial[0].tag.split('}')[1], initial[0].get('value'))}) + simulators.update( {sim.get('name') : props}) + + structure = {"header" : header, "Simulators" : simulators} + connections = {} + for c in ("Variable", "Signal", "Group", "SignalGroup"): + cons = [] + for con in el.findall(".//{*}"+c+"Connection"): + assert len(con) == 2, f"Two sub-elements expected. Found {len(con)}" + props = [] + for i in range(2): + for p in con[i].attrib.values(): + props.append( p) + cons.append( props) + if len( cons): + connections.update( {"Connections"+c : cons}) + if len(connections): + structure.update( connections) + + return structure \ No newline at end of file diff --git a/tests/data/Oscillator/HarmonicOscillator.fmu b/tests/data/Oscillator/HarmonicOscillator.fmu index d2974bf..4c69a35 100644 Binary files a/tests/data/Oscillator/HarmonicOscillator.fmu and b/tests/data/Oscillator/HarmonicOscillator.fmu differ diff --git a/tests/data/crane_table.js5 b/tests/data/crane_table.js5 deleted file mode 100644 index 46812d6..0000000 --- a/tests/data/crane_table.js5 +++ /dev/null @@ -1,16 +0,0 @@ -{ -header : { - xmlns : "http://opensimulationplatform.com/MSMI/OSPSystemStructure", - version : "0.1", - StartTime : 0.0, - BaseStepSize : 0.01, - }, -Simulators : { - simpleTable : {source: "SimpleTable.fmu", interpolate: True}, - mobileCrane : {source: "MobileCrane.fmu" stepSize: 0.01, - pedestal.pedestalMass: 5000.0, boom.boom[0]: 20.0}, - }, -ConnectionsVariable : [ - ["simpleTable", "outputs[0]", "mobileCrane", "pedestal.angularVelocity"], - ], -} \ No newline at end of file