fmu-manipulation-toolbox 1.8.4.3b0__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 (34) 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 +21 -12
  5. fmu_manipulation_toolbox/checker.py +11 -8
  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 +534 -247
  12. fmu_manipulation_toolbox/gui.py +47 -55
  13. fmu_manipulation_toolbox/gui_style.py +8 -0
  14. fmu_manipulation_toolbox/help.py +3 -0
  15. fmu_manipulation_toolbox/operations.py +251 -179
  16. fmu_manipulation_toolbox/remoting.py +107 -0
  17. fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
  18. fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
  19. fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
  20. fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
  21. fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
  22. fmu_manipulation_toolbox/resources/linux64/server_sm +0 -0
  23. fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
  24. fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
  25. fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
  26. fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
  27. fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
  28. fmu_manipulation_toolbox/split.py +67 -34
  29. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/METADATA +1 -1
  30. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/RECORD +34 -27
  31. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/WHEEL +0 -0
  32. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/entry_points.txt +0 -0
  33. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/licenses/LICENSE.txt +0 -0
  34. {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/top_level.txt +0 -0
@@ -1,37 +1,92 @@
1
1
  import logging
2
+ import getpass
3
+ import math
2
4
  import os
3
5
  import shutil
4
6
  import uuid
7
+ import platform
5
8
  import zipfile
6
9
  from datetime import datetime
7
10
  from pathlib import Path
8
11
  from typing import *
9
12
 
10
- from .operations import FMU, OperationAbstract, FMUError
13
+ from .operations import FMU, OperationAbstract, FMUError, FMUPort
11
14
  from .version import __version__ as tool_version
12
15
 
13
16
 
14
17
  logger = logging.getLogger("fmu_manipulation_toolbox")
15
18
 
16
19
 
17
- class FMUPort:
18
- def __init__(self, attrs: Dict[str, str]):
19
- self.causality = attrs.pop("causality", "local")
20
- self.variability = attrs.pop("variability", "continuous")
21
- self.name = attrs.pop("name")
22
- self.vr = int(attrs.pop("valueReference"))
23
- self.description = attrs.pop("description", None)
24
-
25
- self.type_name = attrs.pop("type_name", None)
26
- self.start_value = attrs.pop("start", None)
27
- self.initial = attrs.pop("initial", None)
20
+ class EmbeddedFMUPort:
21
+ FMI_TO_CONTAINER = {
22
+ 2: {
23
+ 'Real': 'real64',
24
+ 'Integer': 'integer32',
25
+ 'String': 'string',
26
+ 'Boolean': 'boolean'
27
+ },
28
+ 3: {
29
+ 'Float64': 'real64',
30
+ 'Float32': 'real32',
31
+ 'Int8': 'integer8',
32
+ 'UInt8': 'uinteger8',
33
+ 'Int16': 'integer16',
34
+ 'UInt16': 'uinteger16',
35
+ 'Int32': 'integer32',
36
+ 'UInt32': 'uinteger32',
37
+ 'Int64': 'integer64',
38
+ 'UInt64': 'uinteger64',
39
+ 'String': 'string',
40
+ 'Boolean': 'boolean1'
41
+ }
42
+ }
43
+
44
+ CONTAINER_TO_FMI = {
45
+ 2: {
46
+ 'real64': 'Real',
47
+ 'integer32': 'Integer',
48
+ 'string': 'String',
49
+ 'boolean': 'Boolean'
50
+ },
51
+ 3: {
52
+ 'real64': 'Float64' ,
53
+ 'real32': 'Float32' ,
54
+ 'integer8': 'Int8' ,
55
+ 'uinteger8': 'UInt8' ,
56
+ 'integer16': 'Int16' ,
57
+ 'uinteger16': 'UInt16' ,
58
+ 'integer32': 'Int32' ,
59
+ 'uinteger32': 'UInt32' ,
60
+ 'integer64': 'Int64' ,
61
+ 'uinteger64': 'UInt64' ,
62
+ 'string': 'String' ,
63
+ 'boolean1': 'Boolean'
64
+ }
65
+ }
66
+
67
+ ALL_TYPES = (
68
+ "real64", "real32",
69
+ "integer8", "uinteger8", "integer16", "uinteger16", "integer32", "uinteger32", "integer64", "uinteger64",
70
+ "boolean", "boolean1",
71
+ "string"
72
+ )
73
+
74
+ def __init__(self, fmi_type, attrs: Union[FMUPort, Dict[str, str]], fmi_version=0):
75
+ self.causality = attrs.get("causality", "local")
76
+ self.variability = attrs.get("variability", "continuous")
77
+ self.name = attrs["name"]
78
+ self.vr = int(attrs["valueReference"])
79
+ self.description = attrs.get("description", None)
80
+
81
+ if fmi_version > 0:
82
+ self.type_name = self.FMI_TO_CONTAINER[fmi_version][fmi_type]
83
+ else:
84
+ self.type_name = fmi_type
28
85
 
29
- def set_port_type(self, type_name: str, attrs: Dict[str, str]):
30
- self.type_name = type_name
31
- self.start_value = attrs.pop("start", None)
32
- self.initial = attrs.pop("initial", None)
86
+ self.start_value = attrs.get("start", None)
87
+ self.initial = attrs.get("initial", None)
33
88
 
34
- def xml(self, vr: int, name=None, causality=None, start=None, fmi_version=2):
89
+ def xml(self, vr: int, name=None, causality=None, start=None, fmi_version=2) -> str:
35
90
  if name is None:
36
91
  name = self.name
37
92
  if causality is None:
@@ -39,23 +94,23 @@ class FMUPort:
39
94
  if start is None:
40
95
  start = self.start_value
41
96
  if self.variability is None:
42
- self.variability = "continuous" if self.type_name == "Real" else "discrete"
97
+ self.variability = "continuous" if "real" in self.type_name else "discrete"
43
98
 
99
+ try:
100
+ fmi_type = self.CONTAINER_TO_FMI[fmi_version][self.type_name]
101
+ except KeyError:
102
+ logger.error(f"Cannot expose '{name}' because type '{self.type_name}' is not compatible "
103
+ f"with FMI-{fmi_version}.0")
104
+ return ""
44
105
 
45
106
  if fmi_version == 2:
46
- child_dict = {
107
+ child_attrs = {
47
108
  "start": start,
48
109
  }
49
- if "Float" in self.type_name:
50
- type_name = "Real"
51
- elif "Int" in self.type_name:
52
- type_name = "Integer"
53
- else:
54
- type_name = self.type_name
55
110
 
56
- child_str = (f"<{type_name} " +
57
- " ".join([f'{key}="{value}"' if value is not None else ""
58
- for (key, value) in child_dict.items()]) +
111
+ filtered_child_attrs = {key: value for key, value in child_attrs.items() if value is not None}
112
+ child_str = (f"<{fmi_type} " +
113
+ " ".join([f'{key}="{value}"' for (key, value) in filtered_child_attrs.items()]) +
59
114
  "/>")
60
115
 
61
116
  scalar_attrs = {
@@ -66,28 +121,59 @@ class FMUPort:
66
121
  "initial": self.initial,
67
122
  "description": self.description,
68
123
  }
69
- scalar_attrs_str = " ".join([f'{key}="{value}"' if value is not None else ""
70
- for (key, value) in scalar_attrs.items()])
124
+ filtered_attrs = {key: value for key, value in scalar_attrs.items() if value is not None}
125
+ scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in filtered_attrs.items()])
71
126
  return f'<ScalarVariable {scalar_attrs_str}>{child_str}</ScalarVariable>'
72
127
  else:
73
- return f'FIX ME'
128
+ if fmi_type in ('String', 'Binary'):
129
+ if start:
130
+ child_str = f'<Start value="{start}"/>'
131
+ else:
132
+ child_str = ''
133
+ scalar_attrs = {
134
+ "name": name,
135
+ "valueReference": vr,
136
+ "causality": causality,
137
+ "variability": self.variability,
138
+ "initial": self.initial,
139
+ "description": self.description,
140
+ }
141
+ filtered_attrs = {key: value for key, value in scalar_attrs.items() if value is not None}
142
+ scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in filtered_attrs.items()])
143
+ return f'<{fmi_type} {scalar_attrs_str}>{child_str}</{fmi_type}>'
144
+ else:
145
+ scalar_attrs = {
146
+ "name": name,
147
+ "valueReference": vr,
148
+ "causality": causality,
149
+ "variability": self.variability,
150
+ "initial": self.initial,
151
+ "description": self.description,
152
+ "start": start
153
+ }
154
+ filtered_attrs = {key: value for key, value in scalar_attrs.items() if value is not None}
155
+ scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in filtered_attrs.items()])
156
+
157
+ return f'<{fmi_type} {scalar_attrs_str}/>'
74
158
 
