KratosMappingApplication 10.4.4__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.
- KratosMultiphysics/.libs/KratosMappingApplication.so +0 -0
- KratosMultiphysics/.libs/libKratosMappingCore.dylib +0 -0
- KratosMultiphysics/MappingApplication/__init__.py +51 -0
- KratosMultiphysics/MappingApplication/empire_mortar_mapper.py +342 -0
- KratosMultiphysics/MappingApplication/python_mapper.py +55 -0
- KratosMultiphysics/MappingApplication/python_mapper_factory.py +39 -0
- KratosMultiphysics/MappingApplication/serial_output_process.py +143 -0
- kratosmappingapplication-10.4.4.dist-info/METADATA +504 -0
- kratosmappingapplication-10.4.4.dist-info/RECORD +14 -0
- kratosmappingapplication-10.4.4.dist-info/WHEEL +6 -0
- kratosmappingapplication.dylibs/libKratosCore.dylib +4 -0
- kratosmappingapplication.dylibs/libgcc_s.1.1.dylib +0 -0
- kratosmappingapplication.dylibs/libgomp.1.dylib +0 -0
- kratosmappingapplication.dylibs/libstdc++.6.dylib +0 -0
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Application dependent names and paths
|
|
2
|
+
from KratosMultiphysics import _ImportApplication
|
|
3
|
+
from KratosMultiphysics import Mapper as _CoreMapper
|
|
4
|
+
from KratosMultiphysics import MapperFactory as _CoreMapperFactory
|
|
5
|
+
from KratosMappingApplication import *
|
|
6
|
+
application = KratosMappingApplication()
|
|
7
|
+
application_name = "KratosMappingApplication"
|
|
8
|
+
|
|
9
|
+
_ImportApplication(application, application_name)
|
|
10
|
+
|
|
11
|
+
# hacks for backward compatibility
|
|
12
|
+
from KratosMultiphysics.kratos_utilities import IssueDeprecationWarning
|
|
13
|
+
|
|
14
|
+
class _DeprecatedMapper:
|
|
15
|
+
def __getattribute__(self, method_name):
|
|
16
|
+
IssueDeprecationWarning("MappingApplication", 'The "Mapper" was moved to the Core! (used for "{}")'.format(method_name))
|
|
17
|
+
return getattr(_CoreMapper, method_name)
|
|
18
|
+
|
|
19
|
+
class _DeprecatedMapperFactory:
|
|
20
|
+
def __getattr__(self, method_name):
|
|
21
|
+
IssueDeprecationWarning("MappingApplication", 'The "MapperFactory" was moved to the Core! (used for "{}")'.format(method_name))
|
|
22
|
+
return getattr(_CoreMapperFactory, method_name)
|
|
23
|
+
|
|
24
|
+
def CreateMPIMapper(self, *args):
|
|
25
|
+
IssueDeprecationWarning("MappingApplication", 'CreateMPIMapper is deprecated, please use "MappingApplication.MPIExtension.MPIMapperFactory.CreateMapper" instead')
|
|
26
|
+
from KratosMultiphysics.MappingApplication.MPIExtension import MPIMapperFactory
|
|
27
|
+
return MPIMapperFactory.CreateMapper(*args)
|
|
28
|
+
|
|
29
|
+
def HasMPIMapper(self, *args):
|
|
30
|
+
IssueDeprecationWarning("MappingApplication", 'HasMPIMapper is deprecated, please use "MappingApplication.MPIExtension.MPIMapperFactory.HasMapper" instead')
|
|
31
|
+
from KratosMultiphysics.MappingApplication.MPIExtension import MPIMapperFactory
|
|
32
|
+
return MPIMapperFactory.HasMapper(*args)
|
|
33
|
+
|
|
34
|
+
def GetRegisteredMPIMapperNames(self, *args):
|
|
35
|
+
IssueDeprecationWarning("MappingApplication", 'GetRegisteredMPIMapperNames is deprecated, please use "MappingApplication.MPIExtension.MPIMapperFactory.GetRegisteredMapperNames" instead')
|
|
36
|
+
from KratosMultiphysics.MappingApplication.MPIExtension import MPIMapperFactory
|
|
37
|
+
return MPIMapperFactory.GetRegisteredMapperNames(*args)
|
|
38
|
+
|
|
39
|
+
Mapper = _DeprecatedMapper()
|
|
40
|
+
MapperFactory = _DeprecatedMapperFactory()
|
|
41
|
+
|
|
42
|
+
'''
|
|
43
|
+
TODO:
|
|
44
|
+
- Test for Serialization
|
|
45
|
+
- Test for local-search?
|
|
46
|
+
- Cleanup how the MapperParams are used
|
|
47
|
+
- Further cleanup Trilinos and try some things (read up on opt-stuff)
|
|
48
|
+
- use std::unordered_set for row & column indices-vectors in trilinos => does the map need sorted indices?
|
|
49
|
+
- Function-Documentation
|
|
50
|
+
- Delete copy and assignment-constructors?
|
|
51
|
+
'''
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Importing the Kratos Library
|
|
2
|
+
import KratosMultiphysics as KM
|
|
3
|
+
|
|
4
|
+
# Mapping imports
|
|
5
|
+
from KratosMultiphysics.MappingApplication import Mapper
|
|
6
|
+
from KratosMultiphysics.MappingApplication.python_mapper import PythonMapper
|
|
7
|
+
|
|
8
|
+
# other imports
|
|
9
|
+
import os
|
|
10
|
+
import ctypes as ctp
|
|
11
|
+
|
|
12
|
+
def Create(model_part_origin, model_part_destination, mapper_settings):
|
|
13
|
+
return EmpireMortarMapper(model_part_origin, model_part_destination, mapper_settings)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EmpireMortarMapper(PythonMapper):
|
|
17
|
+
"""Wrapper for the Mortar mapper of EMPIRE
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
Empire needs to be compiled separately
|
|
21
|
+
It can then be used by either of the two ways:
|
|
22
|
+
- (default) use "startEMPIRE" to bring "EMPIRE_MAPPER_LIBSO_ON_MACHINE" to the environment
|
|
23
|
+
- use "path_mapper_lib" to sprecify the path to "libEMPIRE_MapperLib.so" (by default located in "EMPIRE-Core/lib/")
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
mapper_count = 0
|
|
27
|
+
instances = 0
|
|
28
|
+
mapper_lib = None
|
|
29
|
+
|
|
30
|
+
def __init__(self, model_part_origin, model_part_destination, mapper_settings):
|
|
31
|
+
super().__init__(model_part_origin, model_part_destination, mapper_settings)
|
|
32
|
+
|
|
33
|
+
if model_part_origin.IsDistributed() or model_part_destination.IsDistributed():
|
|
34
|
+
raise Exception('{} does not support mapping with distributed ModelParts!'.format(self._ClassName()))
|
|
35
|
+
|
|
36
|
+
if EmpireMortarMapper.mapper_lib:
|
|
37
|
+
KM.Logger.PrintInfo("EmpireMortarMapper", "Mapper lib is already loaded")
|
|
38
|
+
else:
|
|
39
|
+
KM.Logger.PrintInfo("EmpireMortarMapper", "Attempting to load mapper lib")
|
|
40
|
+
EmpireMortarMapper.mapper_lib = LoadEmpireMapperLib(self.mapper_settings["path_mapper_lib"].GetString())
|
|
41
|
+
|
|
42
|
+
self.mapper_name = "EmpireMortarMapper_"+str(EmpireMortarMapper.mapper_count)
|
|
43
|
+
|
|
44
|
+
self.mesh_name_origin = model_part_origin.FullName()+"_o_"+str(EmpireMortarMapper.mapper_count)
|
|
45
|
+
self.mesh_name_destination = model_part_destination.FullName()+"_d_"+str(EmpireMortarMapper.mapper_count)
|
|
46
|
+
|
|
47
|
+
self.__CreateEmpireFEMesh(self.model_part_origin, self.mesh_name_origin)
|
|
48
|
+
self.__CreateEmpireFEMesh(self.model_part_destination, self.mesh_name_destination)
|
|
49
|
+
|
|
50
|
+
self.__CreateMapper()
|
|
51
|
+
|
|
52
|
+
EmpireMortarMapper.mapper_count += 1 # required for identification purposes
|
|
53
|
+
EmpireMortarMapper.instances += 1
|
|
54
|
+
self.__inverse_mapper = None
|
|
55
|
+
|
|
56
|
+
def __del__(self):
|
|
57
|
+
if EmpireMortarMapper.mapper_lib.hasMapper(ConvertToChar(self.mapper_name)):
|
|
58
|
+
EmpireMortarMapper.mapper_lib.deleteMapper(ConvertToChar(self.mapper_name))
|
|
59
|
+
|
|
60
|
+
if EmpireMortarMapper.mapper_lib.hasMesh(ConvertToChar(self.mesh_name_origin)):
|
|
61
|
+
EmpireMortarMapper.mapper_lib.deleteMesh(ConvertToChar(self.mesh_name_origin))
|
|
62
|
+
if EmpireMortarMapper.mapper_lib.hasMesh(ConvertToChar(self.mesh_name_destination)):
|
|
63
|
+
EmpireMortarMapper.mapper_lib.deleteMesh(ConvertToChar(self.mesh_name_destination))
|
|
64
|
+
|
|
65
|
+
EmpireMortarMapper.instances -= 1
|
|
66
|
+
if EmpireMortarMapper.instances == 0: # last mapper was destroyed
|
|
67
|
+
if self.echo_level > 1:
|
|
68
|
+
KM.Logger.PrintInfo('EmpireMortarMapper', 'Destroying last instance, deleting all meshes & mappers')
|
|
69
|
+
# delete everything to make sure nothing is left
|
|
70
|
+
EmpireMortarMapper.mapper_lib.deleteAllMappers()
|
|
71
|
+
EmpireMortarMapper.mapper_lib.deleteAllMeshes()
|
|
72
|
+
|
|
73
|
+
def UpdateInterface(self):
|
|
74
|
+
raise NotImplementedError('"UpdateInterface" is not yet implemented for "{}"!'.format(self._ClassName()))
|
|
75
|
+
|
|
76
|
+
# protected methods
|
|
77
|
+
def _MapInternal(self, variable_origin, variable_destination, mapper_flags):
|
|
78
|
+
if mapper_flags.Is(Mapper.USE_TRANSPOSE):
|
|
79
|
+
mapper_flags.Reset(Mapper.USE_TRANSPOSE)
|
|
80
|
+
mapper_flags.Set(KM.VISITED, True)
|
|
81
|
+
self.__GetInverseMapper().Map(variable_destination, variable_origin, mapper_flags)
|
|
82
|
+
return
|
|
83
|
+
elif mapper_flags.Is(KM.VISITED):
|
|
84
|
+
self.__MapInternalTranspose(variable_origin, variable_destination, mapper_flags)
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
self.__CheckMapperExists()
|
|
88
|
+
|
|
89
|
+
var_dim = GetVariableDimension(variable_origin)
|
|
90
|
+
|
|
91
|
+
origin_data_size = self.model_part_origin.NumberOfNodes()*var_dim
|
|
92
|
+
destination_data_size = self.model_part_destination.NumberOfNodes()*var_dim
|
|
93
|
+
|
|
94
|
+
c_origin_array = KratosFieldToCArray(self.model_part_origin.Nodes, variable_origin)
|
|
95
|
+
c_destination_array = (ctp.c_double * destination_data_size)(0.0)
|
|
96
|
+
|
|
97
|
+
EmpireMortarMapper.mapper_lib.doConsistentMapping(
|
|
98
|
+
ConvertToChar(self.mapper_name),
|
|
99
|
+
ctp.c_int(var_dim),
|
|
100
|
+
ctp.c_int(origin_data_size),
|
|
101
|
+
c_origin_array,
|
|
102
|
+
ctp.c_int(destination_data_size),
|
|
103
|
+
c_destination_array
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
CArrayToKratosField(
|
|
107
|
+
c_destination_array,
|
|
108
|
+
destination_data_size,
|
|
109
|
+
self.model_part_destination.Nodes,
|
|
110
|
+
variable_destination,
|
|
111
|
+
mapper_flags.Is(Mapper.ADD_VALUES), mapper_flags.Is(Mapper.SWAP_SIGN))
|
|
112
|
+
|
|
113
|
+
def _InverseMapInternal(self, variable_origin, variable_destination, mapper_flags):
|
|
114
|
+
if mapper_flags.Is(Mapper.USE_TRANSPOSE):
|
|
115
|
+
self.__MapInternalTranspose(variable_origin, variable_destination, mapper_flags)
|
|
116
|
+
else:
|
|
117
|
+
self.__GetInverseMapper().Map(variable_destination, variable_origin, mapper_flags)
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def _GetDefaultParameters(cls):
|
|
121
|
+
this_defaults = KM.Parameters("""{
|
|
122
|
+
"path_mapper_lib" : "",
|
|
123
|
+
"dual" : false,
|
|
124
|
+
"enforce_consistency" : false,
|
|
125
|
+
"opposite_normals" : false,
|
|
126
|
+
"use_initial_configuration" : false
|
|
127
|
+
}""")
|
|
128
|
+
this_defaults.AddMissingParameters(super()._GetDefaultParameters())
|
|
129
|
+
return this_defaults
|
|
130
|
+
|
|
131
|
+
# private methods
|
|
132
|
+
def __CreateEmpireFEMesh(self, model_part, mesh_name):
|
|
133
|
+
if model_part.NumberOfNodes() < 1:
|
|
134
|
+
raise Exception('No nodes exist in ModelPart "{}"!'.format(model_part.FullName()))
|
|
135
|
+
|
|
136
|
+
num_elements = model_part.NumberOfElements()
|
|
137
|
+
num_conditions = model_part.NumberOfConditions()
|
|
138
|
+
|
|
139
|
+
if num_elements > 0 and num_conditions > 0:
|
|
140
|
+
err_msg = "Both Elements and Conditions are present which is not allowed!\n"
|
|
141
|
+
err_msg += "Name of ModelPart: {}\n".format(model_part.FullName())
|
|
142
|
+
err_msg += "Number of Elements: {}\n".format(num_elements)
|
|
143
|
+
err_msg += "Number of Conditions: {}".format(num_elements)
|
|
144
|
+
raise Exception(err_msg)
|
|
145
|
+
|
|
146
|
+
if num_elements + num_conditions == 0:
|
|
147
|
+
err_msg = "No Elements and Conditions are present which is not allowed!\n"
|
|
148
|
+
err_msg += "Name of ModelPart: {}\n".format(model_part.FullName())
|
|
149
|
+
raise Exception(err_msg)
|
|
150
|
+
|
|
151
|
+
if num_conditions > 0:
|
|
152
|
+
entities_to_use = model_part.Conditions
|
|
153
|
+
else:
|
|
154
|
+
entities_to_use = model_part.Elements
|
|
155
|
+
|
|
156
|
+
for ent in entities_to_use:
|
|
157
|
+
if ent.GetGeometry().PointsNumber() not in [3,4]:
|
|
158
|
+
raise Exception("The EmpireMortarMapper only works with Triangles and Quadrilaterals")
|
|
159
|
+
|
|
160
|
+
c_mesh_name = ConvertToChar(mesh_name)
|
|
161
|
+
|
|
162
|
+
if EmpireMortarMapper.mapper_lib.hasMesh(c_mesh_name):
|
|
163
|
+
raise Exception('Mesh "{}" exists already in Empire!'.format(mesh_name))
|
|
164
|
+
|
|
165
|
+
c_num_nodes = ctp.c_int(model_part.NumberOfNodes())
|
|
166
|
+
c_num_elems = ctp.c_int(len(entities_to_use))
|
|
167
|
+
c_node_ids = (ctp.c_int * c_num_nodes.value) (0)
|
|
168
|
+
c_node_coords = (ctp.c_double * (3 * c_num_nodes.value))(0.0)
|
|
169
|
+
c_num_nodes_per_elem = (ctp.c_int * c_num_elems.value) (0)
|
|
170
|
+
|
|
171
|
+
if self.mapper_settings["use_initial_configuration"].GetBool():
|
|
172
|
+
for i_node, node in enumerate(model_part.Nodes):
|
|
173
|
+
c_node_ids[i_node] = node.Id
|
|
174
|
+
c_node_coords[i_node*3] = node.X0
|
|
175
|
+
c_node_coords[i_node*3+1] = node.Y0
|
|
176
|
+
c_node_coords[i_node*3+2] = node.Z0
|
|
177
|
+
|
|
178
|
+
else:
|
|
179
|
+
for i_node, node in enumerate(model_part.Nodes):
|
|
180
|
+
c_node_ids[i_node] = node.Id
|
|
181
|
+
c_node_coords[i_node*3] = node.X
|
|
182
|
+
c_node_coords[i_node*3+1] = node.Y
|
|
183
|
+
c_node_coords[i_node*3+2] = node.Z
|
|
184
|
+
|
|
185
|
+
elem_node_ctr = 0
|
|
186
|
+
for elem_ctr, elem in enumerate(entities_to_use):
|
|
187
|
+
c_num_nodes_per_elem[elem_ctr] = len(elem.GetNodes())
|
|
188
|
+
elem_node_ctr += c_num_nodes_per_elem[elem_ctr]
|
|
189
|
+
|
|
190
|
+
elem_index = 0
|
|
191
|
+
c_elems = (ctp.c_int * elem_node_ctr) (0)
|
|
192
|
+
for elem_ctr, elem in enumerate(entities_to_use):
|
|
193
|
+
for elem_node_ctr, elem_node in enumerate(elem.GetNodes()):
|
|
194
|
+
c_elems[elem_index + elem_node_ctr] = elem_node.Id
|
|
195
|
+
elem_index += len(elem.GetNodes())
|
|
196
|
+
|
|
197
|
+
triangulateAll = False
|
|
198
|
+
EmpireMortarMapper.mapper_lib.initFEMesh(c_mesh_name, c_num_nodes, c_num_elems, triangulateAll)
|
|
199
|
+
EmpireMortarMapper.mapper_lib.setNodesToFEMesh(c_mesh_name, c_node_ids, c_node_coords)
|
|
200
|
+
EmpireMortarMapper.mapper_lib.setElementsToFEMesh(c_mesh_name, c_num_nodes_per_elem, c_elems)
|
|
201
|
+
|
|
202
|
+
if self.echo_level > 1:
|
|
203
|
+
KM.Logger.PrintInfo('EmpireMortarMapper', 'Printing Mesh "{}"'.format(model_part.FullName()))
|
|
204
|
+
EmpireMortarMapper.mapper_lib.printMesh(c_mesh_name)
|
|
205
|
+
|
|
206
|
+
def __GetInverseMapper(self):
|
|
207
|
+
if not self.__inverse_mapper:
|
|
208
|
+
self.__inverse_mapper = self.__class__(self.model_part_destination, self.model_part_origin, self.mapper_settings)
|
|
209
|
+
return self.__inverse_mapper
|
|
210
|
+
|
|
211
|
+
def __CreateMapper(self):
|
|
212
|
+
dual = int(self.mapper_settings["dual"].GetBool())
|
|
213
|
+
enforceConsistency = int(self.mapper_settings["enforce_consistency"].GetBool())
|
|
214
|
+
opposite_normals = int(self.mapper_settings["opposite_normals"].GetBool())
|
|
215
|
+
|
|
216
|
+
EmpireMortarMapper.mapper_lib.initFEMMortarMapper(
|
|
217
|
+
ConvertToChar(self.mapper_name),
|
|
218
|
+
ConvertToChar(self.mesh_name_origin),
|
|
219
|
+
ConvertToChar(self.mesh_name_destination),
|
|
220
|
+
ctp.c_int(opposite_normals),
|
|
221
|
+
ctp.c_int(dual),
|
|
222
|
+
ctp.c_int(enforceConsistency)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
self.__CheckMapperExists()
|
|
226
|
+
|
|
227
|
+
EmpireMortarMapper.mapper_lib.buildCouplingMatrices(ConvertToChar(self.mapper_name))
|
|
228
|
+
|
|
229
|
+
def __CheckMapperExists(self):
|
|
230
|
+
if not EmpireMortarMapper.mapper_lib.hasMapper(ConvertToChar(self.mapper_name)):
|
|
231
|
+
raise Exception('Mapper "{}" does not exist!'.format(self.mapper_name))
|
|
232
|
+
|
|
233
|
+
def __MapInternalTranspose(self, variable_origin, variable_destination, mapper_flags):
|
|
234
|
+
self.__CheckMapperExists()
|
|
235
|
+
|
|
236
|
+
var_dim = GetVariableDimension(variable_destination)
|
|
237
|
+
|
|
238
|
+
origin_data_size = self.model_part_origin.NumberOfNodes()*var_dim
|
|
239
|
+
destination_data_size = self.model_part_destination.NumberOfNodes()*var_dim
|
|
240
|
+
|
|
241
|
+
c_destination_array = KratosFieldToCArray(self.model_part_destination.Nodes, variable_destination)
|
|
242
|
+
c_origin_array = (ctp.c_double * origin_data_size)(0.0)
|
|
243
|
+
|
|
244
|
+
EmpireMortarMapper.mapper_lib.doConservativeMapping(
|
|
245
|
+
ConvertToChar(self.mapper_name),
|
|
246
|
+
ctp.c_int(var_dim),
|
|
247
|
+
ctp.c_int(destination_data_size),
|
|
248
|
+
c_destination_array,
|
|
249
|
+
ctp.c_int(origin_data_size),
|
|
250
|
+
c_origin_array
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
CArrayToKratosField(
|
|
254
|
+
c_origin_array,
|
|
255
|
+
origin_data_size,
|
|
256
|
+
self.model_part_origin.Nodes,
|
|
257
|
+
variable_origin,
|
|
258
|
+
mapper_flags.Is(Mapper.ADD_VALUES), mapper_flags.Is(Mapper.SWAP_SIGN))
|
|
259
|
+
|
|
260
|
+
# Helper functions
|
|
261
|
+
def GetVariableDimension(variable):
|
|
262
|
+
var_type = KM.KratosGlobals.GetVariableType(variable.Name())
|
|
263
|
+
if var_type == "Array":
|
|
264
|
+
return 3
|
|
265
|
+
elif var_type == "Double":
|
|
266
|
+
return 1
|
|
267
|
+
else:
|
|
268
|
+
raise Exception('Wrong variable type: "{}". Only "Array", "Double" and "Component" are allowed'.format(var_type))
|
|
269
|
+
|
|
270
|
+
def ConvertToChar(string):
|
|
271
|
+
return ctp.c_char_p(string.encode(encoding='UTF-8'))
|
|
272
|
+
|
|
273
|
+
def LoadEmpireMapperLib(path_mapper_lib):
|
|
274
|
+
KM.Logger.PrintInfo("EmpireMapperLibLoader", "Determining path to mapper lib")
|
|
275
|
+
# first try automatic detection using the environment that is set by Empire => startEMPIRE
|
|
276
|
+
if ('EMPIRE_MAPPER_LIBSO_ON_MACHINE' in os.environ):
|
|
277
|
+
KM.Logger.PrintInfo("EmpireMapperLibLoader", "EMPIRE_MAPPER_LIBSO_ON_MACHINE found in environment")
|
|
278
|
+
mapper_lib_path = os.environ['EMPIRE_MAPPER_LIBSO_ON_MACHINE']
|
|
279
|
+
|
|
280
|
+
else:
|
|
281
|
+
KM.Logger.PrintInfo("EmpireMapperLibLoader", "EMPIRE_MAPPER_LIBSO_ON_MACHINE NOT found in environment, using manually specified path to load the mapper lib")
|
|
282
|
+
mapper_lib_path = path_mapper_lib
|
|
283
|
+
if mapper_lib_path == "":
|
|
284
|
+
raise Exception('The automatic detection of the mapper lib failed, the path to the mapper lib has to be specified!')
|
|
285
|
+
|
|
286
|
+
KM.Logger.PrintInfo("EmpireMapperLibLoader", "Attempting to load the mapper lib")
|
|
287
|
+
# TODO check if still both are needed! (does the mapperlib link to MPI?)
|
|
288
|
+
try:
|
|
289
|
+
try: # OpenMPI
|
|
290
|
+
loaded_mapper_lib = ctp.CDLL(mapper_lib_path, ctp.RTLD_GLOBAL)
|
|
291
|
+
KM.Logger.PrintInfo('EmpireMapperLibLoader', 'Using standard OpenMPI')
|
|
292
|
+
except: # Intel MPI or OpenMPI compiled with "–disable-dlopen" option
|
|
293
|
+
loaded_mapper_lib = ctp.cdll.LoadLibrary(mapper_lib_path)
|
|
294
|
+
KM.Logger.PrintInfo('EmpireMapperLibLoader', 'Using Intel MPI or OpenMPI compiled with "–disable-dlopen" option')
|
|
295
|
+
except OSError:
|
|
296
|
+
raise Exception("Mapper lib could not be loaded!")
|
|
297
|
+
|
|
298
|
+
KM.Logger.PrintInfo("EmpireMapperLibLoader", 'Successfully loaded the mapper lib from "{}"'.format(mapper_lib_path))
|
|
299
|
+
|
|
300
|
+
return loaded_mapper_lib
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def SetSolutionStepValue(node, variable, value):
|
|
304
|
+
return node.SetSolutionStepValue(variable, 0, value)
|
|
305
|
+
|
|
306
|
+
def KratosFieldToCArray(nodes, variable):
|
|
307
|
+
dim = GetVariableDimension(variable)
|
|
308
|
+
size = dim * len(nodes)
|
|
309
|
+
c_array = (ctp.c_double * size)(0.0)
|
|
310
|
+
|
|
311
|
+
if dim == 1:
|
|
312
|
+
for i_node, node in enumerate(nodes):
|
|
313
|
+
c_array[i_node] = node.GetSolutionStepValue(variable)
|
|
314
|
+
else:
|
|
315
|
+
for i_node, node in enumerate(nodes):
|
|
316
|
+
node_value = node.GetSolutionStepValue(variable)
|
|
317
|
+
for i_dim in range(dim):
|
|
318
|
+
c_array[i_node*dim + i_dim] = node_value[i_dim]
|
|
319
|
+
|
|
320
|
+
return c_array
|
|
321
|
+
|
|
322
|
+
def CArrayToKratosField(c_array, c_array_size, nodes, variable, add_values, swap_sign):
|
|
323
|
+
dim = GetVariableDimension(variable)
|
|
324
|
+
if c_array_size != dim * len(nodes):
|
|
325
|
+
raise RuntimeError("Wrong size!")
|
|
326
|
+
|
|
327
|
+
if swap_sign:
|
|
328
|
+
for i in range(c_array_size):
|
|
329
|
+
c_array[i] *= (-1)
|
|
330
|
+
|
|
331
|
+
if add_values:
|
|
332
|
+
current_values = KratosFieldToCArray(nodes, variable)
|
|
333
|
+
for i in range(c_array_size):
|
|
334
|
+
c_array[i] += current_values[i]
|
|
335
|
+
|
|
336
|
+
if dim == 1:
|
|
337
|
+
for i_node, node in enumerate(nodes):
|
|
338
|
+
node.SetSolutionStepValue(variable, 0, c_array[i_node])
|
|
339
|
+
else:
|
|
340
|
+
for i_node, node in enumerate(nodes):
|
|
341
|
+
values = [c_array[i_node*dim], c_array[i_node*dim+1], c_array[i_node*dim+2]]
|
|
342
|
+
node.SetSolutionStepValue(variable, 0, values)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Importing the Kratos Library
|
|
2
|
+
import KratosMultiphysics as KM
|
|
3
|
+
|
|
4
|
+
# Other imports
|
|
5
|
+
from abc import ABCMeta, abstractmethod
|
|
6
|
+
|
|
7
|
+
class PythonMapper(metaclass=ABCMeta):
|
|
8
|
+
"""Baseclass for python based mappers in Kratos
|
|
9
|
+
The interface matches the C++ version ("custom_mappers/mapper.h")
|
|
10
|
+
The py-mappers are intentionally NOT derived from the c++ version.
|
|
11
|
+
Reasons:
|
|
12
|
+
- Doing so would require some special treatment of the pure virtual functions exposed to python
|
|
13
|
+
- They are more or less temporary until Kratos has more Mappers
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, model_part_origin, model_part_destination, mapper_settings):
|
|
16
|
+
self.model_part_origin = model_part_origin
|
|
17
|
+
self.model_part_destination = model_part_destination
|
|
18
|
+
|
|
19
|
+
self.mapper_settings = mapper_settings
|
|
20
|
+
self.mapper_settings.ValidateAndAssignDefaults(self._GetDefaultParameters())
|
|
21
|
+
|
|
22
|
+
self.echo_level = self.mapper_settings["echo_level"].GetInt()
|
|
23
|
+
|
|
24
|
+
# public methods, same as in "custom_mappers/mapper.h"
|
|
25
|
+
def Map(self, variable_origin, variable_destination, mapper_flags=KM.Flags()):
|
|
26
|
+
CheckVariables(variable_origin, variable_destination)
|
|
27
|
+
self._MapInternal(variable_origin, variable_destination, mapper_flags)
|
|
28
|
+
|
|
29
|
+
def InverseMap(self, variable_origin, variable_destination, mapper_flags=KM.Flags()):
|
|
30
|
+
CheckVariables(variable_origin, variable_destination)
|
|
31
|
+
self._InverseMapInternal(variable_origin, variable_destination, mapper_flags)
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def UpdateInterface(self): pass
|
|
35
|
+
|
|
36
|
+
# protected methods
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def _MapInternal(self, variable_origin, variable_destination, mapper_flags): pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def _InverseMapInternal(self, variable_origin, variable_destination, mapper_flags): pass
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def _GetDefaultParameters(cls):
|
|
45
|
+
return KM.Parameters("""{
|
|
46
|
+
"mapper_type" : "",
|
|
47
|
+
"echo_level" : 0
|
|
48
|
+
}""")
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def _ClassName(cls):
|
|
52
|
+
return cls.__name__
|
|
53
|
+
|
|
54
|
+
def CheckVariables(variable_origin, variable_destination):
|
|
55
|
+
pass
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Python factory for all the currently available Mappers in Kratos
|
|
2
|
+
# The intention is to give the users a unique place to create Mappers
|
|
3
|
+
# The goal is to implement the Mappers from the other Apps also in the
|
|
4
|
+
# MappingApp (which inherently also work in MPI) and replace them in
|
|
5
|
+
# the long run.
|
|
6
|
+
# This way users won't notice / won't have to change their code
|
|
7
|
+
|
|
8
|
+
import KratosMultiphysics as KM
|
|
9
|
+
|
|
10
|
+
from importlib import import_module
|
|
11
|
+
|
|
12
|
+
def _InternalCreateMapper(mapper_factory, err_info, model_part_origin, model_part_destination, mapper_settings):
|
|
13
|
+
if not isinstance(mapper_settings, KM.Parameters):
|
|
14
|
+
raise Exception("expected input shall be a Parameters object, encapsulating a json string")
|
|
15
|
+
|
|
16
|
+
mapper_type = mapper_settings["mapper_type"].GetString()
|
|
17
|
+
|
|
18
|
+
# use the MappingApp if it has the requested mapper
|
|
19
|
+
if mapper_factory.HasMapper(mapper_type):
|
|
20
|
+
return mapper_factory.CreateMapper(model_part_origin, model_part_destination, mapper_settings)
|
|
21
|
+
else:
|
|
22
|
+
mapper_module = import_module(mapper_type)
|
|
23
|
+
return mapper_module.Create(model_part_origin, model_part_destination, mapper_settings)
|
|
24
|
+
|
|
25
|
+
list_avail_mappers = mapper_factory.GetRegisteredMapperNames()
|
|
26
|
+
|
|
27
|
+
err_msg = 'The requested mapper "{}" is not available in {}\n'.format(mapper_type, err_info)
|
|
28
|
+
err_msg += 'The following mappers are available:'
|
|
29
|
+
for avail_mapper in list_avail_mappers:
|
|
30
|
+
err_msg += '\n\t{}'.format(avail_mapper)
|
|
31
|
+
raise Exception(err_msg)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def CreateMapper(model_part_origin, model_part_destination, mapper_settings):
|
|
35
|
+
return _InternalCreateMapper(KM.MapperFactory, "serial (non-MPI)", model_part_origin, model_part_destination, mapper_settings)
|
|
36
|
+
|
|
37
|
+
def CreateMPIMapper(model_part_origin, model_part_destination, mapper_settings):
|
|
38
|
+
from KratosMultiphysics.MappingApplication.MPIExtension import MPIMapperFactory
|
|
39
|
+
return _InternalCreateMapper(MPIMapperFactory, "MPI", model_part_origin, model_part_destination, mapper_settings)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import KratosMultiphysics as KM
|
|
2
|
+
from KratosMultiphysics import MappingApplication # registering the mappers
|
|
3
|
+
from KratosMultiphysics.kratos_utilities import GenerateVariableListFromInput
|
|
4
|
+
from KratosMultiphysics.process_factory import KratosProcessFactory
|
|
5
|
+
|
|
6
|
+
def Factory(settings, model):
|
|
7
|
+
if not isinstance(settings, KM.Parameters):
|
|
8
|
+
raise Exception("expected input shall be a Parameters object, encapsulating a json string")
|
|
9
|
+
return SerialOutputProcess(model, settings["Parameters"])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SerialOutputProcess(KM.OutputProcess):
|
|
13
|
+
"""This process is used in distributed simulations to do post-processing on one rank
|
|
14
|
+
For this first the results are mapped to a serial ModelPart, with which then postprocessing is done
|
|
15
|
+
This is not the most efficient approach by principle, but sometimes it is necessary to collect
|
|
16
|
+
all the results on one rank
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self, model, settings):
|
|
19
|
+
super().__init__()
|
|
20
|
+
|
|
21
|
+
self.settings = settings
|
|
22
|
+
|
|
23
|
+
default_settings = KM.Parameters('''{
|
|
24
|
+
"main_model_part_name_origin" : "UNSPECIFIED",
|
|
25
|
+
"main_model_part_name_destination" : "UNSPECIFIED",
|
|
26
|
+
"mdpa_file_name_destination" : "",
|
|
27
|
+
"historical_variables_destination" : [],
|
|
28
|
+
"destination_rank" : 0,
|
|
29
|
+
"mapper_settings" : {},
|
|
30
|
+
"mapping_settings" : [],
|
|
31
|
+
"output_process_settings" : {}
|
|
32
|
+
}''')
|
|
33
|
+
|
|
34
|
+
settings.ValidateAndAssignDefaults(default_settings)
|
|
35
|
+
|
|
36
|
+
if len(settings["mapper_settings"].keys()) == 0:
|
|
37
|
+
raise Exception('no "mapper_settings" were specified!')
|
|
38
|
+
|
|
39
|
+
if settings["mapping_settings"].size() == 0:
|
|
40
|
+
raise Exception('no "mapping_settings" were specified!')
|
|
41
|
+
|
|
42
|
+
mdpa_file_name_destination = settings["mdpa_file_name_destination"].GetString()
|
|
43
|
+
|
|
44
|
+
model_part_origin = model.GetModelPart(settings["main_model_part_name_origin"].GetString())
|
|
45
|
+
if model_part_origin.IsSubModelPart():
|
|
46
|
+
raise Exception('Origin ModelPart cannot be a SubModelPart!')
|
|
47
|
+
|
|
48
|
+
self.model_part_destination = model.CreateModelPart(settings["main_model_part_name_destination"].GetString())
|
|
49
|
+
if self.model_part_destination.IsSubModelPart():
|
|
50
|
+
raise Exception('Destination ModelPart cannot be a SubModelPart!')
|
|
51
|
+
|
|
52
|
+
self.model_part_destination.ProcessInfo = model_part_origin.ProcessInfo # for detecting output writing
|
|
53
|
+
|
|
54
|
+
for var in GenerateVariableListFromInput(settings["historical_variables_destination"]):
|
|
55
|
+
self.model_part_destination.AddNodalSolutionStepVariable(var)
|
|
56
|
+
|
|
57
|
+
self.data_comm = model_part_origin.GetCommunicator().GetDataCommunicator()
|
|
58
|
+
|
|
59
|
+
self.destination_rank = settings["destination_rank"].GetInt()
|
|
60
|
+
if self.destination_rank >= self.data_comm.Size():
|
|
61
|
+
raise Exception("Destination rank %i larger than available size %i" %(self.destination_rank, self.data_comm.Size()))
|
|
62
|
+
|
|
63
|
+
# optionally read mdpa (only on one rank)
|
|
64
|
+
if mdpa_file_name_destination != "":
|
|
65
|
+
if self.data_comm.Rank() == self.destination_rank:
|
|
66
|
+
import_flags = KM.ModelPartIO.READ | KM.ModelPartIO.SKIP_TIMER
|
|
67
|
+
KM.ModelPartIO(mdpa_file_name_destination, import_flags).ReadModelPart(self.model_part_destination)
|
|
68
|
+
|
|
69
|
+
# properly initialize in MPI (on other ranks)
|
|
70
|
+
if model_part_origin.IsDistributed():
|
|
71
|
+
import KratosMultiphysics.mpi as KratosMPI
|
|
72
|
+
|
|
73
|
+
# initialize SubModelPartStructure on other ranks
|
|
74
|
+
KratosMPI.DistributedModelPartInitializer(self.model_part_destination, self.data_comm, self.destination_rank).CopySubModelPartStructure()
|
|
75
|
+
|
|
76
|
+
data_comm_destination = KratosMPI.DataCommunicatorFactory.CreateFromRanksAndRegister(
|
|
77
|
+
self.data_comm,
|
|
78
|
+
[self.destination_rank],
|
|
79
|
+
"destination_mapping")
|
|
80
|
+
|
|
81
|
+
if self.data_comm.Rank() != self.destination_rank:
|
|
82
|
+
KratosMPI.ModelPartCommunicatorUtilities.SetMPICommunicatorRecursively(self.model_part_destination, data_comm_destination)
|
|
83
|
+
|
|
84
|
+
# optionally create output process (only on one rank)
|
|
85
|
+
self.output_process = None
|
|
86
|
+
if len(settings["output_process_settings"].keys()) > 0 and self.data_comm.Rank() == self.destination_rank:
|
|
87
|
+
output_proc_params = KM.Parameters('''{ "dummy" : [] }''')
|
|
88
|
+
output_proc_params["dummy"].Append(settings["output_process_settings"])
|
|
89
|
+
self.output_process = KratosProcessFactory(model).ConstructListOfProcesses(output_proc_params["dummy"])[0]
|
|
90
|
+
|
|
91
|
+
# create mapper
|
|
92
|
+
if model_part_origin.IsDistributed():
|
|
93
|
+
from KratosMultiphysics.MappingApplication.MPIExtension import MPIMapperFactory
|
|
94
|
+
|
|
95
|
+
self.mapper = MPIMapperFactory.CreateMapper(
|
|
96
|
+
model_part_origin,
|
|
97
|
+
self.model_part_destination,
|
|
98
|
+
settings["mapper_settings"])
|
|
99
|
+
else:
|
|
100
|
+
self.mapper = KM.MapperFactory.CreateMapper(
|
|
101
|
+
model_part_origin,
|
|
102
|
+
self.model_part_destination,
|
|
103
|
+
settings["mapper_settings"])
|
|
104
|
+
|
|
105
|
+
def ExecuteFinalizeSolutionStep(self):
|
|
106
|
+
defaults = KM.Parameters('''{
|
|
107
|
+
"variable_origin" : "UNSPECIFIED",
|
|
108
|
+
"variable_destination" : "UNSPECIFIED",
|
|
109
|
+
"mapping_options" : []
|
|
110
|
+
}''')
|
|
111
|
+
|
|
112
|
+
for mapping_params in self.settings["mapping_settings"]:
|
|
113
|
+
mapping_params.ValidateAndAssignDefaults(defaults)
|
|
114
|
+
|
|
115
|
+
variable_origin = KM.KratosGlobals.GetVariable(mapping_params["variable_origin"].GetString())
|
|
116
|
+
variable_destination = KM.KratosGlobals.GetVariable(mapping_params["variable_destination"].GetString())
|
|
117
|
+
mapper_flags = GetMapperFlags(mapping_params["mapping_options"])
|
|
118
|
+
|
|
119
|
+
self.mapper.Map(variable_origin, variable_destination, mapper_flags)
|
|
120
|
+
|
|
121
|
+
def IsOutputStep(self):
|
|
122
|
+
is_output_step = False
|
|
123
|
+
if self.data_comm.Rank() == self.destination_rank:
|
|
124
|
+
if self.output_process:
|
|
125
|
+
is_output_step = self.output_process.IsOutputStep()
|
|
126
|
+
return bool(self.data_comm.Broadcast(int(is_output_step), self.destination_rank))
|
|
127
|
+
|
|
128
|
+
def PrintOutput(self):
|
|
129
|
+
if self.data_comm.Rank() == self.destination_rank and self.output_process:
|
|
130
|
+
self.output_process.PrintOutput()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def GetMapperFlags(settings):
|
|
134
|
+
mapper_flags_dict = {
|
|
135
|
+
"add_values" : KM.Mapper.ADD_VALUES,
|
|
136
|
+
"swap_sign" : KM.Mapper.SWAP_SIGN,
|
|
137
|
+
"use_transpose" : KM.Mapper.USE_TRANSPOSE
|
|
138
|
+
}
|
|
139
|
+
mapper_flags = KM.Flags()
|
|
140
|
+
for flag_name in settings.GetStringArray():
|
|
141
|
+
mapper_flags |= mapper_flags_dict[flag_name]
|
|
142
|
+
|
|
143
|
+
return mapper_flags
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: KratosMappingApplication
|
|
3
|
+
Version: 10.4.4
|
|
4
|
+
Summary: KRATOS Multiphysics ("Kratos") is a framework for building parallel, multi-disciplinary simulation software, aiming at modularity, extensibility, and high performance. Kratos is written in C++, and counts with an extensive Python interface.
|
|
5
|
+
Author-email: Kratos Team <kratos@listas.cimne.upc.edu>
|
|
6
|
+
License: BSD-4-Clause
|
|
7
|
+
Classifier: Programming Language :: C++
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Requires-Dist: kratosmultiphysics==10.4.4
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
## Mapping Application
|
|
15
|
+
|
|
16
|
+
The Mapping Application contains the core developments in mapping data between non matching grids. It works both in shared and distributed (**MPI**) memory environments as well as in 1D, 2D and 3D domains.
|
|
17
|
+
|
|
18
|
+
### Overview
|
|
19
|
+
|
|
20
|
+
- [List of features](#list-of-features)
|
|
21
|
+
- [Dependencies](#dependencies)
|
|
22
|
+
- [Mapping in CoSimulation](#Mapping-in-CoSimulation)
|
|
23
|
+
- [Basic Usage](#basic-usage)
|
|
24
|
+
- [Advanced Usage](#advanced-usage)
|
|
25
|
+
- [Available Mappers](#available-mappers)
|
|
26
|
+
- [When to use which Mapper?](#when-to-use-which-mapper)
|
|
27
|
+
- [Using the Mapper for ModelParts that are not part of all ranks](#using-the-mapper-for-modelparts-that-are-not-part-of-all-ranks)
|
|
28
|
+
- [Miscellaneous functionalities](#miscellaneous-functionalities)
|
|
29
|
+
- [FAQ](#faq)
|
|
30
|
+
|
|
31
|
+
### List of features
|
|
32
|
+
|
|
33
|
+
- Parallelism:
|
|
34
|
+
- Serial (no parallelism)
|
|
35
|
+
- Shared memory (OpenMP)
|
|
36
|
+
- Distributed memory (MPI)
|
|
37
|
+
- Domain sizes: 1D / 2D / 3D
|
|
38
|
+
- Matching and non matching grids
|
|
39
|
+
- Different mapping technologies (see [here](#available-mappers)):
|
|
40
|
+
- Nearest Neighbor
|
|
41
|
+
- Nearest Element
|
|
42
|
+
- Barycentric
|
|
43
|
+
- Radial Basis Function Mapper
|
|
44
|
+
- Beam Mapper
|
|
45
|
+
- Metamappers
|
|
46
|
+
- 3D/2D metamapper (metamapper which obtains the solution for the 3D destination model part from the original 2D solution)
|
|
47
|
+
- Mapping operations (see [here](#customizing-the-behavior-of-the-mapping-with-flags))
|
|
48
|
+
|
|
49
|
+
### Dependencies
|
|
50
|
+
|
|
51
|
+
The serial / shared memory parallel compilation of the Mapping Application doesn't have any dependencies (except the `KratosCore`).
|
|
52
|
+
|
|
53
|
+
The distributed compilation of the Mapping Application depends on the [Trilinos library](https://trilinos.github.io/). Also most of the MPI-solvers in Kratos depend on Trilinos, see the [Trilinos Application](../TrilinosApplication).
|
|
54
|
+
|
|
55
|
+
### Mapping in CoSimulation
|
|
56
|
+
|
|
57
|
+
The Mapping Application can be used for mapping within the [CoSimulation Application](../CoSimulationApplication). This can be done by using the [KratosMappingDataTransferOperator](../CoSimulationApplication/python_scripts/data_transfer_operators/kratos_mapping.py).
|
|
58
|
+
|
|
59
|
+
### Basic Usage
|
|
60
|
+
|
|
61
|
+
The _Mapper_ maps nodal data from one _ModelPart_ to another. This means that the input for the _Mapper_ is two _ModelParts_, the **Origin** and the **Destination**. Furthermore settings in the form of _Kratos::Parameters_ are passed.
|
|
62
|
+
|
|
63
|
+
The _Mapper_ is constructed using the _MapperFactory_. See the following basic example.
|
|
64
|
+
|
|
65
|
+
```py
|
|
66
|
+
# import the Kratos Core
|
|
67
|
+
import KratosMultiphysics as KM
|
|
68
|
+
# import the MappingApplication to load the mappers
|
|
69
|
+
import KratosMultiphysics.MappingApplication as KratosMapping
|
|
70
|
+
|
|
71
|
+
# create ModelParts
|
|
72
|
+
# ...
|
|
73
|
+
|
|
74
|
+
mapper_settings = KM.Parameters("""{
|
|
75
|
+
"mapper_type": "nearest_neighbor",
|
|
76
|
+
"echo_level" : 0
|
|
77
|
+
}""")
|
|
78
|
+
|
|
79
|
+
# creating a mapper for shared memory
|
|
80
|
+
mapper = KM.MapperFactory.CreateMapper(
|
|
81
|
+
model_part_origin,
|
|
82
|
+
model_part_destination,
|
|
83
|
+
mapper_settings
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
For constructing an _MPI-Mapper_ use the `MPIExtension` instead:
|
|
88
|
+
|
|
89
|
+
```py
|
|
90
|
+
# creating a mapper for distributed memory
|
|
91
|
+
from KratosMultiphysics.MappingApplication import MPIExtension as MappingMPIExtension
|
|
92
|
+
mpi_mapper = MappingMPIExtension.MPIMapperFactory.CreateMapper(
|
|
93
|
+
model_part_origin,
|
|
94
|
+
model_part_destination,
|
|
95
|
+
mapper_settings
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
After constructing the _Mapper_ / _MPI-Mapper_ it can be used immediately to map any scalar and vector quantities, no further initialization is necessary.\
|
|
100
|
+
The **Map** function is used to map values from the **Origin** to the **Destination**. For this the _Variables_ have to be specified. See the following example for mapping scalar quantities.
|
|
101
|
+
|
|
102
|
+
```py
|
|
103
|
+
# mapping scalar quantities
|
|
104
|
+
# this maps the nodal quantities of TEMPERATURE on the origin-ModelPart
|
|
105
|
+
# to the nodal quantities of AMBIENT_TEMPERATURE on the destination-ModelPart
|
|
106
|
+
|
|
107
|
+
mapper.Map(KM.TEMPERATURE, KM.AMBIENT_TEMPERATURE)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The **Map** function is overloaded, this means that mapping vector quantities works in the same way as mapping scalar quantities.
|
|
111
|
+
|
|
112
|
+
```py
|
|
113
|
+
# mapping vector quantities
|
|
114
|
+
# this maps the nodal quantities of VELOCITY on the origin-ModelPart
|
|
115
|
+
# to the nodal quantities of MESH_VELOCITY on the destination-ModelPart.
|
|
116
|
+
|
|
117
|
+
mapper.Map(KM.VELOCITY, KM.MESH_VELOCITY)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Mapping from **Destination** to **Origin** can be done using the **InverseMap** function which works in the same way as the **Map** function.
|
|
121
|
+
|
|
122
|
+
```py
|
|
123
|
+
# inverse mapping scalar quantities
|
|
124
|
+
# this maps the nodal quantities of AMBIENT_TEMPERATURE on the destination-ModelPart
|
|
125
|
+
# to the nodal quantities of TEMPERATURE on the origin-ModelPart
|
|
126
|
+
|
|
127
|
+
mapper.InverseMap(KM.TEMPERATURE, KM.AMBIENT_TEMPERATURE)
|
|
128
|
+
|
|
129
|
+
# inverse mapping vector quantities
|
|
130
|
+
# this maps the nodal quantities of MESH_VELOCITY on the destination-ModelPart
|
|
131
|
+
# to the nodal quantities of VELOCITY on the origin-ModelPart
|
|
132
|
+
|
|
133
|
+
mapper.InverseMap(KM.VELOCITY, KM.MESH_VELOCITY)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
For the 3D/2D metamapper the settings to consider are the following, where `base_mapper` is the backend mapper to be considered.
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
mapper_params = KM.Parameters("""{
|
|
140
|
+
"mapper_type" : "projection_3D_2D",
|
|
141
|
+
"base_mapper" : "nearest_neighbor",
|
|
142
|
+
"search_settings" : {},
|
|
143
|
+
"echo_level" : 0
|
|
144
|
+
}""")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Advanced Usage
|
|
148
|
+
|
|
149
|
+
The previous section introduced the basics of using the _MappingApplication_. The more advanced usage is explained in this section.
|
|
150
|
+
|
|
151
|
+
#### Customizing the behavior of the mapping with Flags
|
|
152
|
+
|
|
153
|
+
By default the mapping functions **Map** and **InverseMap** will overwrite the values where they map to. In order to add instead of overwrite the values the behavior can be customized by using _Kratos::Flags_. Consider in the following example that several forces are acting on a surface. Overwriting the values would cancel the previously applied forces.
|
|
154
|
+
|
|
155
|
+
```py
|
|
156
|
+
# Instead of overwriting, this will add the values to the existing ones
|
|
157
|
+
|
|
158
|
+
mapper.Map(KM.REACTION, KM.FORCE, KM.Mapper.ADD_VALUES)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Sometimes it can be necessary to swap the signs of quantities that are to be mapped. This can be done with the following:
|
|
162
|
+
|
|
163
|
+
```py
|
|
164
|
+
# Swapping the sign, i.e. multiplying the values with (-1)
|
|
165
|
+
|
|
166
|
+
mapper.Map(KM.DISPLACEMENT, KM.MESH_DISPLACEMENT, KM.Mapper.SWAP_SIGN)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The flags can also be combined:
|
|
170
|
+
|
|
171
|
+
```py
|
|
172
|
+
mapper.Map(KM.REACTION, KM.FORCE, KM.Mapper.ADD_VALUES | KM.Mapper.SWAP_SIGN)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Historical nodal values are used by default. Mapping to an from nonhistorical nodal values is also supported, the following examples show the usage:
|
|
176
|
+
|
|
177
|
+
This maps the values from the origin (`REACTION`) as historical values to the destination (`FORCE`) as nonhistorical values:
|
|
178
|
+
|
|
179
|
+
```py
|
|
180
|
+
mapper.Map(KM.REACTION, KM.FORCE, KM.Mapper.TO_NON_HISTORICAL)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
This maps the values from the origin (`REACTION`) as nonhistorical values to the destination (`FORCE`) as historical values:
|
|
184
|
+
|
|
185
|
+
```py
|
|
186
|
+
mapper.Map(KM.REACTION, KM.FORCE, KM.Mapper.FROM_NON_HISTORICAL)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
This maps the values from the destination (`FORCE`) as historical values to the origin (`REACTION`) as nonhistorical values:
|
|
190
|
+
|
|
191
|
+
```py
|
|
192
|
+
mapper.InverseMap(KM.REACTION, KM.FORCE, KM.Mapper.TO_NON_HISTORICAL)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
This maps the values from the destination (`FORCE`) as nonhistorical values to the origin (`REACTION`) as historical values:
|
|
196
|
+
|
|
197
|
+
```py
|
|
198
|
+
mapper.InverseMap(KM.REACTION, KM.FORCE, KM.Mapper.FROM_NON_HISTORICAL)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Of course it is possible to use both origin and destination nonhistorical. This maps the values from the origin (`REACTION`) as nonhistorical values to the destination (`FORCE`) as nonhistorical values:
|
|
202
|
+
|
|
203
|
+
```py
|
|
204
|
+
mapper.Map(KM.REACTION, KM.FORCE, KM.Mapper.FROM_NON_HISTORICAL | KM.Mapper.TO_NON_HISTORICAL)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Many _Mappers_ internally construct a mapping matrix. It is possible to use the transpose of this matrix for mapping with `USE_TRANSPOSE`. This is often used for conservative mapping of forces in FSI, when the virtual work on both interfaces should be preserved.
|
|
208
|
+
|
|
209
|
+
```py
|
|
210
|
+
mapper.Map(KM.REACTION, KM.FORCE, KM.Mapper.USE_TRANSPOSE)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Updating the Interface
|
|
214
|
+
|
|
215
|
+
In case of moving interfaces (e.g. in a problem involving Contact between bodies) it can become necessary to update the _Mapper_ to take the new geometrical positions of the interfaces into account.\
|
|
216
|
+
One way of doing this would be to construct a new _Mapper_, but this is not efficient and sometimes not even possible.
|
|
217
|
+
|
|
218
|
+
Hence the _Mapper_ provides the **UpdateInterface** function for updating itseld with respect to the new geometrical positions of the interfaces.\
|
|
219
|
+
Note that this is potentially an expensive operation due to searching the new geometrical neighbors on the interface.
|
|
220
|
+
|
|
221
|
+
```py
|
|
222
|
+
mapper.UpdateInterface()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### Checking which mappers are available
|
|
226
|
+
|
|
227
|
+
The following can be used to see which _Mappers_ are available:
|
|
228
|
+
|
|
229
|
+
```py
|
|
230
|
+
# available mappers for shared memory
|
|
231
|
+
KM.MapperFactory.GetRegisteredMapperNames()
|
|
232
|
+
|
|
233
|
+
# available mappers for distributed memory
|
|
234
|
+
MappingMPIExtension.MPIMapperFactory.GetRegisteredMapperNames()
|
|
235
|
+
|
|
236
|
+
# check if mapper for shared memory exists
|
|
237
|
+
KM.MapperFactory.HasMapper("mapper_name")
|
|
238
|
+
|
|
239
|
+
# check if mapper for distributed memory exists
|
|
240
|
+
MappingMPIExtension.MPIMapperFactory.HasMapper("mapper_name")
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### Search settings
|
|
244
|
+
The search of neighbors / partners on the other side of the interface is a crucial task when creating the mapper. Especially in distributed computations (MPI) this can be very expensive and time consuming. Hence the search of the mapper is very optimized to provide robust and fast results. For this the search works in several iterations where the search radius is increased in each iteration.
|
|
245
|
+
The default settings of the search are working fine in most cases, but in some special cases it might still be necessary to tweak and optimize the settings. The following settings are available (as sub-parameter `search_settings` of the settings that are given to the mapper):
|
|
246
|
+
|
|
247
|
+
| name | type | default| description |
|
|
248
|
+
|---|---|---|---|
|
|
249
|
+
| `search_radius`| `double` | computed | The search radius to start with in the first iteration. In each next iteration it will be increased by multiplying with `search_radius_increase_factor` (`search_radius *= search_radius_increase_factor`) |
|
|
250
|
+
| `max_search_radius` | `double` | computed | The max search radius to use. |
|
|
251
|
+
| `search_radius_increase_factor`| `double` | `2.0` | factor by which the search radius is increasing in each search iteration (see above). **Tuning this parameter is usually the best way to achieve a faster search**. In many cases decreasing it will speed up the search, especially for volumetric mapping, but it is case dependent. |
|
|
252
|
+
| `max_num_search_iterations` | `int` | computed (min 3) | max number of search iterations that is conducted. If the search is successful before then it will terminate earlier. The more heterogeneous the mesh the larger this will be.
|
|
253
|
+
|
|
254
|
+
It is recommended to set the `echo_level` to 2 or higher for getting useful information from the search. This will help to debug the search in case of problems.
|
|
255
|
+
|
|
256
|
+
### Available Mappers
|
|
257
|
+
|
|
258
|
+
This section explains the theory behind the mappers.
|
|
259
|
+
|
|
260
|
+
#### Nearest Neighbor
|
|
261
|
+
|
|
262
|
+
The _NearestNeighborMapper_ is a very simple/basic _Mapper_. Searches its closest neighbor (node) on the other interface. During mapping it gets/sets its value to the value of its closest neighbor.
|
|
263
|
+
|
|
264
|
+
This mapper is best suited for problems where both interfaces have a similar discretization. Furthermore it is very robust and can be used for setting up problems when one does not (yet) want to deal with mapping.
|
|
265
|
+
|
|
266
|
+
Internally it constructs the mapping matrix, hence it offers the usage of the transposed mapping matrix. When using this, for very inhomogeneous interface discretizations it can come to oscillations in the mapped quantities.
|
|
267
|
+
|
|
268
|
+
**Supported mesh topologies**: This mapper only works with nodes and hence supports any mesh topology
|
|
269
|
+
|
|
270
|
+
#### Nearest Neighbor for IGA scenarios
|
|
271
|
+
|
|
272
|
+
The _NearestNeighborMapperIGA_ is a simple and robust Mapper for IGA/FEM partitioned simulations. For each node on the FEM side, it finds the closest integration point on the IGA interface.
|
|
273
|
+
During mapping, it evaluates the IGA shape functions at that location to assemble the mapping matrix.
|
|
274
|
+
|
|
275
|
+
This mapper is suited for cases where the origin domain is discretized with IGA elements and the destination with any node-based discretization technique (e.g., FEM or FCV).
|
|
276
|
+
|
|
277
|
+
Internally, it constructs the mapping matrix, which also allows the use of its transpose for conservative mapping (e.g., mapping forces from FEM to IGA). In cases of highly inhomogeneous interface discretizations, using the transpose may introduce oscillations in the mapped values.
|
|
278
|
+
|
|
279
|
+
**Supported mesh topologies**: This mapper operates on nodes and supports any mesh topology. The only requirement is that the origin domain must be the IGA domain; otherwise, the mapping problem is not well defined.
|
|
280
|
+
|
|
281
|
+
#### Nearest Element
|
|
282
|
+
|
|
283
|
+
The _NearestElementMapper_ projects nodes to the elements( or conditions) on other side of the interface. Mapping is then done by interpolating the values of the nodes of the elements by using the shape functions at the projected position. The NearestElementMapper supports IGA/FEM partitioned simulations where the origin must be the IGA domain. Each FEM node is projected onto the IGA surface or its boundary curves, and the shape functions are evaluated at that point to assemble the mapping matrix.
|
|
284
|
+
|
|
285
|
+
This mapper is best suited for problems where the _NearestNeighborMapper_ cannot be used, i.e. for cases where the discretization on the interfaces is different. Note that it is less robust than the _NearestNeighborMapper_ due to the projections it performs. In case a projection fails, it uses an approximation that is similar to the approach of the _NearestNeighborMapper_. This can be disabled by setting `use_approximation` to `false` in the mapper-settings.
|
|
286
|
+
|
|
287
|
+
Internally it constructs the mapping matrix, hence it offers the usage of the transposed mapping matrix. When using this, for very inhomogeneous interface discretizations it can come to oscillations in the mapped quantities.
|
|
288
|
+
|
|
289
|
+
**Supported mesh topologies**: Any mesh topology available in Kratos, which includes the most common linear and quadratic geometries, see [here](../../kratos/geometries).
|
|
290
|
+
|
|
291
|
+
#### Barycentric
|
|
292
|
+
|
|
293
|
+
The _BarycentricMapper_ uses the closest nodes to reconstructs a geometry. This geometry is used in the same way as the _NearestElementMapper_ for interpolating the values of the nodes using the shape functions.
|
|
294
|
+
|
|
295
|
+
This mapper can be used when no geometries are available and interpolative properties of the mapper are required. E.g. for particle methods when only nodes or point-based entities are available. Overall it can be seen as combining the advantages of the _NearestNeighborMapper_ (which only requires points as input) with the advantages of the _NearestElementMapper_ (which has interpolative properties). The disadvantage is that the reconstruction of the geometry can cause problems in complex situations, hence it should only be used if the _NearestElementMapper_ cannot be used.
|
|
296
|
+
|
|
297
|
+
Furthermore, the geometry type for the reconstruction/interpolation has to be chosen with the `interpolation_type` setting. The following types are available: `line`, `triangle` and `tetrahedra`
|
|
298
|
+
|
|
299
|
+
Internally it constructs the mapping matrix, hence it offers the usage of the transposed mapping matrix. When using this, for very inhomogeneous interface discretizations it can come to oscillations in the mapped quantities.
|
|
300
|
+
|
|
301
|
+
**Supported mesh topologies**: This mapper only works with nodes and hence supports any mesh topology
|
|
302
|
+
|
|
303
|
+
#### Radial Basis Function (RBF) Mapper
|
|
304
|
+
The _RadialBasisFunctionMapper_ is a global, mesh-independent mapper that constructs a smooth interpolation field based on Radial Basis Functions (RBFs). In contrast to purely local methods, this mapper uses all (or a user-defined subset of) points from the origin interface to build an RBF system that is then evaluated at the destination points.
|
|
305
|
+
|
|
306
|
+
This allows for smooth, high-quality transfer of field quantities between arbitrarily discretized, non-matching, or strongly non-uniform interfaces. It is therefore particularly suitable for multi-physics problems where interface meshes can differ substantially.
|
|
307
|
+
|
|
308
|
+
The default configuration looks as follows:
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
"mapper_settings" : {
|
|
312
|
+
"echo_level" : 0,
|
|
313
|
+
"radial_basis_function_type" : "thin_plate_spline",
|
|
314
|
+
"additional_polynomial_degree" : 0,
|
|
315
|
+
"origin_is_iga" : false,
|
|
316
|
+
"destination_is_iga" : false,
|
|
317
|
+
"max_support_points" : 0,
|
|
318
|
+
"use_all_rbf_support_points": true,
|
|
319
|
+
"precompute_mapping_matrix" : true,
|
|
320
|
+
"search_settings" : {},
|
|
321
|
+
"linear_solver_settings" : {}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Important notes:**
|
|
326
|
+
- Only the origin domain can be IGA.
|
|
327
|
+
The mapper can extract coordinates from IGA gauss points on the origin side (```json"origin_is_iga": true```). The destination side must currently be a standard finite element mesh (nodes as mapping coordinates).
|
|
328
|
+
-Global RBF System
|
|
329
|
+
Internally, the mapper assembles and solves a global RBF interpolation system.
|
|
330
|
+
If ```json"precompute_mapping_matrix": true```, the resulting mapping matrix is stored and reused, allowing efficient repeated mapping calls.
|
|
331
|
+
- Support Points
|
|
332
|
+
- ```json"use_all_rbf_support_points": true``` → all origin points contribute to the RBF system.
|
|
333
|
+
- ```json"max_support_points" > 0``` → restricts support to a local neighborhood for each destination point.
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
#### Beam Mapper
|
|
337
|
+
The _BeamMapper_ provides support for mapping between 1D beam elements and 2D/3D surface meshes. It follows the formulation of Wang (2019) and is intended for cases where beam DOFs (displacements and rotations) must be transferred consistently to a surrounding surface, e.g. in FSI or beam–solid coupling.
|
|
338
|
+
|
|
339
|
+
The mapper projects each surface node onto the undeformed beam centerline and assigns a local rigid cross section. The motion of every projected surface point is obtained through rigid body motion of this cross section, using the beam’s axial and rotational DOFs. Hermitian shape functions are used along the beam to interpolate both displacements and rotations.
|
|
340
|
+
|
|
341
|
+
A typical configuration json looks as follows:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
"mapper" : {
|
|
345
|
+
"type" : "kratos_beam_mapping",
|
|
346
|
+
"model_part_name_beam" : "Structure.Parts_Beam_beam",
|
|
347
|
+
"model_part_name_surface" : "Structure.Parts_Shell_wet_surface",
|
|
348
|
+
"solver_name_beam": "beam_structure",
|
|
349
|
+
"solver_name_surface" : "dummy_fluid",
|
|
350
|
+
"echo_level": 3,
|
|
351
|
+
"mapper_settings" : {
|
|
352
|
+
"mapper_type" : "beam_mapper",
|
|
353
|
+
"use_corotation" : false,
|
|
354
|
+
"search_settings": {
|
|
355
|
+
"max_num_search_iterations" : 30,
|
|
356
|
+
"search_radius": 3.0
|
|
357
|
+
},
|
|
358
|
+
"echo_level": 0
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Explanation of the main entries:**
|
|
364
|
+
- `"type"`: selects the CoSimulation mapper. "kratos_beam_mapping" activates the _BeamMapper_.
|
|
365
|
+
- `"model_part_name_beam" / "model_part_name_surface"`: names of the origin beam model part and the target surface model part used for the mapping.
|
|
366
|
+
- `"solver_name_beam" / "solver_name_surface"`: identifiers of the solvers that own these model parts. They are used internally by the CoSimulation framework to retrieve nodal values.
|
|
367
|
+
- `"echo_level"`: controls the amount of printed information for debugging.
|
|
368
|
+
- `"mapper_settings"`
|
|
369
|
+
- `"mapper_type"`: must be "beam_mapper" to use the _BeamMapper_.
|
|
370
|
+
- `"use_corotation"`: enables the co-rotational formulation (false → linear mapping, true → large-rotation mapping)
|
|
371
|
+
- `"search_settings"`: parameters controlling the projection of surface nodes onto the beam centerline.
|
|
372
|
+
|
|
373
|
+
**Note:**
|
|
374
|
+
This mapper currently supports FEM beam elements only. IGA beams are not yet supported.
|
|
375
|
+
|
|
376
|
+
### When to use which Mapper?
|
|
377
|
+
|
|
378
|
+
- **Matching Interface**\
|
|
379
|
+
For a matching interface the _NearestNeighborMapper_ is the best / fastes choice. Note that the ordering / numbering of the nodes doesn't matter.
|
|
380
|
+
|
|
381
|
+
- **Interfaces with almost matching discretizations**\
|
|
382
|
+
In this case both the _NearestNeighborMapper_ and the _NearestElementMapper_ can yield good results.
|
|
383
|
+
|
|
384
|
+
- **Interfaces with non matching discretizations**\
|
|
385
|
+
The _NearestElementMapper_ is recommended because it results in smoother mapping results due to the interpolation using the shape functions.
|
|
386
|
+
|
|
387
|
+
- **Interfaces with non matching discretizations when no geometries are available for interpolation**\
|
|
388
|
+
The _NearestElementMapper_ cannot be used as it requires geometries for the ionterpolation. Here the _BarycentricMapper_ is recommended because it reconstructs geometries from the surrounding nodes and then uses it to interpolate.
|
|
389
|
+
|
|
390
|
+
### Using the Mapper for ModelParts that are not part of all ranks
|
|
391
|
+
|
|
392
|
+
In MPI parallel simulations usually all `ModelParts` are distributed across all ranks. However in some cases this does not hold, for example in FSI when the fluid runs on all ranks but the structure runs serial on one rank. In this case it is necessary to do the following:
|
|
393
|
+
|
|
394
|
+
- Create a dummy-`ModelPart` on the ranks that do not have the original ModelPart.
|
|
395
|
+
- **IMPORTANT**: This `ModelPart` must have a `DataCommunicator` that is not defined on the ranks that are not part of the original `ModelPart`.
|
|
396
|
+
- Create and MPI-mapper as explained [above](#basic-usage), using the original and the dummy `ModelPart`s on the respective ranks.
|
|
397
|
+
|
|
398
|
+
Check [this test](https://github.com/KratosMultiphysics/Kratos/blob/master/applications/MappingApplication/tests/blade_mapping_test.py) for more details and usage example.
|
|
399
|
+
|
|
400
|
+
For an example the following assumptions are made:
|
|
401
|
+
|
|
402
|
+
- Overall 4 MPI processes are used
|
|
403
|
+
- `model_part_fluid` is distributed across all 4 ranks
|
|
404
|
+
- `model_part_structure` is not distributed and exists only on rank 0
|
|
405
|
+
|
|
406
|
+
```py
|
|
407
|
+
import KratosMultiphysics as KM
|
|
408
|
+
import KratosMultiphysics.mpi as KratosMPI
|
|
409
|
+
|
|
410
|
+
# "model_part_fluid" was already read and exists on all ranks
|
|
411
|
+
# "model_part_structure" was already read and exists only on rank 0
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# getting the DataCommunicator that wraps `MPI_COMM_WORLD` i.e. contains all ranks
|
|
415
|
+
world_data_comm = KM.ParallelEnvironment.GetDataCommunicator("World)
|
|
416
|
+
|
|
417
|
+
# define the ranks on which the structure ModelPart exists
|
|
418
|
+
# structure can also be distributed across several (but not all) ranks
|
|
419
|
+
structure_ranks = [0]
|
|
420
|
+
|
|
421
|
+
# create a DataCommunicator containing only the structure ranks
|
|
422
|
+
structure_ranks_data_comm_name = "structure_ranks"
|
|
423
|
+
data_comm_all_structure_ranks = KratosMPI.DataCommunicatorFactory.CreateFromRanksAndRegister(
|
|
424
|
+
world_data_comm,
|
|
425
|
+
structure_ranks,
|
|
426
|
+
structure_ranks_data_comm_name)
|
|
427
|
+
|
|
428
|
+
# create a dummy ModelPart on the ranks where the original ModelPart does not exist
|
|
429
|
+
if world_data_comm.Rank() not in structure_ranks:
|
|
430
|
+
dummy_model = KM.Model()
|
|
431
|
+
model_part_structure = dummy_model.CreateModelPart("structure_dummy")
|
|
432
|
+
|
|
433
|
+
# Important: set the DataCommunicator so that the Mapper knows on which ranks the ModelPart is only a dummy
|
|
434
|
+
KratosMPI.ModelPartCommunicatorUtilities.SetMPICommunicator(model_part_structure, data_comm_all_structure_ranks)
|
|
435
|
+
|
|
436
|
+
# now the Mapper can be created with the original and the dummy ModelParts
|
|
437
|
+
mpi_mapper = MappingMPIExtension.MPIMapperFactory.CreateMapper(
|
|
438
|
+
model_part_fluid,
|
|
439
|
+
model_part_structure,
|
|
440
|
+
mapper_settings
|
|
441
|
+
)
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Miscellaneous functionalities
|
|
445
|
+
- [serial_output_process](https://github.com/KratosMultiphysics/Kratos/blob/master/applications/MappingApplication/python_scripts/serial_output_process.py): This process can be used to map results to one rank and then do postprocessing on this rank. This has two advantages:
|
|
446
|
+
- Some output formats write one file per rank in distributed simulations, which leads to many files when running with many cores. This process collects the results on one rank and can hence reduce the number of files significantly
|
|
447
|
+
- Different meshes can be used to do the postprocessing. This is in particular useful when the computational mesh is very fine, but a coarser mesh would be sufficient for postprocessing.
|
|
448
|
+
|
|
449
|
+
<ins>The following input parameters are used:</ins>
|
|
450
|
+
- `model_part_name_origin`: name of the origin ModelPart where the data comes from (is being mapped from)
|
|
451
|
+
- `model_part_name_destination`: name of destination ModelPart where the data is mapped to. This ModelPart is being read.
|
|
452
|
+
- `mdpa_file_name_destination`: name of the mdpa file containing the mesh that is used for the destination
|
|
453
|
+
- `historical_variables_destination` list of historical variables that are allocated on the destination ModelPart
|
|
454
|
+
- `destination_rank` rank on which the processing of the destination happens (i.e. the rank on which the destination ModelPart is read). Note that this increases the memory usage significantly, especially for large destination meshes. The default is rank 0, which in most distributed simulations acts as the master rank with already increased computational effort. Hence it can make sense to use another rank, preferably on another compute node, to optimize the memory and computational load balance
|
|
455
|
+
- `mapper_settings`: setting that are passed to the mapper, as explained above
|
|
456
|
+
- `mapping_settings`: list of mapping steps to be executed before the postprocessing is done. `variable_origin` and `variable_destination` must be specified, while `mapping_options` is optional and can contain the flags as explained above.
|
|
457
|
+
- `output_process_settings`: The settings for the output process (which will be only executed on the destination rank). **Important**: For mapping onto a serial ModelPart, the DataCommunicator is set as explained [here](#using-the-mapper-for-modelparts-that-are-not-part-of-all-ranks). This means that the destination ModelPart is not valid on other ranks and can hence not be used in the regular postprocessing (which happens also on the ranks where it is not valid and hence some MPI-functionalities would fail)
|
|
458
|
+
Example input:
|
|
459
|
+
~~~js
|
|
460
|
+
"python_module" : "serial_output_process",
|
|
461
|
+
"kratos_module" : "KratosMultiphysics.MappingApplication",
|
|
462
|
+
"Parameters" : {
|
|
463
|
+
"model_part_name_origin" : "FluidModelPart",
|
|
464
|
+
"model_part_name_destination" : "PostProcessing",
|
|
465
|
+
"mdpa_file_name_destination" : "coarse_mesh",
|
|
466
|
+
"historical_variables_destination" : ["REACTION", "DISPLACEMENT"],
|
|
467
|
+
"mapper_settings" : {"mapper_type" : "nearest_neighbor"},
|
|
468
|
+
"mapping_settings" : [{
|
|
469
|
+
"variable_origin" : "REACTION",
|
|
470
|
+
"variable_destination" : "REACTION"
|
|
471
|
+
},{
|
|
472
|
+
"variable_origin" : "REACTION",
|
|
473
|
+
"variable_destination" : "REACTION",
|
|
474
|
+
"mapping_options" : ["add_values"]
|
|
475
|
+
},{
|
|
476
|
+
"variable_origin" : "MESH_DISPLACEMENT",
|
|
477
|
+
"variable_destination" : "DISPLACEMENT"
|
|
478
|
+
}],
|
|
479
|
+
"output_process_settings" : {
|
|
480
|
+
"python_module" : "vtk_output_process",
|
|
481
|
+
"kratos_module" : "KratosMultiphysics",
|
|
482
|
+
"Parameters" : {
|
|
483
|
+
// ...
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
~~~
|
|
488
|
+
|
|
489
|
+
### FAQ
|
|
490
|
+
|
|
491
|
+
- **Is mapping of elemental / conditional data or gauss-point values possible?**\
|
|
492
|
+
The mapper only supports mapping of nodal data. In order to map other quantities, those have to first be inter- / extrapolated to the nodes.
|
|
493
|
+
|
|
494
|
+
- **Something is not working with the mapping. What should I do?**\
|
|
495
|
+
Problems with mapping can have many sources. The first thing in debugging what is happening is to increase the `echo_level` of the _Mapper_. Then in many times warnings are shown in case of some problems.
|
|
496
|
+
|
|
497
|
+
- **I get oscillatory solutions when mapping with `USE_TRANSPOSE`**\
|
|
498
|
+
Research has shown that "simple" mappers like _NearestNeighbor_ and _NearestElement_ can have problems with mapping with the transpose (i.e. when using `USE_TRANSPOSE`) if the meshes are very different. Using the _MortarMapper_ technology can improve this situation. This _Mapper_ is currently under development.
|
|
499
|
+
|
|
500
|
+
- **Projections find the wrong result**\
|
|
501
|
+
For complex geometries the projections can fail to find the correct result if many lines or surfaces are close. In those situations it helps to partition the mapping interface and construct multiple mappers with the smaller interfaces.
|
|
502
|
+
|
|
503
|
+
- **Creation of the mapper takes very long**\
|
|
504
|
+
Often this is because of of unfit search settings. If the settings are not suitable for the problem then the mapper creation time can increase several magnitudes! Check [here](#search-settings) for an explanation of how to set the search settings in case the defaults are not working well.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
kratosmappingapplication-10.4.4.dist-info/RECORD,,
|
|
2
|
+
kratosmappingapplication-10.4.4.dist-info/WHEEL,sha256=QX0UaoWXQIAv8vp5R3do793urekDoJdJRoGCk8nXIBY,115
|
|
3
|
+
kratosmappingapplication-10.4.4.dist-info/METADATA,sha256=QIpkbWKP8r8lrIgkUT5rvcqodOnJF5wBoqPilwGjvbw,28794
|
|
4
|
+
KratosMultiphysics/MappingApplication/python_mapper.py,sha256=awCUAMwotL7waBTJsFfIfQh63iYlzH_vJxzsqKUg3eA,2070
|
|
5
|
+
KratosMultiphysics/MappingApplication/empire_mortar_mapper.py,sha256=s8uwyBLCfEPwYBWbaIi1IJsegE-erstByXUxH_HH9m4,15126
|
|
6
|
+
KratosMultiphysics/MappingApplication/__init__.py,sha256=7c09Lo9AkwNDNLhDYe-EKwtPQRsB_kaodgVUj6_vJlw,2546
|
|
7
|
+
KratosMultiphysics/MappingApplication/python_mapper_factory.py,sha256=i595t-6Zb-dGlV9vvUY-0f7mlvxa67HqTQMt-HsdRMQ,1959
|
|
8
|
+
KratosMultiphysics/MappingApplication/serial_output_process.py,sha256=xqkvP51m8rfHZ1IJcsPoM5f3y3vlr7AKV2WmaRCzMcA,6713
|
|
9
|
+
KratosMultiphysics/.libs/libKratosMappingCore.dylib,sha256=GOu3ba_gwMz3ZCX60bxgaUZgnrg_HJ91yQ249HBVga4,5670832
|
|
10
|
+
KratosMultiphysics/.libs/KratosMappingApplication.so,sha256=SoKlKIRlNXLbM9glBgvoYcCmUVzKMKTXGTYAouFG0Tg,1616368
|
|
11
|
+
kratosmappingapplication.dylibs/libstdc++.6.dylib,sha256=Eli3BatHs3QkQsDxgMG3KgVmdo2d749UsXWOTiVCd48,3403264
|
|
12
|
+
kratosmappingapplication.dylibs/libKratosCore.dylib,sha256=-JSH8gscjlOg5V7qrVkyrSxhyGiYmOueaiYYy_y_TCo,63629792
|
|
13
|
+
kratosmappingapplication.dylibs/libgcc_s.1.1.dylib,sha256=GQDTek66XPaqkYCKOaB4qPc6g7Yvl4sPpHg2OS3X9Q0,219232
|
|
14
|
+
kratosmappingapplication.dylibs/libgomp.1.dylib,sha256=VqkEHBq6qgnM3LYm2gfdKKYbpyWU2r7Dv4lPiDS-lko,438880
|
|
Binary file
|
|
Binary file
|
|
Binary file
|