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.
- 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 +21 -12
- fmu_manipulation_toolbox/checker.py +11 -8
- fmu_manipulation_toolbox/cli/__init__.py +0 -0
- fmu_manipulation_toolbox/cli/fmucontainer.py +105 -0
- fmu_manipulation_toolbox/cli/fmusplit.py +48 -0
- fmu_manipulation_toolbox/cli/fmutool.py +127 -0
- fmu_manipulation_toolbox/cli/utils.py +36 -0
- fmu_manipulation_toolbox/container.py +534 -247
- fmu_manipulation_toolbox/gui.py +47 -55
- fmu_manipulation_toolbox/gui_style.py +8 -0
- fmu_manipulation_toolbox/help.py +3 -0
- fmu_manipulation_toolbox/operations.py +251 -179
- fmu_manipulation_toolbox/remoting.py +107 -0
- fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
- fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
- fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/server_sm +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 +67 -34
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/METADATA +1 -1
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/RECORD +34 -27
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/WHEEL +0 -0
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/licenses/LICENSE.txt +0 -0
- {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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
30
|
-
self.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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
|
-
|
|
287
|
-
logger.debug(f"Adding FMU #{len(self.
|
|
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.
|
|
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.
|
|
563
|
+
local = self.links[cport_from]
|
|
349
564
|
except KeyError:
|
|
350
|
-
local =
|
|
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.
|
|
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:
|
|
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
|
|
434
|
-
|
|
435
|
-
for fmu in self.
|
|
436
|
-
if step_size:
|
|
437
|
-
|
|
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
|
-
|
|
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.
|
|
450
|
-
if
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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.
|
|
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 =
|
|
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 =
|
|
510
|
-
logger.info(f"start_time={self.start_time} (deduced from '{
|
|
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 =
|
|
516
|
-
logger.info(f"stop_time={self.stop_time} (deduced from '{
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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.
|
|
554
|
-
vr = vr_table.add_vr("
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
859
|
+
local_per_type: Dict[str, List[int]] = {}
|
|
860
|
+
links_per_fmu: Dict[str, List[Link]] = {}
|
|
628
861
|
|
|
629
|
-
for type_name in
|
|
862
|
+
for type_name in EmbeddedFMUPort.ALL_TYPES:
|
|
630
863
|
inputs_per_type[type_name] = []
|
|
631
864
|
outputs_per_type[type_name] = []
|
|
632
|
-
|
|
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.
|
|
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
|
|
648
|
-
start_values_fmu_per_type[
|
|
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,
|
|
651
|
-
outputs_per_type[
|
|
652
|
-
#
|
|
653
|
-
for
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
|
670
|
-
print(f"# {type_name}", file=txt_file)
|
|
671
|
-
|
|
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
|
|
687
|
-
print(f"{
|
|
688
|
-
for
|
|
689
|
-
print(f"{
|
|
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.
|
|
693
|
-
for type_name in
|
|
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
|
|
697
|
-
print(f"{vr} {
|
|
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
|
|
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
|
|
703
|
-
reset = 1 if
|
|
704
|
-
print(f"{
|
|
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
|
|
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
|
|
710
|
-
print(f"{vr} {
|
|
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
|
-
|
|
735
|
-
|
|
1018
|
+
self.copyfile(self.description_pathname, documentation_directory)
|
|
1019
|
+
|
|
1020
|
+
self.copyfile(origin / "model.png", base_directory)
|
|
736
1021
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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):
|