75
159
 
76
160
  class EmbeddedFMU(OperationAbstract):
77
161
  capability_list = ("needsExecutionTool",
78
- "canBeInstantiatedOnlyOncePerProcess")
162
+ "canBeInstantiatedOnlyOncePerProcess",
163
+ "canHandleVariableCommunicationStepSize")
79
164
 
80
165
  def __init__(self, filename):
81
166
  self.fmu = FMU(filename)
82
167
  self.name = Path(filename).name
83
- self.id = Path(filename).stem
168
+ self.id = Path(filename).stem.lower()
84
169
 
85
170
  self.step_size = None
86
171
  self.start_time = None
87
172
  self.stop_time = None
88
173
  self.model_identifier = None
89
174
  self.guid = None
90
- self.ports: Dict[str, FMUPort] = {}
175
+ self.fmi_version = None
176
+ self.ports: Dict[str, EmbeddedFMUPort] = {}
91
177
 
92
178
  self.capabilities: Dict[str, str] = {}
93
179
  self.current_port = None # used during apply_operation()
@@ -100,21 +186,10 @@ class EmbeddedFMU(OperationAbstract):
100
186
  fmi_version = attrs['fmiVersion']
101
187
  if fmi_version == "2.0":
102
188
  self.guid = attrs['guid']
103
- if fmi_version == "3.0":
189
+ self.fmi_version = 2
190
+ if fmi_version == "3.0": # TODO: handle 3.x cases
104
191
  self.guid = attrs['instantiationToken']
105
-
106
-
107
- def scalar_attrs(self, attrs) -> int:
108
- if 'type_name' in attrs: # FMI 3.0
109
- type_name = attrs.pop('type_name')
110
- port = FMUPort(attrs)
111
- port.type_name = type_name
112
- self.ports[port.name] = port
113
- else: # FMI 2.0
114
- self.current_port = FMUPort(attrs)
115
- self.ports[self.current_port.name] = self.current_port
116
-
117
- return 0
192
+ self.fmi_version = 3
118
193
 
119
194
  def cosimulation_attrs(self, attrs: Dict[str, str]):
120
195
  self.model_identifier = attrs['modelIdentifier']
@@ -129,15 +204,18 @@ class EmbeddedFMU(OperationAbstract):
129
204
  self.start_time = float(attrs.get("startTime", 0.0))
130
205
  self.stop_time = float(attrs.get("stopTime", self.start_time + 1.0))
131
206
 
132
- def scalar_type(self, type_name, attrs):
133
- if self.current_port:
134
- if type_name == "Enumeration":
135
- type_name = "Integer"
136
- self.current_port.set_port_type(type_name, attrs)
137
- self.current_port = None
207
+ def port_attrs(self, fmu_port: FMUPort):
208
+ # Container will manage Enumeration as Integer
209
+ if fmu_port.fmi_type == "Enumeration":
210
+ if self.fmi_version == 2:
211
+ fmu_port.fmi_type = "Integer"
212
+ else:
213
+ fmu_port.fmi_type = "Int32"
214
+ port = EmbeddedFMUPort(fmu_port.fmi_type, fmu_port, fmi_version=self.fmi_version)
215
+ self.ports[port.name] = port
138
216
 
139
217
  def __repr__(self):
140
- return f"FMU '{self.name}' ({len(self.ports)} variables)"
218
+ return f"FMU '{self.name}' ({len(self.ports)} variables, ts={self.step_size}s)"
141
219
 
142
220
 
143
221
  class FMUContainerError(Exception):
@@ -190,12 +268,51 @@ class ContainerInput:
190
268
  self.cport_list.append(cport_to)
191
269
 
192
270
 
193
- class Local:
271
+ class Link:
272
+ CONVERSION_FUNCTION = {
273
+ "real32/real64": "F32_F64",
274
+
275
+ "Int8/Int16": "D8_D16",
276
+ "Int8/UInt16": "D8_U16",
277
+ "Int8/Int32": "D8_D32",
278
+ "Int8/UInt32": "D8_U32",
279
+ "Int8/Int64": "D8_D64",
280
+ "Int8/UInt64": "D8_U64",
281
+
282
+ "UInt8/Int16": "U8_D16",
283
+ "UInt8/UInt16": "U8_U16",
284
+ "UInt8/Int32": "U8_D32",
285
+ "UInt8/UInt32": "U8_U32",
286
+ "UInt8/Int64": "U8_D64",
287
+ "UInt8/UInt64": "U8_U64",
288
+
289
+ "Int16/Int32": "D16_D32",
290
+ "Int16/UInt32": "D16_U32",
291
+ "Int16/Int64": "D16_D64",
292
+ "Int16/UInt64": "D16_U64",
293
+
294
+ "UInt16/Int32": "U16_D32",
295
+ "UInt16/UInt32": "U16_U32",
296
+ "UInt16/Int64": "U16_D64",
297
+ "UInt16/UInt64": "U16_U64",
298
+
299
+ "Int32/Int64": "D32_D64",
300
+ "Int32/UInt64": "D32_U64",
301
+
302
+ "UInt32/Int64": "U32_D64",
303
+ "UInt32/UInt64": "U32_U64",
304
+
305
+ "boolean/boolean1": "B_B1",
306
+ "boolean1/boolean": "B1_B",
307
+ }
308
+
194
309
  def __init__(self, cport_from: ContainerPort):
