Coverage for src/meshpy/core/coupling.py: 94%

35 statements  

« prev     ^ index     » next       coverage.py v7.9.0, created at 2025-06-13 04:26 +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 implements a class to couple geometry together.""" 

23 

24from typing import List as _List 

25from typing import Union as _Union 

26 

27import numpy as _np 

28 

29import meshpy.core.conf as _conf 

30from meshpy.core.boundary_condition import ( 

31 BoundaryConditionBase as _BoundaryConditionBase, 

32) 

33from meshpy.core.conf import mpy as _mpy 

34from meshpy.core.geometry_set import GeometrySet as _GeometrySet 

35from meshpy.core.geometry_set import GeometrySetBase as _GeometrySetBase 

36from meshpy.core.node import Node as _Node 

37 

38 

39class Coupling(_BoundaryConditionBase): 

40 """Represents a coupling between geometries in 4C.""" 

41 

42 def __init__( 

43 self, 

44 geometry: _Union[_GeometrySetBase, _List[_Node]], 

45 coupling_type: _Union[_conf.BoundaryCondition, str], 

46 coupling_dof_type: _Union[_conf.CouplingDofType, dict], 

47 *, 

48 check_overlapping_nodes: bool = True, 

49 ): 

50 """Initialize this object. 

51 

52 Args: 

53 geometry: Geometry set or nodes that should be coupled. 

54 coupling_type: If this is a string, this will be the section that 

55 this coupling will be added to. If it is a mpy.bc, the section 

56 will be determined automatically. 

57 coupling_dof_type: If this is a dictionary it is the dictionary 

58 that will be used in the input file, otherwise it has to be 

59 of type mpy.coupling_dof. 

60 check_overlapping_nodes: If all nodes of this coupling condition 

61 have to be at the same physical position. 

62 """ 

63 

64 if isinstance(geometry, _GeometrySetBase): 

65 pass 

66 elif isinstance(geometry, list): 

67 geometry = _GeometrySet(geometry) 

68 else: 

69 raise TypeError( 

70 f"Coupling expects a GeometrySetBase item, got {type(geometry)}" 

71 ) 

72 

73 # Couplings only work for point sets 

74 if ( 

75 isinstance(geometry, _GeometrySetBase) 

76 and geometry.geometry_type is not _mpy.geo.point 

77 ): 

78 raise TypeError("Couplings are only implemented for point sets.") 

79 

80 super().__init__(geometry, bc_type=coupling_type, data=coupling_dof_type) 

81 self.check_overlapping_nodes = check_overlapping_nodes 

82 

83 # Perform sanity checks for this boundary condition 

84 self.check() 

85 

86 def check(self): 

87 """Check that all nodes that are coupled have the same position 

88 (depending on the check_overlapping_nodes parameter).""" 

89 

90 if not self.check_overlapping_nodes: 

91 return 

92 

93 nodes = self.geometry_set.get_points() 

94 diff = _np.zeros([len(nodes), 3]) 

95 for i, node in enumerate(nodes): 

96 # Get the difference to the first node 

97 diff[i, :] = node.coordinates - nodes[0].coordinates 

98 if _np.max(_np.linalg.norm(diff, axis=1)) > _mpy.eps_pos: 

99 raise ValueError( 

100 "The nodes given to Coupling do not have the same position." 

101 ) 

102 

103 

104def coupling_factory(geometry, coupling_type, coupling_dof_type, **kwargs): 

105 """Create coupling conditions for the nodes in geometry. 

106 

107 Some solvers only allow coupling conditions containing two points at 

108 once, in that case we have to create multiple coupling conditions 

109 between the individual points to ensure the correct representation 

110 of the coupling. 

111 """ 

112 

113 if coupling_type.is_point_coupling_pairwise(): 

114 main_node = geometry[0] 

115 return [ 

116 Coupling([main_node, node], coupling_type, coupling_dof_type, **kwargs) 

117 for node in geometry[1:] 

118 ] 

119 else: 

120 return [Coupling(geometry, coupling_type, coupling_dof_type, **kwargs)]