fmu-manipulation-toolbox 1.8.4.2rc1__py3-none-any.whl → 1.9__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.
Files changed (51) hide show
  1. fmu_manipulation_toolbox/__init__.py +0 -1
  2. fmu_manipulation_toolbox/__main__.py +1 -1
  3. fmu_manipulation_toolbox/__version__.py +1 -1
  4. fmu_manipulation_toolbox/assembly.py +22 -13
  5. fmu_manipulation_toolbox/checker.py +16 -11
  6. fmu_manipulation_toolbox/cli/__init__.py +0 -0
  7. fmu_manipulation_toolbox/cli/fmucontainer.py +105 -0
  8. fmu_manipulation_toolbox/cli/fmusplit.py +48 -0
  9. fmu_manipulation_toolbox/cli/fmutool.py +127 -0
  10. fmu_manipulation_toolbox/cli/utils.py +36 -0
  11. fmu_manipulation_toolbox/container.py +1054 -0
  12. fmu_manipulation_toolbox/gui.py +48 -56
  13. fmu_manipulation_toolbox/gui_style.py +8 -0
  14. fmu_manipulation_toolbox/help.py +3 -0
  15. fmu_manipulation_toolbox/operations.py +577 -0
  16. fmu_manipulation_toolbox/remoting.py +107 -0
  17. fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
  18. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Annotation.xsd +51 -0
  19. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3AttributeGroups.xsd +119 -0
  20. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3BuildDescription.xsd +117 -0
  21. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3InterfaceType.xsd +80 -0
  22. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3LayeredStandardManifest.xsd +93 -0
  23. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3ModelDescription.xsd +131 -0
  24. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Terminal.xsd +87 -0
  25. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3TerminalsAndIcons.xsd +84 -0
  26. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Type.xsd +207 -0
  27. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Unit.xsd +69 -0
  28. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Variable.xsd +413 -0
  29. fmu_manipulation_toolbox/resources/fmi-3.0/fmi3VariableDependency.xsd +64 -0
  30. fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
  31. fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
  32. fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
  33. fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
  34. fmu_manipulation_toolbox/resources/linux64/server_sm +0 -0
  35. fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
  36. fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
  37. fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
  38. fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
  39. fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
  40. fmu_manipulation_toolbox/split.py +331 -0
  41. {fmu_manipulation_toolbox-1.8.4.2rc1.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/METADATA +1 -1
  42. fmu_manipulation_toolbox-1.9.dist-info/RECORD +71 -0
  43. fmu_manipulation_toolbox-1.9.dist-info/entry_points.txt +7 -0
  44. fmu_manipulation_toolbox/cli.py +0 -235
  45. fmu_manipulation_toolbox/fmu_container.py +0 -753
  46. fmu_manipulation_toolbox/fmu_operations.py +0 -489
  47. fmu_manipulation_toolbox-1.8.4.2rc1.dist-info/RECORD +0 -52
  48. fmu_manipulation_toolbox-1.8.4.2rc1.dist-info/entry_points.txt +0 -3
  49. {fmu_manipulation_toolbox-1.8.4.2rc1.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/WHEEL +0 -0
  50. {fmu_manipulation_toolbox-1.8.4.2rc1.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/licenses/LICENSE.txt +0 -0
  51. {fmu_manipulation_toolbox-1.8.4.2rc1.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/top_level.txt +0 -0
@@ -1,753 +0,0 @@
1
- import logging
2
- import os
3
- import shutil
4
- import uuid
5
- import zipfile
6
- from datetime import datetime
7
- from pathlib import Path
8
- from typing import *
9
-
10
- from .fmu_operations import FMU, OperationAbstract
11
- from .version import __version__ as tool_version
12
-
13
-
14
- logger = logging.getLogger("fmu_manipulation_toolbox")
15
-
16
-
17
- class FMUPort:
18
- def __init__(self, attrs: Dict[str, str]):
19
- self.name = attrs["name"]
20
- self.vr = int(attrs["valueReference"])
21
- self.causality = attrs.get("causality", "local")
22
- self.attrs = attrs.copy()
23
- self.attrs.pop("name")
24
- self.attrs.pop("valueReference")
25
- if "causality" in self.attrs:
26
- self.attrs.pop("causality")
27
- self.type_name = None
28
- self.child = None
29
-
30
- def set_port_type(self, type_name: str, attrs: Dict[str, str]):
31
- self.type_name = type_name
32
- self.child = attrs.copy()
33
- for unsupported in ("unit", "declaredType"):
34
- if unsupported in self.child:
35
- self.child.pop(unsupported)
36
-
37
- def xml(self, vr: int, name=None, causality=None, start=None):
38
-
39
- if self.child is None:
40
- raise FMUContainerError(f"FMUPort has no child. Bug?")
41
-
42
- child_str = f"<{self.type_name}"
43
- if self.child:
44
- if start is not None and 'start' in self.child:
45
- self.child['start'] = start
46
- child_str += " " + " ".join([f'{key}="{value}"' for (key, value) in self.child.items()]) + "/>"
47
- else:
48
- child_str += "/>"
49
-
50
- if name is None:
51
- name = self.name
52
- if causality is None:
53
- causality = self.causality
54
-
55
- variability = "continuous" if self.type_name == "Real" else "discrete"
56
-
57
- scalar_attrs = {
58
- "name": name,
59
- "valueReference": vr,
60
- "causality": causality,
61
- "variability": variability,
62
- }
63
- scalar_attrs.update(self.attrs)
64
-
65
- scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in scalar_attrs.items()])
66
-
67
- return f'<ScalarVariable {scalar_attrs_str}>{child_str}</ScalarVariable>'
68
-
69
-
70
- class EmbeddedFMU(OperationAbstract):
71
- capability_list = ("needsExecutionTool",
72
- "canBeInstantiatedOnlyOncePerProcess")
73
-
74
- def __init__(self, filename):
75
- self.fmu = FMU(filename)
76
- self.name = Path(filename).name
77
- self.id = Path(filename).stem
78
-
79
- self.fmi_version = None
80
- self.step_size = None
81
- self.start_time = None
82
- self.stop_time = None
83
- self.model_identifier = None
84
- self.guid = None
85
- self.ports: Dict[str, FMUPort] = {}
86
-
87
- self.capabilities: Dict[str, str] = {}
88
- self.current_port = None # used during apply_operation()
89
-
90
- self.fmu.apply_operation(self) # Should be the last command in constructor!
91
- if self.model_identifier is None:
92
- raise FMUContainerError(f"FMU '{self.name}' does not implement Co-Simulation mode.")
93
-
94
- def fmi_attrs(self, attrs):
95
- self.guid = attrs['guid']
96
- self.fmi_version = attrs['fmiVersion']
97
-
98
- def scalar_attrs(self, attrs) -> int:
99
- self.current_port = FMUPort(attrs)
100
- self.ports[self.current_port.name] = self.current_port
101
-
102
- return 0
103
-
104
- def cosimulation_attrs(self, attrs: Dict[str, str]):
105
- self.model_identifier = attrs['modelIdentifier']
106
- for capability in self.capability_list:
107
- self.capabilities[capability] = attrs.get(capability, "false")
108
-
109
- def experiment_attrs(self, attrs: Dict[str, str]):
110
- try:
111
- self.step_size = float(attrs['stepSize'])
112
- except KeyError:
113
- logger.warning(f"FMU '{self.name}' does not specify preferred step size")
114
- self.start_time = float(attrs.get("startTime", 0.0))
115
- self.stop_time = float(attrs.get("stopTime", self.start_time + 1.0))
116
-
117
- def scalar_type(self, type_name, attrs):
118
- if self.current_port:
119
- if type_name == "Enumeration":
120
- type_name = "Integer"
121
- self.current_port.set_port_type(type_name, attrs)
122
- self.current_port = None
123
-
124
- def __repr__(self):
125
- return f"FMU '{self.name}' ({len(self.ports)} variables)"
126
-
127
-
128
- class FMUContainerError(Exception):
129
- def __init__(self, reason: str):
130
- self.reason = reason
131
-
132
- def __repr__(self):
133
- return f"{self.reason}"
134
-
135
-
136
- class ContainerPort:
137
- def __init__(self, fmu: EmbeddedFMU, port_name: str):
138
- self.fmu = fmu
139
- try:
140
- self.port = fmu.ports[port_name]
141
- except KeyError:
142
- raise FMUContainerError(f"Port '{fmu.name}/{port_name}' does not exist")
143
- self.vr = None
144
-
145
- def __repr__(self):
146
- return f"Port {self.fmu.name}/{self.port.name}"
147
-
148
- def __hash__(self):
149
- return hash(str(self))
150
-
151
- def __eq__(self, other):
152
- return str(self) == str(other)
153
-
154
-
155
- class ContainerInput:
156
- def __init__(self, name: str, cport_to: ContainerPort):
157
- self.name = name
158
- self.type_name = cport_to.port.type_name
159
- self.causality = cport_to.port.causality
160
- self.cport_list = [cport_to]
161
- self.vr = None
162
-
163
- def add_cport(self, cport_to: ContainerPort):
164
- if cport_to in self.cport_list: # Cannot be reached ! (Assembly prevent this to happen)
165
- raise FMUContainerError(f"Duplicate INPUT {cport_to} already connected to {self.name}")
166
-
167
- if cport_to.port.type_name != self.type_name:
168
- raise FMUContainerError(f"Cannot connect {self.name} of type {self.type_name} to "
169
- f"{cport_to} of type {cport_to.port.type_name}")
170
-
171
- if cport_to.port.causality != self.causality:
172
- raise FMUContainerError(f"Cannot connect {self.causality.upper()} {self.name} to "
173
- f"{cport_to.port.causality.upper()} {cport_to}")
174
-
175
- self.cport_list.append(cport_to)
176
-
177
-
178
- class Local:
179
- def __init__(self, cport_from: ContainerPort):
180
- self.name = cport_from.fmu.id + "." + cport_from.port.name # strip .fmu suffix
181
- self.cport_from = cport_from
182
- self.cport_to_list: List[ContainerPort] = []
183
- self.vr = None
184
-
185
- if not cport_from.port.causality == "output":
186
- raise FMUContainerError(f"{cport_from} is {cport_from.port.causality} instead of OUTPUT")
187
-
188
- def add_target(self, cport_to: ContainerPort):
189
- if not cport_to.port.causality == "input":
190
- raise FMUContainerError(f"{cport_to} is {cport_to.port.causality} instead of INPUT")
191
-
192
- if cport_to.port.type_name == self.cport_from.port.type_name:
193
- self.cport_to_list.append(cport_to)
194
- else:
195
- raise FMUContainerError(f"failed to connect {self.cport_from} to {cport_to} due to type.")
196
-
197
-
198
- class ValueReferenceTable:
199
- def __init__(self):
200
- self.vr_table: Dict[str, int] = {
201
- "Real": 0,
202
- "Integer": 0,
203
- "Boolean": 0,
204
- "String": 0,
205
- }
206
-
207
- def get_vr(self, cport: ContainerPort) -> int:
208
- return self.add_vr(cport.port.type_name)
209
-
210
- def add_vr(self, type_name: str) -> int:
211
- vr = self.vr_table[type_name]
212
- self.vr_table[type_name] += 1
213
- return vr
214
-
215
-
216
- class AutoWired:
217
- def __init__(self):
218
- self.rule_input = []
219
- self.rule_output = []
220
- self.rule_link = []
221
- self.nb_param = 0
222
-
223
- def __repr__(self):
224
- return (f"{self.nb_param} parameters, {len(self.rule_input) - self.nb_param} inputs,"
225
- f" {len(self.rule_output)} outputs, {len(self.rule_link)} links.")
226
-
227
- def add_input(self, from_port, to_fmu, to_port):
228
- self.rule_input.append([from_port, to_fmu, to_port])
229
-
230
- def add_parameter(self, from_port, to_fmu, to_port):
231
- self.rule_input.append([from_port, to_fmu, to_port])
232
- self.nb_param += 1
233
-
234
- def add_output(self, from_fmu, from_port, to_port):
235
- self.rule_output.append([from_fmu, from_port, to_port])
236
-
237
- def add_link(self, from_fmu, from_port, to_fmu, to_port):
238
- self.rule_link.append([from_fmu, from_port, to_fmu, to_port])
239
-
240
-
241
- class FMUContainer:
242
- def __init__(self, identifier: str, fmu_directory: Union[str, Path], description_pathname=None):
243
- self.fmu_directory = Path(fmu_directory)
244
- self.identifier = identifier
245
- if not self.fmu_directory.is_dir():
246
- raise FMUContainerError(f"{self.fmu_directory} is not a valid directory")
247
- self.involved_fmu: Dict[str, EmbeddedFMU] = {}
248
- self.execution_order: List[EmbeddedFMU] = []
249
-
250
- self.description_pathname = description_pathname
251
-
252
- self.start_time = None
253
- self.stop_time = None
254
-
255
- # Rules
256
- self.inputs: Dict[str, ContainerInput] = {}
257
- self.outputs: Dict[str, ContainerPort] = {}
258
- self.locals: Dict[ContainerPort, Local] = {}
259
-
260
- self.rules: Dict[ContainerPort, str] = {}
261
- self.start_values: Dict[ContainerPort, str] = {}
262
-
263
- def get_fmu(self, fmu_filename: str) -> EmbeddedFMU:
264
- if fmu_filename in self.involved_fmu:
265
- return self.involved_fmu[fmu_filename]
266
-
267
- try:
268
- fmu = EmbeddedFMU(self.fmu_directory / fmu_filename)
269
- self.involved_fmu[fmu.name] = fmu
270
- self.execution_order.append(fmu)
271
- if not fmu.fmi_version == "2.0":
272
- raise FMUContainerError("Only FMI-2.0 is supported by FMUContainer")
273
- logger.debug(f"Adding FMU #{len(self.execution_order)}: {fmu}")
274
- except Exception as e:
275
- raise FMUContainerError(f"Cannot load '{fmu_filename}': {e}")
276
-
277
- return fmu
278
-
279
- def mark_ruled(self, cport: ContainerPort, rule: str):
280
- if cport in self.rules:
281
- previous_rule = self.rules[cport]
282
- if rule not in ("OUTPUT", "LINK") and previous_rule not in ("OUTPUT", "LINK"):
283
- raise FMUContainerError(f"try to {rule} port {cport} which is already {previous_rule}")
284
-
285
- self.rules[cport] = rule
286
-
287
- def get_all_cports(self):
288
- return [ContainerPort(fmu, port_name) for fmu in self.execution_order for port_name in fmu.ports]
289
-
290
- def add_input(self, container_port_name: str, to_fmu_filename: str, to_port_name: str):
291
- if not container_port_name:
292
- container_port_name = to_port_name
293
- cport_to = ContainerPort(self.get_fmu(to_fmu_filename), to_port_name)
294
- if cport_to.port.causality not in ("input", "parameter"): # check causality
295
- raise FMUContainerError(f"Tried to use '{cport_to}' as INPUT of the container but FMU causality is "
296
- f"'{cport_to.port.causality}'.")
297
-
298
- try:
299
- input_port = self.inputs[container_port_name]
300
- input_port.add_cport(cport_to)
301
- except KeyError:
302
- self.inputs[container_port_name] = ContainerInput(container_port_name, cport_to)
303
-
304
- logger.debug(f"INPUT: {to_fmu_filename}:{to_port_name}")
305
- self.mark_ruled(cport_to, 'INPUT')
306
-
307
- def add_output(self, from_fmu_filename: str, from_port_name: str, container_port_name: str):
308
- if not container_port_name: # empty is allowed
309
- container_port_name = from_port_name
310
-
311
- cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
312
- if cport_from.port.causality not in ("output", "local"): # check causality
313
- raise FMUContainerError(f"Tried to use '{cport_from}' as OUTPUT of the container but FMU causality is "
314
- f"'{cport_from.port.causality}'.")
315
-
316
- if container_port_name in self.outputs:
317
- raise FMUContainerError(f"Duplicate OUTPUT {container_port_name} already connected to {cport_from}")
318
-
319
- logger.debug(f"OUTPUT: {from_fmu_filename}:{from_port_name}")
320
- self.mark_ruled(cport_from, 'OUTPUT')
321
- self.outputs[container_port_name] = cport_from
322
-
323
- def drop_port(self, from_fmu_filename: str, from_port_name: str):
324
- cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
325
- if not cport_from.port.causality == "output": # check causality
326
- raise FMUContainerError(f"{cport_from}: trying to DROP {cport_from.port.causality}")
327
-
328
- logger.debug(f"DROP: {from_fmu_filename}:{from_port_name}")
329
- self.mark_ruled(cport_from, 'DROP')
330
-
331
- def add_link(self, from_fmu_filename: str, from_port_name: str, to_fmu_filename: str, to_port_name: str):
332
- cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
333
- try:
334
- local = self.locals[cport_from]
335
- except KeyError:
336
- local = Local(cport_from)
337
-
338
- cport_to = ContainerPort(self.get_fmu(to_fmu_filename), to_port_name)
339
- local.add_target(cport_to) # Causality is check in the add() function
340
-
341
- self.mark_ruled(cport_from, 'LINK')
342
- self.mark_ruled(cport_to, 'LINK')
343
- self.locals[cport_from] = local
344
-
345
- def add_start_value(self, fmu_filename: str, port_name: str, value: str):
346
- cport = ContainerPort(self.get_fmu(fmu_filename), port_name)
347
-
348
- try:
349
- if cport.port.type_name == 'Real':
350
- value = float(value)
351
- elif cport.port.type_name == 'Integer':
352
- value = int(value)
353
- elif cport.port.type_name == 'Boolean':
354
- value = int(bool(value))
355
- else:
356
- value = value
357
- except ValueError:
358
- raise FMUContainerError(f"Start value is not conforming to '{cport.port.type_name}' format.")
359
-
360
- self.start_values[cport] = value
361
-
362
- def find_inputs(self, port_to_connect: FMUPort) -> List[ContainerPort]:
363
- candidates = []
364
- for cport in self.get_all_cports():
365
- if (cport.port.causality == 'input' and cport not in self.rules and cport.port.name == port_to_connect.name
366
- and cport.port.type_name == port_to_connect.type_name):
367
- candidates.append(cport)
368
- return candidates
369
-
370
- def add_implicit_rule(self, auto_input=True, auto_output=True, auto_link=True, auto_parameter=False,
371
- auto_local=False) -> AutoWired:
372
-
373
- auto_wired = AutoWired()
374
- # Auto Link outputs
375
- for cport in self.get_all_cports():
376
- if cport.port.causality == 'output':
377
- candidates_cport_list = self.find_inputs(cport.port)
378
- if auto_link and candidates_cport_list:
379
- for candidate_cport in candidates_cport_list:
380
- logger.info(f"AUTO LINK: {cport} -> {candidate_cport}")
381
- self.add_link(cport.fmu.name, cport.port.name,
382
- candidate_cport.fmu.name, candidate_cport.port.name)
383
- auto_wired.add_link(cport.fmu.name, cport.port.name,
384
- candidate_cport.fmu.name, candidate_cport.port.name)
385
- elif auto_output and cport not in self.rules:
386
- logger.info(f"AUTO OUTPUT: Expose {cport}")
387
- self.add_output(cport.fmu.name, cport.port.name, cport.port.name)
388
- auto_wired.add_output(cport.fmu.name, cport.port.name, cport.port.name)
389
- elif cport.port.causality == 'local':
390
- local_portname = None
391
- if cport.port.name.startswith("container."):
392
- local_portname = "container." + cport.fmu.id + "." + cport.port.name[10:]
393
- logger.info(f"PROFILING: Expose {cport}")
394
- elif auto_local:
395
- local_portname = cport.fmu.id + "." + cport.port.name
396
- logger.info(f"AUTO LOCAL: Expose {cport}")
397
- if local_portname:
398
- self.add_output(cport.fmu.name, cport.port.name, local_portname)
399
- auto_wired.add_output(cport.fmu.name, cport.port.name, local_portname)
400
-
401
- if auto_input:
402
- # Auto link inputs
403
- for cport in self.get_all_cports():
404
- if cport not in self.rules:
405
- if cport.port.causality == 'parameter' and auto_parameter:
406
- parameter_name = cport.fmu.id + "." + cport.port.name
407
- logger.info(f"AUTO PARAMETER: {cport} as {parameter_name}")
408
- self.add_input(parameter_name, cport.fmu.name, cport.port.name)
409
- auto_wired.add_parameter(parameter_name, cport.fmu.name, cport.port.name)
410
- elif cport.port.causality == 'input':
411
- logger.info(f"AUTO INPUT: Expose {cport}")
412
- self.add_input(cport.port.name, cport.fmu.name, cport.port.name)
413
- auto_wired.add_input(cport.port.name, cport.fmu.name, cport.port.name)
414
-
415
- logger.info(f"Auto-wiring: {auto_wired}")
416
-
417
- return auto_wired
418
-
419
- def minimum_step_size(self) -> float:
420
- step_size = None
421
- for fmu in self.execution_order:
422
- if step_size:
423
- if fmu.step_size and fmu.step_size < step_size:
424
- step_size = fmu.step_size
425
- else:
426
- step_size = fmu.step_size
427
-
428
- if not step_size:
429
- step_size = 0.1
430
- logger.warning(f"Defaulting to step_size={step_size}")
431
-
432
- return step_size
433
-
434
- def sanity_check(self, step_size: Optional[float]):
435
- for fmu in self.execution_order:
436
- if not fmu.step_size:
437
- continue
438
- ts_ratio = step_size / fmu.step_size
439
- if ts_ratio < 1.0:
440
- logger.warning(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
441
- f"step_size={fmu.step_size}s.")
442
- if ts_ratio != int(ts_ratio):
443
- logger.warning(f"Container step_size={step_size}s should divisible by FMU '{fmu.name}' "
444
- f"step_size={fmu.step_size}s.")
445
- for port_name in fmu.ports:
446
- cport = ContainerPort(fmu, port_name)
447
- if cport not in self.rules:
448
- if cport.port.causality == 'input':
449
- logger.error(f"{cport} is not connected")
450
- if cport.port.causality == 'output':
451
- logger.warning(f"{cport} is not connected")
452
-
453
- def make_fmu(self, fmu_filename: Union[str, Path], step_size: Optional[float] = None, debug=False, mt=False,
454
- profiling=False):
455
- if isinstance(fmu_filename, str):
456
- fmu_filename = Path(fmu_filename)
457
-
458
- if step_size is None:
459
- logger.info(f"step_size will be deduced from the embedded FMU's")
460
- step_size = self.minimum_step_size()
461
- self.sanity_check(step_size)
462
-
463
- logger.info(f"Building FMU '{fmu_filename}', step_size={step_size}")
464
-
465
- base_directory = self.fmu_directory / fmu_filename.with_suffix('')
466
- resources_directory = self.make_fmu_skeleton(base_directory)
467
- with open(base_directory / "modelDescription.xml", "wt") as xml_file:
468
- self.make_fmu_xml(xml_file, step_size, profiling)
469
- with open(resources_directory / "container.txt", "wt") as txt_file:
470
- self.make_fmu_txt(txt_file, step_size, mt, profiling)
471
-
472
- self.make_fmu_package(base_directory, fmu_filename)
473
- if not debug:
474
- self.make_fmu_cleanup(base_directory)
475
-
476
- def make_fmu_xml(self, xml_file, step_size: float, profiling: bool):
477
- vr_table = ValueReferenceTable()
478
-
479
- timestamp = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
480
- guid = str(uuid.uuid4())
481
- embedded_fmu = ", ".join([fmu_name for fmu_name in self.involved_fmu])
482
- try:
483
- author = os.getlogin()
484
- except OSError:
485
- author = "Unspecified"
486
-
487
- capabilities = {}
488
- for capability in EmbeddedFMU.capability_list:
489
- capabilities[capability] = "false"
490
- for fmu in self.involved_fmu.values():
491
- if fmu.capabilities[capability] == "true":
492
- capabilities[capability] = "true"
493
-
494
- if self.start_time is None:
495
- self.start_time = self.execution_order[0].start_time
496
- logger.info(f"start_time={self.start_time} (deduced from '{self.execution_order[0].name}')")
497
- else:
498
- logger.info(f"start_time={self.start_time}")
499
-
500
- if self.stop_time is None:
501
- self.stop_time = self.execution_order[0].stop_time
502
- logger.info(f"stop_time={self.stop_time} (deduced from '{self.execution_order[0].name}')")
503
- else:
504
- logger.info(f"stop_time={self.stop_time}")
505
-
506
- xml_file.write(f"""<?xml version="1.0" encoding="ISO-8859-1"?>
507
- <fmiModelDescription
508
- fmiVersion="2.0"
509
- modelName="{self.identifier}"
510
- generationTool="FMUContainer-{tool_version}"
511
- generationDateAndTime="{timestamp}"
512
- guid="{guid}"
513
- description="FMUContainer with {embedded_fmu}"
514
- author="{author}"
515
- license="Proprietary"
516
- copyright="See Embedded FMU's copyrights."
517
- variableNamingConvention="structured">
518
-
519
- <CoSimulation
520
- modelIdentifier="{self.identifier}"
521
- canHandleVariableCommunicationStepSize="true"
522
- canBeInstantiatedOnlyOncePerProcess="{capabilities['canBeInstantiatedOnlyOncePerProcess']}"
523
- canNotUseMemoryManagementFunctions="true"
524
- canGetAndSetFMUstate="false"
525
- canSerializeFMUstate="false"
526
- providesDirectionalDerivative="false"
527
- needsExecutionTool="{capabilities['needsExecutionTool']}">
528
- </CoSimulation>
529
-
530
- <LogCategories>
531
- <Category name="fmucontainer"/>
532
- </LogCategories>
533
-
534
- <DefaultExperiment stepSize="{step_size}" startTime="{self.start_time}" stopTime="{self.stop_time}"/>
535
-
536
- <ModelVariables>
537
- """)
538
- if profiling:
539
- for fmu in self.execution_order:
540
- vr = vr_table.add_vr("Real")
541
- name = f"container.{fmu.id}.rt_ratio"
542
- print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
543
- f'<Real /></ScalarVariable>', file=xml_file)
544
-
545
- # Local variable should be first to ensure to attribute them the lowest VR.
546
- for local in self.locals.values():
547
- vr = vr_table.get_vr(local.cport_from)
548
- print(f' {local.cport_from.port.xml(vr, name=local.name, causality="local")}', file=xml_file)
549
- local.vr = vr
550
-
551
- for input_port_name, input_port in self.inputs.items():
552
- vr = vr_table.add_vr(input_port.type_name)
553
- # Get Start and XML from first connected input
554
- start = self.start_values.get(input_port.cport_list[0], None)
555
- print(f" {input_port.cport_list[0].port.xml(vr, name=input_port_name, start=start)}", file=xml_file)
556
- input_port.vr = vr
557
-
558
- for output_port_name, cport in self.outputs.items():
559
- vr = vr_table.get_vr(cport)
560
- print(f" {cport.port.xml(vr, name=output_port_name)}", file=xml_file)
561
- cport.vr = vr
562
-
563
- xml_file.write(""" </ModelVariables>
564
-
565
- <ModelStructure>
566
- <Outputs>
567
- """)
568
-
569
- index_offset = len(self.locals) + len(self.inputs) + 1
570
- for i, _ in enumerate(self.outputs.keys()):
571
- print(f' <Unknown index="{index_offset+i}"/>', file=xml_file)
572
- xml_file.write(""" </Outputs>
573
- <InitialUnknowns>
574
- """)
575
- for i, _ in enumerate(self.outputs.keys()):
576
- print(f' <Unknown index="{index_offset+i}"/>', file=xml_file)
577
- xml_file.write(""" </InitialUnknowns>
578
- </ModelStructure>
579
-
580
- </fmiModelDescription>
581
- """)
582
-
583
- def make_fmu_txt(self, txt_file, step_size: float, mt: bool, profiling: bool):
584
- if mt:
585
- print("# Use MT\n1", file=txt_file)
586
- else:
587
- print("# Don't use MT\n0", file=txt_file)
588
-
589
- if profiling:
590
- print("# Profiling ENABLED\n1", file=txt_file)
591
- else:
592
- print("# Profiling DISABLED\n0", file=txt_file)
593
-
594
- print(f"# Internal time step in seconds", file=txt_file)
595
- print(f"{step_size}", file=txt_file)
596
- print(f"# NB of embedded FMU's", file=txt_file)
597
- print(f"{len(self.involved_fmu)}", file=txt_file)
598
- fmu_rank: Dict[str, int] = {}
599
- for i, fmu in enumerate(self.execution_order):
600
- print(f"{fmu.name}", file=txt_file)
601
- print(f"{fmu.model_identifier}", file=txt_file)
602
- print(f"{fmu.guid}", file=txt_file)
603
- fmu_rank[fmu.name] = i
604
-
605
- # Prepare data structure
606
- type_names_list = ("Real", "Integer", "Boolean", "String") # Ordered list
607
- inputs_per_type: Dict[str, List[ContainerInput]] = {} # Container's INPUT
608
- outputs_per_type: Dict[str, List[ContainerPort]] = {} # Container's OUTPUT
609
-
610
- inputs_fmu_per_type: Dict[str, Dict[str, Dict[ContainerPort, int]]] = {} # [type][fmu]
611
- start_values_fmu_per_type = {}
612
- outputs_fmu_per_type = {}
613
- locals_per_type: Dict[str, List[Local]] = {}
614
-
615
- for type_name in type_names_list:
616
- inputs_per_type[type_name] = []
617
- outputs_per_type[type_name] = []
618
- locals_per_type[type_name] = []
619
-
620
- inputs_fmu_per_type[type_name] = {}
621
- start_values_fmu_per_type[type_name] = {}
622
- outputs_fmu_per_type[type_name] = {}
623
-
624
- for fmu in self.execution_order:
625
- inputs_fmu_per_type[type_name][fmu.name] = {}
626
- start_values_fmu_per_type[type_name][fmu.name] = {}
627
- outputs_fmu_per_type[type_name][fmu.name] = {}
628
-
629
- # Fill data structure
630
- # Inputs
631
- for input_port_name, input_port in self.inputs.items():
632
- inputs_per_type[input_port.type_name].append(input_port)
633
- for cport, value in self.start_values.items():
634
- start_values_fmu_per_type[cport.port.type_name][cport.fmu.name][cport] = value
635
- # Outputs
636
- for output_port_name, cport in self.outputs.items():
637
- outputs_per_type[cport.port.type_name].append(cport)
638
- # Locals
639
- for local in self.locals.values():
640
- vr = local.vr
641
- locals_per_type[local.cport_from.port.type_name].append(local)
642
- outputs_fmu_per_type[local.cport_from.port.type_name][local.cport_from.fmu.name][local.cport_from] = vr
643
- for cport_to in local.cport_to_list:
644
- inputs_fmu_per_type[cport_to.port.type_name][cport_to.fmu.name][cport_to] = vr
645
-
646
- print(f"# NB local variables Real, Integer, Boolean, String", file=txt_file)
647
- for type_name in type_names_list:
648
- nb = len(locals_per_type[type_name])
649
- if profiling and type_name == "Real":
650
- nb += len(self.execution_order)
651
- print(f"{nb} ", file=txt_file, end='')
652
- print("", file=txt_file)
653
-
654
- print("# CONTAINER I/O: <VR> <NB> <FMU_INDEX> <FMU_VR> [<FMU_INDEX> <FMU_VR>]", file=txt_file)
655
- for type_name in type_names_list:
656
- print(f"# {type_name}", file=txt_file)
657
- nb = len(inputs_per_type[type_name]) + len(outputs_per_type[type_name]) + len(locals_per_type[type_name])
658
- nb_input_link = 0
659
- for input_port in inputs_per_type[type_name]:
660
- nb_input_link += len(input_port.cport_list) - 1
661
-
662
- if profiling and type_name == "Real":
663
- nb += len(self.execution_order)
664
- print(f"{nb} {nb+nb_input_link}", file=txt_file)
665
- for profiling_port, _ in enumerate(self.execution_order):
666
- print(f"{profiling_port} 1 -2 {profiling_port}", file=txt_file)
667
- else:
668
- print(f"{nb} {nb+nb_input_link}", file=txt_file)
669
- for input_port in inputs_per_type[type_name]:
670
- cport_string = [f"{fmu_rank[cport.fmu.name]} {cport.port.vr}" for cport in input_port.cport_list]
671
- print(f"{input_port.vr} {len(input_port.cport_list)}", " ".join(cport_string), file=txt_file)
672
- for cport in outputs_per_type[type_name]:
673
- print(f"{cport.vr} 1 {fmu_rank[cport.fmu.name]} {cport.port.vr}", file=txt_file)
674
- for local in locals_per_type[type_name]:
675
- print(f"{local.vr} 1 -1 {local.vr}", file=txt_file)
676
-
677
- # LINKS
678
- for fmu in self.execution_order:
679
- for type_name in type_names_list:
680
- print(f"# Inputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
681
- print(len(inputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
682
- for cport, vr in inputs_fmu_per_type[type_name][fmu.name].items():
683
- print(f"{vr} {cport.port.vr}", file=txt_file)
684
-
685
- for type_name in type_names_list:
686
- print(f"# Start values of {fmu.name} - {type_name}: <FMU_VR> <RESET> <VALUE>", file=txt_file)
687
- print(len(start_values_fmu_per_type[type_name][fmu.name]), file=txt_file)
688
- for cport, value in start_values_fmu_per_type[type_name][fmu.name].items():
689
- reset = 1 if cport.port.causality == "input" else 0
690
- print(f"{cport.port.vr} {reset} {value}", file=txt_file)
691
-
692
- for type_name in type_names_list:
693
- print(f"# Outputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
694
- print(len(outputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
695
- for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
696
- print(f"{vr} {cport.port.vr}", file=txt_file)
697
-
698
- @staticmethod
699
- def long_path(path: Union[str, Path]) -> str:
700
- # https://stackoverflow.com/questions/14075465/copy-a-file-with-a-too-long-path-to-another-directory-in-python
701
- if os.name == 'nt':
702
- return "\\\\?\\" + os.path.abspath(str(path))
703
- else:
704
- return path
705
-
706
- def make_fmu_skeleton(self, base_directory: Path) -> Path:
707
- logger.debug(f"Initialize directory '{base_directory}'")
708
-
709
- origin = Path(__file__).parent / "resources"
710
- resources_directory = base_directory / "resources"
711
- documentation_directory = base_directory / "documentation"
712
- binaries_directory = base_directory / "binaries"
713
-
714
- base_directory.mkdir(exist_ok=True)
715
- resources_directory.mkdir(exist_ok=True)
716
- binaries_directory.mkdir(exist_ok=True)
717
- documentation_directory.mkdir(exist_ok=True)
718
-
719
- if self.description_pathname:
720
- logger.debug(f"Copying {self.description_pathname}")
721
- shutil.copy(self.description_pathname, documentation_directory)
722
-
723
- shutil.copy(origin / "model.png", base_directory)
724
- for bitness in ('win32', 'win64'):
725
- library_filename = origin / bitness / "container.dll"
726
- if library_filename.is_file():
727
- binary_directory = binaries_directory / bitness
728
- binary_directory.mkdir(exist_ok=True)
729
- shutil.copy(library_filename, binary_directory / f"{self.identifier}.dll")
730
-
731
- for i, fmu in enumerate(self.involved_fmu.values()):
732
- shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
733
- self.long_path(resources_directory / f"{i:02x}"), dirs_exist_ok=True)
734
- return resources_directory
735
-
736
- def make_fmu_package(self, base_directory: Path, fmu_filename: Path):
737
- logger.debug(f"Zipping directory '{base_directory}' => '{fmu_filename}'")
738
- zip_directory = self.long_path(str(base_directory.absolute()))
739
- offset = len(zip_directory) + 1
740
- with zipfile.ZipFile(self.fmu_directory / fmu_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
741
- def add_file(directory: Path):
742
- for entry in directory.iterdir():
743
- if entry.is_dir():
744
- add_file(directory / entry)
745
- elif entry.is_file:
746
- zip_file.write(str(entry), str(entry)[offset:])
747
-
748
- add_file(Path(zip_directory))
749
- logger.info(f"'{fmu_filename}' is available.")
750
-
751
- def make_fmu_cleanup(self, base_directory: Path):
752
- logger.debug(f"Delete directory '{base_directory}'")
753
- shutil.rmtree(self.long_path(base_directory))