195
310
  self.name = cport_from.fmu.id + "." + cport_from.port.name # strip .fmu suffix
196
311
  self.cport_from = cport_from
197
312
  self.cport_to_list: List[ContainerPort] = []
198
- self.vr = None
313
+
314
+ self.vr: Optional[int] = None
315
+ self.vr_converted: Dict[str, Optional[int]] = {}
199
316
 
200
317
  if not cport_from.port.causality == "output":
201
318
  raise FMUContainerError(f"{cport_from} is {cport_from.port.causality} instead of OUTPUT")
@@ -206,28 +323,54 @@ class Local:
206
323
 
207
324
  if cport_to.port.type_name == self.cport_from.port.type_name:
208
325
  self.cport_to_list.append(cport_to)
326
+ elif self.get_conversion(cport_to):
327
+ self.cport_to_list.append(cport_to)
328
+ self.vr_converted[cport_to.port.type_name] = None
209
329
  else:
210
330
  raise FMUContainerError(f"failed to connect {self.cport_from} to {cport_to} due to type.")
211
331
 
332
+ def get_conversion(self, cport_to: ContainerPort) -> Optional[str]:
333
+ try:
334
+ conversion = f"{self.cport_from.port.type_name}/{cport_to.port.type_name}"
335
+ return self.CONVERSION_FUNCTION[conversion]
336
+ except KeyError:
337
+ return None
338
+
339
+ def nb_local(self) -> int:
340
+ return 1+len(self.vr_converted)
341
+
212
342
 
213
343
  class ValueReferenceTable:
214
344
  def __init__(self):
215
- self.vr_table: Dict[str, int] = {
216
- "Real": 0,
217
- "Float64": 0,
218
- "Integer": 0,
219
- "Boolean": 0,
220
- "String": 0,
221
- }
345
+ self.vr_table:Dict[str, int] = {}
346
+ self.masks: Dict[str, int] = {}
347
+ self.nb_local_variable:Dict[str, int] = {}
348
+ for i, type_name in enumerate(EmbeddedFMUPort.ALL_TYPES):
349
+ self.vr_table[type_name] = 0
350
+ self.masks[type_name] = i << 24
351
+ self.nb_local_variable[type_name] = 0
352
+
353
+ def add_vr(self, port_or_type_name: Union[ContainerPort, str], local: bool = False) -> int:
354
+ if isinstance(port_or_type_name, ContainerPort):
355
+ type_name = port_or_type_name.port.type_name
356
+ else:
357
+ type_name = port_or_type_name
222
358
 
223
- def get_vr(self, cport: ContainerPort) -> int:
224
- return self.add_vr(cport.port.type_name)
359
+ if local:
360
+ self.nb_local_variable[type_name] += 1
225
361
 
226
- def add_vr(self, type_name: str) -> int:
227
362
  vr = self.vr_table[type_name]
228
363
  self.vr_table[type_name] += 1
229
- return vr
230
364
 
365
+ return vr | self.masks[type_name]
366
+
367
+ def set_link_vr(self, link: Link):
368
+ link.vr = self.add_vr(link.cport_from, local=True)
369
+ for type_name in link.vr_converted.keys():
370
+ link.vr_converted[type_name] = self.add_vr(type_name, local=True)
371
+
372
+ def nb_local(self, type_name: str) -> int:
373
+ return self.nb_local_variable[type_name]
231
374
 
232
375
  class AutoWired:
233
376
  def __init__(self):
@@ -255,15 +398,83 @@ class AutoWired:
255
398
 
256
399
 
257
400
  class FMUContainer:
258
- def __init__(self, identifier: str, fmu_directory: Union[str, Path], description_pathname=None):
401
+ HEADER_XML_2 = """<?xml version="1.0" encoding="ISO-8859-1"?>
402
+ <fmiModelDescription
403
+ fmiVersion="2.0"
404
+ modelName="{identifier}"
405
+ generationTool="FMUContainer-{tool_version}"
406
+ generationDateAndTime="{timestamp}"
407
+ guid="{guid}"
408
+ description="FMUContainer with {embedded_fmu}"
409
+ author="{author}"
410
+ license="Proprietary"
411
+ copyright="See Embedded FMU's copyrights."
412
+ variableNamingConvention="structured">
413
+
414
+ <CoSimulation
415
+ modelIdentifier="{identifier}"
416
+ canHandleVariableCommunicationStepSize="true"
417
+ canBeInstantiatedOnlyOncePerProcess="{only_once}"
418
+ canNotUseMemoryManagementFunctions="true"
419
+ canGetAndSetFMUstate="false"
420
+ canSerializeFMUstate="false"
421
+ providesDirectionalDerivative="false"
422
+ needsExecutionTool="{execution_tool}">
423
+ </CoSimulation>
424
+
425
+ <LogCategories>
426
+ <Category name="fmucontainer"/>
427
+ </LogCategories>
428
+
429
+ <DefaultExperiment stepSize="{step_size}" startTime="{start_time}" stopTime="{stop_time}"/>
430
+
431
+ <ModelVariables>
432
+ <ScalarVariable valueReference="0" name="time" causality="independent"><Real /></ScalarVariable>
433
+ """
434
+
435
+ HEADER_XML_3 = """<?xml version="1.0" encoding="ISO-8859-1"?>
436
+ <fmiModelDescription
437
+ fmiVersion="3.0"
438
+ modelName="{identifier}"
439
+ generationTool="FMUContainer-{tool_version}"
440
+ generationDateAndTime="{timestamp}"
441
+ instantiationToken="{guid}"
442
+ description="FMUContainer with {embedded_fmu}"
443
+ author="{author}"
444
+ license="Proprietary"
445
+ copyright="See Embedded FMU's copyrights."
446
+ variableNamingConvention="structured">
447
+
448
+ <CoSimulation
449
+ modelIdentifier="{identifier}"
450
+ canHandleVariableCommunicationStepSize="true"
451
+ canBeInstantiatedOnlyOncePerProcess="{only_once}"
452
+ canNotUseMemoryManagementFunctions="true"
453
+ canGetAndSetFMUstate="false"
454
+ canSerializeFMUstate="false"
455
+ providesDirectionalDerivative="false"
456
+ needsExecutionTool="{execution_tool}">
457
+ </CoSimulation>
458
+
459
+ <LogCategories>
460
+ <Category name="fmucontainer"/>
461
+ </LogCategories>
462
+
463
+ <DefaultExperiment stepSize="{step_size}" startTime="{start_time}" stopTime="{stop_time}"/>
464
+
465
+ <ModelVariables>
466
+ <Float64 valueReference="0" name="time" causality="independent"/>
467
+ """
468
+
469
+ def __init__(self, identifier: str, fmu_directory: Union[str, Path], description_pathname=None, fmi_version=2):
259
470
  self.fmu_directory = Path(fmu_directory)
