Coverage for src/meshpy/core/mesh_utils.py: 92%
25 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-28 04:21 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-28 04:21 +0000
1# The MIT License (MIT)
2#
3# Copyright (c) 2018-2025 MeshPy Authors
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to deal
7# in the Software without restriction, including without limitation the rights
8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9# copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in
13# all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21# THE SOFTWARE.
22"""This module defines utility functions for meshes."""
24from typing import Dict as _Dict
25from typing import List as _List
26from typing import Tuple as _Tuple
28from meshpy.core.conf import mpy as _mpy
29from meshpy.core.mesh import Mesh as _Mesh
30from meshpy.core.node import Node as _Node
33def get_coupled_nodes_to_master_map(
34 mesh: _Mesh, *, assign_i_global: bool = False
35) -> _Tuple[_Dict[_Node, _Node], _List[_Node]]:
36 """Get a mapping of nodes in a mesh that should be "replaced" because they
37 are coupled via a joint.
39 In some finite element (FE) solvers, nodes coupled via joints are resolved
40 by assigning a "master" node to represent the joint. This function identifies
41 such nodes and creates a mapping where each coupled node is mapped to its
42 master node.
44 Args
45 ----
46 mesh:
47 Input mesh
48 assign_i_global:
49 If this flag is set, the global indices are set in the node objects.
51 Return
52 ----
53 replaced_node_to_master_map:
54 A dictionary mapping each "replaced" node to its "master" node.
55 unique_nodes:
56 A list containing all unique nodes in the mesh, i.e., all nodes which
57 are not coupled and the master nodes.
58 """
60 # Get a dictionary that maps the "replaced" nodes to the "master" ones
61 replaced_node_to_master_map = {}
62 for coupling in mesh.boundary_conditions[_mpy.bc.point_coupling, _mpy.geo.point]:
63 if coupling.coupling_dof_type is not _mpy.coupling_dof.fix:
64 raise ValueError(
65 "This function is only implemented for rigid joints at the DOFs"
66 )
67 coupling_nodes = coupling.geometry_set.get_points()
68 for node in coupling_nodes[1:]:
69 replaced_node_to_master_map[node] = coupling_nodes[0]
71 # Check that no "replaced" node is a "master" node
72 master_nodes = set(replaced_node_to_master_map.values())
73 for replaced_node in replaced_node_to_master_map.keys():
74 if replaced_node in master_nodes:
75 raise ValueError(
76 "A replaced node is also a master nodes. This is not supported"
77 )
79 # Get all unique nodes
80 unique_nodes = [
81 node for node in mesh.nodes if node not in replaced_node_to_master_map
82 ]
84 # Optionally number the nodes
85 if assign_i_global:
86 for i_node, node in enumerate(unique_nodes):
87 node.i_global = i_node
88 for replaced_node, master_node in replaced_node_to_master_map.items():
89 replaced_node.i_global = master_node.i_global
91 # Return the mapping
92 return replaced_node_to_master_map, unique_nodes