fmu-manipulation-toolbox 1.8__py3-none-any.whl → 1.8.2.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fmu_manipulation_toolbox/__version__.py +1 -1
- fmu_manipulation_toolbox/assembly.py +233 -126
- fmu_manipulation_toolbox/cli.py +7 -4
- fmu_manipulation_toolbox/fmu_container.py +60 -27
- fmu_manipulation_toolbox/fmu_operations.py +2 -3
- fmu_manipulation_toolbox/gui.py +356 -103
- fmu_manipulation_toolbox/gui_style.py +129 -0
- fmu_manipulation_toolbox/resources/container.png +0 -0
- fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
- fmu_manipulation_toolbox/resources/fmu.png +0 -0
- fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png +0 -0
- fmu_manipulation_toolbox/resources/icon_fmu.png +0 -0
- fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox/resources/mask.png +0 -0
- fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
- fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dev2.dist-info}/METADATA +8 -3
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dev2.dist-info}/RECORD +25 -21
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dev2.dist-info}/WHEEL +1 -1
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dev2.dist-info}/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dev2.dist-info}/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dev2.dist-info}/top_level.txt +0 -0
|
@@ -74,9 +74,12 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
74
74
|
def __init__(self, filename):
|
|
75
75
|
self.fmu = FMU(filename)
|
|
76
76
|
self.name = Path(filename).name
|
|
77
|
+
self.id = Path(filename).stem
|
|
77
78
|
|
|
78
79
|
self.fmi_version = None
|
|
79
80
|
self.step_size = None
|
|
81
|
+
self.start_time = None
|
|
82
|
+
self.stop_time = None
|
|
80
83
|
self.model_identifier = None
|
|
81
84
|
self.guid = None
|
|
82
85
|
self.ports: Dict[str, FMUPort] = {}
|
|
@@ -103,15 +106,18 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
103
106
|
for capability in self.capability_list:
|
|
104
107
|
self.capabilities[capability] = attrs.get(capability, "false")
|
|
105
108
|
|
|
106
|
-
def experiment_attrs(self, attrs):
|
|
109
|
+
def experiment_attrs(self, attrs: Dict[str, str]):
|
|
107
110
|
try:
|
|
108
111
|
self.step_size = float(attrs['stepSize'])
|
|
109
112
|
except KeyError:
|
|
110
113
|
logger.warning(f"FMU '{self.name}' does not specify preferred step size")
|
|
111
|
-
|
|
114
|
+
self.start_time = float(attrs.get("startTime", 0.0))
|
|
115
|
+
self.stop_time = float(attrs.get("stopTime", self.start_time + 1.0))
|
|
112
116
|
|
|
113
117
|
def scalar_type(self, type_name, attrs):
|
|
114
118
|
if self.current_port:
|
|
119
|
+
if type_name == "Enumeration":
|
|
120
|
+
type_name = "Integer"
|
|
115
121
|
self.current_port.set_port_type(type_name, attrs)
|
|
116
122
|
self.current_port = None
|
|
117
123
|
|
|
@@ -148,7 +154,7 @@ class ContainerPort:
|
|
|
148
154
|
|
|
149
155
|
class Local:
|
|
150
156
|
def __init__(self, cport_from: ContainerPort):
|
|
151
|
-
self.name = cport_from.fmu.
|
|
157
|
+
self.name = cport_from.fmu.id + "." + cport_from.port.name # strip .fmu suffix
|
|
152
158
|
self.cport_from = cport_from
|
|
153
159
|
self.cport_to_list: List[ContainerPort] = []
|
|
154
160
|
self.vr = None
|
|
@@ -195,6 +201,9 @@ class FMUContainer:
|
|
|
195
201
|
|
|
196
202
|
self.description_pathname = description_pathname
|
|
197
203
|
|
|
204
|
+
self.start_time = None
|
|
205
|
+
self.stop_time = None
|
|
206
|
+
|
|
198
207
|
# Rules
|
|
199
208
|
self.inputs: Dict[str, ContainerPort] = {}
|
|
200
209
|
self.outputs: Dict[str, ContainerPort] = {}
|
|
@@ -291,7 +300,7 @@ class FMUContainer:
|
|
|
291
300
|
|
|
292
301
|
self.start_values[cport] = value
|
|
293
302
|
|
|
294
|
-
def find_input(self, port_to_connect: FMUPort) ->
|
|
303
|
+
def find_input(self, port_to_connect: FMUPort) -> Optional[ContainerPort]:
|
|
295
304
|
for fmu in self.execution_order:
|
|
296
305
|
for port in fmu.ports.values():
|
|
297
306
|
if (port.causality == 'input' and port.name == port_to_connect.name
|
|
@@ -299,13 +308,18 @@ class FMUContainer:
|
|
|
299
308
|
return ContainerPort(fmu, port.name)
|
|
300
309
|
return None
|
|
301
310
|
|
|
302
|
-
def add_implicit_rule(self, auto_input
|
|
311
|
+
def add_implicit_rule(self, auto_input=True, auto_output=True, auto_link=True, auto_parameter=False):
|
|
303
312
|
# Auto Link outputs
|
|
304
313
|
for fmu in self.execution_order:
|
|
305
314
|
for port_name in fmu.ports:
|
|
306
315
|
cport = ContainerPort(fmu, port_name)
|
|
307
316
|
if cport not in self.rules:
|
|
308
|
-
if cport.port.causality == '
|
|
317
|
+
if cport.port.causality == 'parameter' and auto_parameter:
|
|
318
|
+
parameter_name = cport.fmu.model_identifier+"."+cport.port.name
|
|
319
|
+
logger.info(f"AUTO PARAMETER: {cport} as {parameter_name}")
|
|
320
|
+
self.inputs[parameter_name] = cport
|
|
321
|
+
self.mark_ruled(cport, 'PARAMETER')
|
|
322
|
+
elif cport.port.causality == 'output':
|
|
309
323
|
candidate_cport = self.find_input(cport.port)
|
|
310
324
|
if auto_link and candidate_cport:
|
|
311
325
|
local = Local(cport)
|
|
@@ -314,11 +328,15 @@ class FMUContainer:
|
|
|
314
328
|
self.mark_ruled(cport, 'LINK')
|
|
315
329
|
self.mark_ruled(candidate_cport, 'LINK')
|
|
316
330
|
self.locals[cport] = local
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
331
|
+
elif auto_output:
|
|
332
|
+
self.mark_ruled(cport, 'OUTPUT')
|
|
333
|
+
self.outputs[port_name] = cport
|
|
334
|
+
logger.info(f"AUTO OUTPUT: Expose {cport}")
|
|
335
|
+
elif cport.port.causality == 'local' and port_name.startswith("container."):
|
|
336
|
+
local_portname = "container." + cport.fmu.id + "." + port_name[10:]
|
|
337
|
+
self.mark_ruled(cport, 'OUTPUT')
|
|
338
|
+
self.outputs[local_portname] = cport
|
|
339
|
+
logger.info(f"PROFILING: Expose {cport}")
|
|
322
340
|
|
|
323
341
|
if auto_input:
|
|
324
342
|
# Auto link inputs
|
|
@@ -346,31 +364,26 @@ class FMUContainer:
|
|
|
346
364
|
|
|
347
365
|
return step_size
|
|
348
366
|
|
|
349
|
-
def sanity_check(self, step_size:
|
|
350
|
-
nb_error = 0
|
|
367
|
+
def sanity_check(self, step_size: Optional[float]):
|
|
351
368
|
for fmu in self.execution_order:
|
|
352
369
|
if not fmu.step_size:
|
|
353
370
|
continue
|
|
354
371
|
ts_ratio = step_size / fmu.step_size
|
|
355
372
|
if ts_ratio < 1.0:
|
|
356
|
-
logger.
|
|
357
|
-
|
|
373
|
+
logger.warning(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
|
|
374
|
+
f"step_size={fmu.step_size}s.")
|
|
358
375
|
if ts_ratio != int(ts_ratio):
|
|
359
|
-
logger.
|
|
360
|
-
|
|
376
|
+
logger.warning(f"Container step_size={step_size}s should divisible by FMU '{fmu.name}' "
|
|
377
|
+
f"step_size={fmu.step_size}s.")
|
|
361
378
|
for port_name in fmu.ports:
|
|
362
379
|
cport = ContainerPort(fmu, port_name)
|
|
363
380
|
if cport not in self.rules:
|
|
364
381
|
if cport.port.causality == 'input':
|
|
365
382
|
logger.error(f"{cport} is not connected")
|
|
366
|
-
nb_error += 1
|
|
367
383
|
if cport.port.causality == 'output':
|
|
368
384
|
logger.warning(f"{cport} is not connected")
|
|
369
385
|
|
|
370
|
-
|
|
371
|
-
raise FMUContainerError(f"Some ports are not connected.")
|
|
372
|
-
|
|
373
|
-
def make_fmu(self, fmu_filename: Union[str, Path], step_size: Union[float, None] = None, debug=False, mt=False,
|
|
386
|
+
def make_fmu(self, fmu_filename: Union[str, Path], step_size: Optional[float] = None, debug=False, mt=False,
|
|
374
387
|
profiling=False):
|
|
375
388
|
if isinstance(fmu_filename, str):
|
|
376
389
|
fmu_filename = Path(fmu_filename)
|
|
@@ -411,6 +424,18 @@ class FMUContainer:
|
|
|
411
424
|
if fmu.capabilities[capability] == "true":
|
|
412
425
|
capabilities[capability] = "true"
|
|
413
426
|
|
|
427
|
+
if self.start_time is None:
|
|
428
|
+
self.start_time = self.execution_order[0].start_time
|
|
429
|
+
logger.info(f"start_time={self.start_time} (deduced from '{self.execution_order[0].name}')")
|
|
430
|
+
else:
|
|
431
|
+
logger.info(f"start_time={self.start_time}")
|
|
432
|
+
|
|
433
|
+
if self.stop_time is None:
|
|
434
|
+
self.stop_time = self.execution_order[0].stop_time
|
|
435
|
+
logger.info(f"stop_time={self.stop_time} (deduced from '{self.execution_order[0].name}')")
|
|
436
|
+
else:
|
|
437
|
+
logger.info(f"stop_time={self.stop_time}")
|
|
438
|
+
|
|
414
439
|
xml_file.write(f"""<?xml version="1.0" encoding="ISO-8859-1"?>
|
|
415
440
|
<fmiModelDescription
|
|
416
441
|
fmiVersion="2.0"
|
|
@@ -439,14 +464,14 @@ class FMUContainer:
|
|
|
439
464
|
<Category name="fmucontainer"/>
|
|
440
465
|
</LogCategories>
|
|
441
466
|
|
|
442
|
-
<DefaultExperiment stepSize="{step_size}"/>
|
|
467
|
+
<DefaultExperiment stepSize="{step_size}" startTime="{self.start_time}" stopTime="{self.stop_time}"/>
|
|
443
468
|
|
|
444
469
|
<ModelVariables>
|
|
445
470
|
""")
|
|
446
471
|
if profiling:
|
|
447
472
|
for fmu in self.execution_order:
|
|
448
473
|
vr = vr_table.add_vr("Real")
|
|
449
|
-
name = f"container.{fmu.
|
|
474
|
+
name = f"container.{fmu.id}.rt_ratio"
|
|
450
475
|
print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
|
|
451
476
|
f'<Real /></ScalarVariable>', file=xml_file)
|
|
452
477
|
|
|
@@ -596,6 +621,14 @@ class FMUContainer:
|
|
|
596
621
|
for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
|
|
597
622
|
print(f"{vr} {cport.port.vr}", file=txt_file)
|
|
598
623
|
|
|
624
|
+
@staticmethod
|
|
625
|
+
def long_path(path: Union[str, Path]) -> str:
|
|
626
|
+
# https://stackoverflow.com/questions/14075465/copy-a-file-with-a-too-long-path-to-another-directory-in-python
|
|
627
|
+
if os.name == 'nt':
|
|
628
|
+
return "\\\\?\\" + os.path.abspath(str(path))
|
|
629
|
+
else:
|
|
630
|
+
return path
|
|
631
|
+
|
|
599
632
|
def make_fmu_skeleton(self, base_directory: Path) -> Path:
|
|
600
633
|
logger.debug(f"Initialize directory '{base_directory}'")
|
|
601
634
|
|
|
@@ -622,8 +655,8 @@ class FMUContainer:
|
|
|
622
655
|
shutil.copy(library_filename, binary_directory / f"{self.identifier}.dll")
|
|
623
656
|
|
|
624
657
|
for fmu in self.involved_fmu.values():
|
|
625
|
-
shutil.copytree(fmu.fmu.tmp_directory,
|
|
626
|
-
|
|
658
|
+
shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
|
|
659
|
+
self.long_path(resources_directory / fmu.name), dirs_exist_ok=True)
|
|
627
660
|
return resources_directory
|
|
628
661
|
|
|
629
662
|
def make_fmu_package(self, base_directory: Path, fmu_filename: Path):
|
|
@@ -631,7 +664,7 @@ class FMUContainer:
|
|
|
631
664
|
with zipfile.ZipFile(self.fmu_directory / fmu_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
|
632
665
|
for root, dirs, files in os.walk(base_directory):
|
|
633
666
|
for file in files:
|
|
634
|
-
zip_file.write(os.path.join(root, file),
|
|
667
|
+
zip_file.write(self.long_path(os.path.join(root, file)),
|
|
635
668
|
os.path.relpath(os.path.join(root, file), base_directory))
|
|
636
669
|
logger.info(f"'{fmu_filename}' is available.")
|
|
637
670
|
|
|
@@ -89,9 +89,8 @@ class Manipulation:
|
|
|
89
89
|
self.remove_port(attrs['name'])
|
|
90
90
|
else:
|
|
91
91
|
self.keep_port(attrs['name'])
|
|
92
|
-
else:
|
|
92
|
+
else: # Keep ScalarVariable as it is.
|
|
93
93
|
self.keep_port(attrs['name'])
|
|
94
|
-
self.skip_until = name # do not read inner tags
|
|
95
94
|
elif name == 'CoSimulation':
|
|
96
95
|
self.operation.cosimulation_attrs(attrs)
|
|
97
96
|
elif name == 'DefaultExperiment':
|
|
@@ -100,7 +99,7 @@ class Manipulation:
|
|
|
100
99
|
self.operation.fmi_attrs(attrs)
|
|
101
100
|
elif name == 'Unknown':
|
|
102
101
|
self.unknown_attrs(attrs)
|
|
103
|
-
elif name in ('Real', 'Integer', 'String', 'Boolean'):
|
|
102
|
+
elif name in ('Real', 'Integer', 'String', 'Boolean', 'Enumeration'):
|
|
104
103
|
self.operation.scalar_type(name, attrs)
|
|
105
104
|
|
|
106
105
|
except ManipulationSkipTag:
|