260
471
  self.identifier = identifier
261
472
  if not self.fmu_directory.is_dir():
262
473
  raise FMUContainerError(f"{self.fmu_directory} is not a valid directory")
263
- self.involved_fmu: Dict[str, EmbeddedFMU] = {}
264
- self.execution_order: List[EmbeddedFMU] = []
474
+ self.involved_fmu: OrderedDict[str, EmbeddedFMU] = OrderedDict()
265
475
 
266
476
  self.description_pathname = description_pathname
477
+ self.fmi_version = fmi_version
267
478
 
268
479
  self.start_time = None
269
480
  self.stop_time = None
@@ -271,20 +482,24 @@ class FMUContainer:
271
482
  # Rules
272
483
  self.inputs: Dict[str, ContainerInput] = {}
273
484
  self.outputs: Dict[str, ContainerPort] = {}
274
- self.locals: Dict[ContainerPort, Local] = {}
485
+ self.links: Dict[ContainerPort, Link] = {}
275
486
 
276
487
  self.rules: Dict[ContainerPort, str] = {}
277
488
  self.start_values: Dict[ContainerPort, str] = {}
278
489
 
490
+ self.vr_table = ValueReferenceTable()
491
+
279
492
  def get_fmu(self, fmu_filename: str) -> EmbeddedFMU:
280
493
  if fmu_filename in self.involved_fmu:
281
494
  return self.involved_fmu[fmu_filename]
282
495
 
283
496
  try:
284
497
  fmu = EmbeddedFMU(self.fmu_directory / fmu_filename)
498
+ if not fmu.fmi_version == self.fmi_version:
499
+ logger.warning(f"Try to embed FMU-{fmu.fmi_version} into container FMI-{self.fmi_version}.")
285
500
  self.involved_fmu[fmu.name] = fmu
286
- self.execution_order.append(fmu)
287
- logger.debug(f"Adding FMU #{len(self.execution_order)}: {fmu}")
501
+
502
+ logger.debug(f"Adding FMU #{len(self.involved_fmu)}: {fmu}")
288
503
  except (FMUContainerError, FMUError) as e:
289
504
  raise FMUContainerError(f"Cannot load '{fmu_filename}': {e}")
290
505
 
@@ -299,7 +514,7 @@ class FMUContainer:
299
514
  self.rules[cport] = rule
300
515
 
301
516
  def get_all_cports(self):
302
- return [ContainerPort(fmu, port_name) for fmu in self.execution_order for port_name in fmu.ports]
517
+ return [ContainerPort(fmu, port_name) for fmu in self.involved_fmu.values() for port_name in fmu.ports]
303
518
 
304
519
  def add_input(self, container_port_name: str, to_fmu_filename: str, to_port_name: str):
305
520
  if not container_port_name:
@@ -345,16 +560,17 @@ class FMUContainer:
345
560
  def add_link(self, from_fmu_filename: str, from_port_name: str, to_fmu_filename: str, to_port_name: str):
346
561
  cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
347
562
  try:
348
- local = self.locals[cport_from]
563
+ local = self.links[cport_from]
349
564
  except KeyError:
350
- local = Local(cport_from)
565
+ local = Link(cport_from)
351
566
 
352
567
  cport_to = ContainerPort(self.get_fmu(to_fmu_filename), to_port_name)
353
568
  local.add_target(cport_to) # Causality is check in the add() function
354
569
 
570
+ logger.debug(f"LINK: {cport_from} -> {cport_to}")
355
571
  self.mark_ruled(cport_from, 'LINK')
356
572
  self.mark_ruled(cport_to, 'LINK')
357
- self.locals[cport_from] = local
573
+ self.links[cport_from] = local
358
574
 
359
575
  def add_start_value(self, fmu_filename: str, port_name: str, value: str):
360
576
  cport = ContainerPort(self.get_fmu(fmu_filename), port_name)
@@ -373,7 +589,7 @@ class FMUContainer:
373
589
 
374
590
  self.start_values[cport] = value
375
591
 
376
- def find_inputs(self, port_to_connect: FMUPort) -> List[ContainerPort]:
592
+ def find_inputs(self, port_to_connect: EmbeddedFMUPort) -> List[ContainerPort]:
377
593
  candidates = []
378
594
  for cport in self.get_all_cports():
379
595
  if (cport.port.causality == 'input' and cport not in self.rules and cport.port.name == port_to_connect.name
@@ -430,32 +646,32 @@ class FMUContainer:
430
646
 
431
647
  return auto_wired
432
648
 
433
- def minimum_step_size(self) -> float:
434
- step_size = None
435
- for fmu in self.execution_order:
436
- if step_size:
437
- if fmu.step_size and fmu.step_size < step_size:
438
- step_size = fmu.step_size
439
- else:
440
- step_size = fmu.step_size
649
+ def default_step_size(self) -> float:
650
+ freq_set = set()
651
+ for fmu in self.involved_fmu.values():
652
+ if fmu.step_size and fmu.capabilities["canHandleVariableCommunicationStepSize"] == "false":
653
+ freq_set.add(int(1.0/fmu.step_size))
441
654
 
442
- if not step_size:
655
+ common_freq = math.gcd(*freq_set)
656
+ try:
657
+ step_size = 1.0 / float(common_freq)
658
+ except ZeroDivisionError:
443
659
  step_size = 0.1
444
660
  logger.warning(f"Defaulting to step_size={step_size}")
445
661
 
446
662
  return step_size
447
663
 
448
664
  def sanity_check(self, step_size: Optional[float]):
449
- for fmu in self.execution_order:
450
- if not fmu.step_size:
451
- continue
452
- ts_ratio = step_size / fmu.step_size
453
- if ts_ratio < 1.0:
454
- logger.warning(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
455
- f"step_size={fmu.step_size}s.")
456
- if ts_ratio != int(ts_ratio):
457
- logger.warning(f"Container step_size={step_size}s should divisible by FMU '{fmu.name}' "
458
- f"step_size={fmu.step_size}s.")
665
+ for fmu in self.involved_fmu.values():
666
+ if fmu.step_size and fmu.capabilities["canHandleVariableCommunicationStepSize"] == "false":
667
+ ts_ratio = step_size / fmu.step_size
668
+ logger.debug(f"container step_size: {step_size} = {fmu.step_size} x {ts_ratio} for {fmu.name}")
669
+ if ts_ratio < 1.0:
670
+ logger.warning(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
671
+ f"step_size={fmu.step_size}s.")
672
+ if ts_ratio != int(ts_ratio):
673
+ logger.warning(f"Container step_size={step_size}s should divisible by FMU '{fmu.name}' "
674
+ f"step_size={fmu.step_size}s.")
459
675
  for port_name in fmu.ports:
460
676
  cport = ContainerPort(fmu, port_name)
461
677
  if cport not in self.rules:
@@ -465,13 +681,13 @@ class FMUContainer:
465
681
  logger.warning(f"{cport} is not connected")
466
682
 
467
683
  def make_fmu(self, fmu_filename: Union[str, Path], step_size: Optional[float] = None, debug=False, mt=False,
468
- profiling=False):
684
+ profiling=False, sequential=False):
469
685
  if isinstance(fmu_filename, str):
