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