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.
@@ -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
@@ -0,0 +1,6 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+ Generator: delocate 0.13.0
6
+
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: kratosmappingapplication.dylibs/libKratosCore.dylib
3
+ size: 63629792 bytes
4
+ sha256: f89487f20b1c8e53a0e55eeaad5932ad2c61c8689898eb9e6a2618cbfcbf4c2a