470
686
  fmu_filename = Path(fmu_filename)
471
687
 
472
688
  if step_size is None:
473
689
  logger.info(f"step_size will be deduced from the embedded FMU's")
474
- step_size = self.minimum_step_size()
690
+ step_size = self.default_step_size()
475
691
  self.sanity_check(step_size)
476
692
 
477
693
  logger.info(f"Building FMU '{fmu_filename}', step_size={step_size}")
@@ -481,20 +697,18 @@ class FMUContainer:
481
697
  with open(base_directory / "modelDescription.xml", "wt") as xml_file:
482
698
  self.make_fmu_xml(xml_file, step_size, profiling)
483
699
  with open(resources_directory / "container.txt", "wt") as txt_file:
484
- self.make_fmu_txt(txt_file, step_size, mt, profiling)
700
+ self.make_fmu_txt(txt_file, step_size, mt, profiling, sequential)
485
701
 
486
702
  self.make_fmu_package(base_directory, fmu_filename)
487
703
  if not debug:
488
704
  self.make_fmu_cleanup(base_directory)
489
705
 
490
706
  def make_fmu_xml(self, xml_file, step_size: float, profiling: bool):
491
- vr_table = ValueReferenceTable()
492
-
493
707
  timestamp = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
494
708
  guid = str(uuid.uuid4())
495
709
  embedded_fmu = ", ".join([fmu_name for fmu_name in self.involved_fmu])
496
710
  try:
497
- author = os.getlogin()
711
+ author = getpass.getuser()
498
712
  except OSError:
499
713
  author = "Unspecified"
500
714
 
@@ -505,137 +719,156 @@ class FMUContainer:
505
719
  if fmu.capabilities[capability] == "true":
506
720
  capabilities[capability] = "true"
507
721
 
722
+ first_fmu = next(iter(self.involved_fmu.values()))
508
723
  if self.start_time is None:
509
- self.start_time = self.execution_order[0].start_time
510
- logger.info(f"start_time={self.start_time} (deduced from '{self.execution_order[0].name}')")
724
+ self.start_time = first_fmu.start_time
725
+ logger.info(f"start_time={self.start_time} (deduced from '{first_fmu.name}')")
511
726
  else:
512
727
  logger.info(f"start_time={self.start_time}")
513
728
 
514
729
  if self.stop_time is None:
515
- self.stop_time = self.execution_order[0].stop_time
516
- logger.info(f"stop_time={self.stop_time} (deduced from '{self.execution_order[0].name}')")
730
+ self.stop_time = first_fmu.stop_time
731
+ logger.info(f"stop_time={self.stop_time} (deduced from '{first_fmu.name}')")
517
732
  else:
518
733
  logger.info(f"stop_time={self.stop_time}")
519
734
 
520
- xml_file.write(f"""<?xml version="1.0" encoding="ISO-8859-1"?>
521
- <fmiModelDescription
522
- fmiVersion="2.0"
523
- modelName="{self.identifier}"
524
- generationTool="FMUContainer-{tool_version}"
525
- generationDateAndTime="{timestamp}"
526
- guid="{guid}"
527
- description="FMUContainer with {embedded_fmu}"
528
- author="{author}"
529
- license="Proprietary"
530
- copyright="See Embedded FMU's copyrights."
531
- variableNamingConvention="structured">
735
+ if self.fmi_version == 2:
736
+ xml_file.write(self.HEADER_XML_2.format(identifier=self.identifier, tool_version=tool_version,
737
+ timestamp=timestamp, guid=guid, embedded_fmu=embedded_fmu,
738
+ author=author,
739
+ only_once=capabilities['canBeInstantiatedOnlyOncePerProcess'],
740
+ execution_tool=capabilities['needsExecutionTool'],
741
+ start_time=self.start_time, stop_time=self.stop_time,
742
+ step_size=step_size))
743
+ elif self.fmi_version == 3:
744
+ xml_file.write(self.HEADER_XML_3.format(identifier=self.identifier, tool_version=tool_version,
745
+ timestamp=timestamp, guid=guid, embedded_fmu=embedded_fmu,
746
+ author=author,
747
+ only_once=capabilities['canBeInstantiatedOnlyOncePerProcess'],
748
+ execution_tool=capabilities['needsExecutionTool'],
749
+ start_time=self.start_time, stop_time=self.stop_time,
750
+ step_size=step_size))
751
+
752
+ vr_time = self.vr_table.add_vr("real64", local=True)
753
+ logger.debug(f"Time vr = {vr_time}")
532
754
 
533
- <CoSimulation
534
- modelIdentifier="{self.identifier}"
535
- canHandleVariableCommunicationStepSize="true"
536
- canBeInstantiatedOnlyOncePerProcess="{capabilities['canBeInstantiatedOnlyOncePerProcess']}"
537
- canNotUseMemoryManagementFunctions="true"
538
- canGetAndSetFMUstate="false"
539
- canSerializeFMUstate="false"
540
- providesDirectionalDerivative="false"
541
- needsExecutionTool="{capabilities['needsExecutionTool']}">
542
- </CoSimulation>
543
-
544
- <LogCategories>
545
- <Category name="fmucontainer"/>
546
- </LogCategories>
547
-
548
- <DefaultExperiment stepSize="{step_size}" startTime="{self.start_time}" stopTime="{self.stop_time}"/>
549
-
550
- <ModelVariables>
551
- """)
552
755
  if profiling:
553
- for fmu in self.execution_order:
554
- vr = vr_table.add_vr("Real")
555
- name = f"container.{fmu.id}.rt_ratio"
556
- print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
557
- f'<Real /></ScalarVariable>', file=xml_file)
756
+ for fmu in self.involved_fmu.values():
757
+ vr = self.vr_table.add_vr("real64", local=True)
758
+ port = EmbeddedFMUPort("real64", {"valueReference": vr,
759
+ "name": f"container.{fmu.id}.rt_ratio",
760
+ "description": f"RT ratio for embedded FMU '{fmu.name}'"})
761
+ print(f" {port.xml(vr, fmi_version=self.fmi_version)}", file=xml_file)
762
+
763
+ index_offset = 2 # index of output ports. Start at 2 to skip "time" port
558
764
 
