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

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.""" 

23 

24from typing import Dict as _Dict 

25from typing import List as _List 

26from typing import Tuple as _Tuple 

27 

28from meshpy.core.conf import mpy as _mpy 

29from meshpy.core.mesh import Mesh as _Mesh 

30from meshpy.core.node import Node as _Node 

31 

32 

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. 

38 

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. 

43 

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. 

50 

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 """ 

59 

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] 

70 

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 ) 

78 

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 ] 

83 

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 

90 

91 # Return the mapping 

92 return replaced_node_to_master_map, unique_nodes