559
765
  # Local variable should be first to ensure to attribute them the lowest VR.
560
- for local in self.locals.values():
561
- vr = vr_table.get_vr(local.cport_from)
562
- print(f' {local.cport_from.port.xml(vr, name=local.name, causality="local")}', file=xml_file)
563
- local.vr = vr
766
+ for link in self.links.values():
767
+ self.vr_table.set_link_vr(link)
768
+ port_local_def = link.cport_from.port.xml(link.vr, name=link.name, causality='local',
769
+ fmi_version=self.fmi_version)
770
+ if port_local_def:
771
+ print(f" {port_local_def}", file=xml_file)
772
+ index_offset += 1
564
773
 
565
774
  for input_port_name, input_port in self.inputs.items():
566
- vr = vr_table.add_vr(input_port.type_name)
775
+ input_port.vr = self.vr_table.add_vr(input_port.type_name)
567
776
  # Get Start and XML from first connected input
568
777
  start = self.start_values.get(input_port.cport_list[0], None)
569
- print(f" {input_port.cport_list[0].port.xml(vr, name=input_port_name, start=start)}", file=xml_file)
570
- input_port.vr = vr
571
-
572
- for output_port_name, cport in self.outputs.items():
573
- vr = vr_table.get_vr(cport)
574
- print(f" {cport.port.xml(vr, name=output_port_name)}", file=xml_file)
575
- cport.vr = vr
576
-
577
- xml_file.write(""" </ModelVariables>
578
-
579
- <ModelStructure>
580
- <Outputs>
581
- """)
582
-
583
- index_offset = len(self.locals) + len(self.inputs) + 1
584
- for i, _ in enumerate(self.outputs.keys()):
585
- print(f' <Unknown index="{index_offset+i}"/>', file=xml_file)
586
- xml_file.write(""" </Outputs>
587
- <InitialUnknowns>
588
- """)
589
- for i, _ in enumerate(self.outputs.keys()):
590
- print(f' <Unknown index="{index_offset+i}"/>', file=xml_file)
591
- xml_file.write(""" </InitialUnknowns>
592
- </ModelStructure>
593
-
594
- </fmiModelDescription>
595
- """)
596
-
597
- def make_fmu_txt(self, txt_file, step_size: float, mt: bool, profiling: bool):
598
- if mt:
599
- print("# Use MT\n1", file=txt_file)
600
- else:
601
- print("# Don't use MT\n0", file=txt_file)
602
-
603
- if profiling:
604
- print("# Profiling ENABLED\n1", file=txt_file)
605
- else:
606
- print("# Profiling DISABLED\n0", file=txt_file)
778
+ port_input_def = input_port.cport_list[0].port.xml(input_port.vr, name=input_port_name,
779
+ start=start, fmi_version=self.fmi_version)
780
+ if port_input_def:
781
+ print(f" {port_input_def}", file=xml_file)
782
+ index_offset += 1
783
+
784
+ for output_port_name, output_port in self.outputs.items():
785
+ output_port.vr = self.vr_table.add_vr(output_port)
786
+ port_output_def = output_port.port.xml(output_port.vr, name=output_port_name,
787
+ fmi_version=self.fmi_version)
788
+ if port_output_def:
789
+ print(f" {port_output_def}", file=xml_file)
790
+
791
+ if self.fmi_version == 2:
792
+ self.make_fmu_xml_epilog_2(xml_file, index_offset)
793
+ elif self.fmi_version == 3:
794
+ self.make_fmu_xml_epilog_3(xml_file)
795
+
796
+ def make_fmu_xml_epilog_2(self, xml_file, index_offset):
797
+ xml_file.write(" </ModelVariables>\n"
798
+ "\n"
799
+ " <ModelStructure>\n")
800
+
801
+
802
+ if self.outputs:
803
+ xml_file.write(" <Outputs>\n")
804
+ index = index_offset
805
+ for output in self.outputs.values():
806
+ if output.port.type_name in EmbeddedFMUPort.CONTAINER_TO_FMI[2]:
807
+ print(f' <Unknown index="{index}"/>', file=xml_file)
808
+ index += 1
809
+ xml_file.write(" </Outputs>\n"
810
+ " <InitialUnknowns>\n")
811
+ index = index_offset
812
+ for output in self.outputs.values():
813
+ if output.port.type_name in EmbeddedFMUPort.CONTAINER_TO_FMI[2]:
814
+ print(f' <Unknown index="{index}"/>', file=xml_file)
815
+ index += 1
816
+ xml_file.write(" </InitialUnknowns>\n")
817
+
818
+ xml_file.write(" </ModelStructure>\n"
819
+ "\n"
820
+ "</fmiModelDescription>")
821
+
822
+ def make_fmu_xml_epilog_3(self, xml_file):
823
+ xml_file.write(" </ModelVariables>\n"
824
+ "\n"
825
+ " <ModelStructure>\n")
826
+ for output in self.outputs.values():
827
+ if output.port.type_name in EmbeddedFMUPort.CONTAINER_TO_FMI[3]:
828
+ print(f' <Output valueReference="{output.vr}"/>', file=xml_file)
829
+ for output in self.outputs.values():
830
+ if output.port.type_name in EmbeddedFMUPort.CONTAINER_TO_FMI[3]:
831
+ print(f' <InitialUnknown valueReference="{output.vr}"/>', file=xml_file)
832
+ xml_file.write(" </ModelStructure>\n"
833
+ "\n"
834
+ "</fmiModelDescription>")
835
+
836
+ def make_fmu_txt(self, txt_file, step_size: float, mt: bool, profiling: bool, sequential: bool):
837
+ print("# Container flags <MT> <Profiling> <Sequential>", file=txt_file)
838
+ flags = [ str(int(flag == True)) for flag in (mt, profiling, sequential)]
839
+ print(" ".join(flags), file=txt_file)
607
840
 
608
841
  print(f"# Internal time step in seconds", file=txt_file)
609
842
  print(f"{step_size}", file=txt_file)
610
843
  print(f"# NB of embedded FMU's", file=txt_file)
611
844
  print(f"{len(self.involved_fmu)}", file=txt_file)
612
845
  fmu_rank: Dict[str, int] = {}
613
- for i, fmu in enumerate(self.execution_order):
614
- print(f"{fmu.name}", file=txt_file)
846
+ for i, fmu in enumerate(self.involved_fmu.values()):
847
+ print(f"{fmu.name} {fmu.fmi_version}", file=txt_file)
615
848
  print(f"{fmu.model_identifier}", file=txt_file)
616
849
  print(f"{fmu.guid}", file=txt_file)
617
850
  fmu_rank[fmu.name] = i
618
851
 
619
852
  # Prepare data structure
620
- type_names_list = ("Real", "Integer", "Boolean", "String") # Ordered list
621
- inputs_per_type: Dict[str, List[ContainerInput]] = {} # Container's INPUT
853
+ inputs_per_type: Dict[str, List[ContainerInput]] = {} # Container's INPUT
622
854
  outputs_per_type: Dict[str, List[ContainerPort]] = {} # Container's OUTPUT
623
855
 
624
856
  inputs_fmu_per_type: Dict[str, Dict[str, Dict[ContainerPort, int]]] = {} # [type][fmu]
625
857
  start_values_fmu_per_type = {}
626
858
  outputs_fmu_per_type = {}
627
- locals_per_type: Dict[str, List[Local]] = {}
859
+ local_per_type: Dict[str, List[int]] = {}
860
+ links_per_fmu: Dict[str, List[Link]] = {}
628
861
 
629
- for type_name in type_names_list:
862
+ for type_name in EmbeddedFMUPort.ALL_TYPES:
630
863
  inputs_per_type[type_name] = []
631
864
  outputs_per_type[type_name] = []
632
- locals_per_type[type_name] = []
865
+ local_per_type[type_name] = []
633
866
 
634
867
  inputs_fmu_per_type[type_name] = {}
635
868
  start_values_fmu_per_type[type_name] = {}
636
869
  outputs_fmu_per_type[type_name] = {}
637
870
 
638
- for fmu in self.execution_order:
871
+ for fmu in self.involved_fmu.values():
639
872
  inputs_fmu_per_type[type_name][fmu.name] = {}
640
873
  start_values_fmu_per_type[type_name][fmu.name] = {}
641
874
  outputs_fmu_per_type[type_name][fmu.name] = {}
@@ -644,70 +877,88 @@ class FMUContainer:
644
877
  # Inputs
645
878
  for input_port_name, input_port in self.inputs.items():
646
879
  inputs_per_type[input_port.type_name].append(input_port)
647
- for cport, value in self.start_values.items():
648
- start_values_fmu_per_type[cport.port.type_name][cport.fmu.name][cport] = value
880
+ for input_port, value in self.start_values.items():
881
+ start_values_fmu_per_type[input_port.port.type_name][input_port.fmu.name][input_port] = value
649
882
  # Outputs
650
- for output_port_name, cport in self.outputs.items():
651
- outputs_per_type[cport.port.type_name].append(cport)
652
- # Locals
653
- for local in self.locals.values():
654
- vr = local.vr
655
- locals_per_type[local.cport_from.port.type_name].append(local)
656
- outputs_fmu_per_type[local.cport_from.port.type_name][local.cport_from.fmu.name][local.cport_from] = vr
657
- for cport_to in local.cport_to_list:
658
- inputs_fmu_per_type[cport_to.port.type_name][cport_to.fmu.name][cport_to] = vr
659
-
660
- print(f"# NB local variables Real, Integer, Boolean, String", file=txt_file)
661
- for type_name in type_names_list:
662
- nb = len(locals_per_type[type_name])
663
- if profiling and type_name == "Real":
664
- nb += len(self.execution_order)
665
- print(f"{nb} ", file=txt_file, end='')
883
+ for output_port_name, output_port in self.outputs.items():
884
+ outputs_per_type[output_port.port.type_name].append(output_port)
885
+ # Links
886
+ for link in self.links.values():
887
+ local_per_type[link.cport_from.port.type_name].append(link.vr)
888
+ outputs_fmu_per_type[link.cport_from.port.type_name][link.cport_from.fmu.name][link.cport_from] = link.vr
889
+ for cport_to in link.cport_to_list:
890
+ if cport_to.port.type_name == link.cport_from.port.type_name:
891
+ inputs_fmu_per_type[cport_to.port.type_name][cport_to.fmu.name][cport_to] = link.vr
892
+ else:
893
+ local_per_type[cport_to.port.type_name].append(link.vr_converted[cport_to.port.type_name])
894
+ links_per_fmu.setdefault(link.cport_from.fmu.name, []).append(link)
895
+ inputs_fmu_per_type[cport_to.port.type_name][cport_to.fmu.name][cport_to] = link.vr_converted[cport_to.port.type_name]
896
+
897
+
898
+ print(f"# NB local variables:", ", ".join(EmbeddedFMUPort.ALL_TYPES), file=txt_file)
899
+ nb_local = [f"{self.vr_table.nb_local(type_name)}" for type_name in EmbeddedFMUPort.ALL_TYPES]
900
+ print(" ".join(nb_local), file=txt_file, end='')
666
901
  print("", file=txt_file)
667
902
 
668
903
  print("# CONTAINER I/O: <VR> <NB> <FMU_INDEX> <FMU_VR> [<FMU_INDEX> <FMU_VR>]", file=txt_file)
669
- for type_name in type_names_list:
670
- print(f"# {type_name}", file=txt_file)
671
- nb = len(inputs_per_type[type_name]) + len(outputs_per_type[type_name]) + len(locals_per_type[type_name])
904
+ for type_name in EmbeddedFMUPort.ALL_TYPES:
905
+ print(f"# {type_name}" , file=txt_file)
906
+ nb_local = (len(inputs_per_type[type_name]) +
907
+ len(outputs_per_type[type_name]) +
908
+ self.vr_table.nb_local(type_name))
672
909
  nb_input_link = 0
673
910
  for input_port in inputs_per_type[type_name]:
674
911
  nb_input_link += len(input_port.cport_list) - 1
912
+ print(f"{nb_local} {nb_local + nb_input_link}", file=txt_file)
913
+ if type_name == "real64":
914
+ print(f"0 1 -1 0", file=txt_file) # Time slot
915
+ if profiling:
916
+ for profiling_port, _ in enumerate(self.involved_fmu.values()):
917
+ print(f"{profiling_port + 1} 1 -2 {profiling_port + 1}", file=txt_file)
675
918
 
676
- if profiling and type_name == "Real":
677
- nb += len(self.execution_order)
678
- print(f"{nb} {nb+nb_input_link}", file=txt_file)
679
- for profiling_port, _ in enumerate(self.execution_order):
680
- print(f"{profiling_port} 1 -2 {profiling_port}", file=txt_file)
681
- else:
682
- print(f"{nb} {nb+nb_input_link}", file=txt_file)
683
919
  for input_port in inputs_per_type[type_name]:
684
920
  cport_string = [f"{fmu_rank[cport.fmu.name]} {cport.port.vr}" for cport in input_port.cport_list]
685
921
  print(f"{input_port.vr} {len(input_port.cport_list)}", " ".join(cport_string), file=txt_file)
686
- for cport in outputs_per_type[type_name]:
687
- print(f"{cport.vr} 1 {fmu_rank[cport.fmu.name]} {cport.port.vr}", file=txt_file)
688
- for local in locals_per_type[type_name]:
689
- print(f"{local.vr} 1 -1 {local.vr}", file=txt_file)
922
+ for output_port in outputs_per_type[type_name]:
923
+ print(f"{output_port.vr} 1 {fmu_rank[output_port.fmu.name]} {output_port.port.vr}", file=txt_file)
924
+ for local_vr in local_per_type[type_name]:
925
+ print(f"{local_vr} 1 -1 {local_vr & 0xFFFFFF}", file=txt_file)
690
926
 
691
927
  # LINKS
692
- for fmu in self.execution_order:
693
- for type_name in type_names_list:
928
+ for fmu in self.involved_fmu.values():
929
+ for type_name in EmbeddedFMUPort.ALL_TYPES:
694
930
  print(f"# Inputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
695
931
  print(len(inputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
696
- for cport, vr in inputs_fmu_per_type[type_name][fmu.name].items():
697
- print(f"{vr} {cport.port.vr}", file=txt_file)
932
+ for input_port, vr in inputs_fmu_per_type[type_name][fmu.name].items():
933
+ print(f"{vr} {input_port.port.vr}", file=txt_file)
698
934
 
699
- for type_name in type_names_list:
935
+ for type_name in EmbeddedFMUPort.ALL_TYPES:
700
936
  print(f"# Start values of {fmu.name} - {type_name}: <FMU_VR> <RESET> <VALUE>", file=txt_file)
701
937
  print(len(start_values_fmu_per_type[type_name][fmu.name]), file=txt_file)
702
- for cport, value in start_values_fmu_per_type[type_name][fmu.name].items():
703
- reset = 1 if cport.port.causality == "input" else 0
704
- print(f"{cport.port.vr} {reset} {value}", file=txt_file)
938
+ for input_port, value in start_values_fmu_per_type[type_name][fmu.name].items():
939
+ reset = 1 if input_port.port.causality == "input" else 0
940
+ print(f"{input_port.port.vr} {reset} {value}", file=txt_file)
705
941
 
706
- for type_name in type_names_list:
942
+ for type_name in EmbeddedFMUPort.ALL_TYPES:
707
943
  print(f"# Outputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
708
944
  print(len(outputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
709
- for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
710
- print(f"{vr} {cport.port.vr}", file=txt_file)
945
+ for output_port, vr in outputs_fmu_per_type[type_name][fmu.name].items():
946
+ print(f"{vr} {output_port.port.vr}", file=txt_file)
947
+
948
+ print(f"# Conversion table of {fmu.name}: <VR_FROM> <VR_TO> <CONVERSION>", file=txt_file)
949
+ try:
950
+ nb = 0
951
+ for link in links_per_fmu[fmu.name]:
952
+ nb += len(link.vr_converted)
953
+ print(f"{nb}", file=txt_file)
954
+ for link in links_per_fmu[fmu.name]:
955
+ for cport_to in link.cport_to_list:
956
+ conversion = link.get_conversion(cport_to)
957
+ if conversion:
958
+ print(f"{link.vr} {link.vr_converted[cport_to.port.type_name]} {conversion}",
959
+ file=txt_file)
960
+ except KeyError:
961
+ print("0", file=txt_file)
711
962
 
712
963
  @staticmethod
713
964
  def long_path(path: Union[str, Path]) -> str:
@@ -717,6 +968,39 @@ class FMUContainer:
717
968
  else:
718
969
  return path
719
970
 
971
+ @staticmethod
972
+ def copyfile(origin, destination):
973
+ logger.debug(f"Copying {origin} in {destination}")
974
+ shutil.copy(origin, destination)
975
+
976
+ def get_bindir_and_suffixe(self) -> Tuple[str, str, str]:
977
+ suffixes = {
978
+ "Windows": "dll",
979
+ "Linux": "so",
980
+ "Darwin": "dylib"
981
+ }
982
+
983
+ origin_bindirs = {
984
+ "Windows": "win64",
985
+ "Linux": "linux64",
986
+ "Darwin": "darwin64"
987
+ }
988
+
989
+ if self.fmi_version == 3:
990
+ target_bindirs = {
991
+ "Windows": "x86_64-windows",
992
+ "Linux": "x86_64-linux",
993
+ "Darwin": "aarch64-darwin"
994
+ }
995
+ else:
996
+ target_bindirs = origin_bindirs
997
+
998
+ os_name = platform.system()
999
+ try:
1000
+ return origin_bindirs[os_name], suffixes[os_name], target_bindirs[os_name]
1001
+ except KeyError:
1002
+ raise FMUContainerError(f"OS '{os_name}' is not supported.")
1003
+
720
1004
  def make_fmu_skeleton(self, base_directory: Path) -> Path:
721
1005
  logger.debug(f"Initialize directory '{base_directory}'")
722
1006
 
@@ -731,20 +1015,23 @@ class FMUContainer:
731
1015
  documentation_directory.mkdir(exist_ok=True)
732
1016
 
733
1017
  if self.description_pathname:
734
- logger.debug(f"Copying {self.description_pathname}")
735
- shutil.copy(self.description_pathname, documentation_directory)
1018
+ self.copyfile(self.description_pathname, documentation_directory)
1019
+
1020
+ self.copyfile(origin / "model.png", base_directory)
736
1021
 
737
- shutil.copy(origin / "model.png", base_directory)
738
- for bitness in ('win32', 'win64'):
739
- library_filename = origin / bitness / "container.dll"
740
- if library_filename.is_file():
741
- binary_directory = binaries_directory / bitness
742
- binary_directory.mkdir(exist_ok=True)
743
- shutil.copy(library_filename, binary_directory / f"{self.identifier}.dll")
1022
+ origin_bindir, suffixe, target_bindir = self.get_bindir_and_suffixe()
1023
+
1024
+ library_filename = origin / origin_bindir / f"container.{suffixe}"
1025
+ if not library_filename.is_file():
1026
+ raise FMUContainerError(f"File {library_filename} not found")
1027
+ binary_directory = binaries_directory / target_bindir
1028
+ binary_directory.mkdir(exist_ok=True)
1029
+ self.copyfile(library_filename, binary_directory / f"{self.identifier}.{suffixe}")
744
1030
 
745
1031
  for i, fmu in enumerate(self.involved_fmu.values()):
746
1032
  shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
747
1033
  self.long_path(resources_directory / f"{i:02x}"), dirs_exist_ok=True)
1034
+
748
1035
  return resources_directory
749
1036
 
750
1037
  def make_fmu_package(self, base_directory: Path, fmu_filename: Path):