@@ -34,8 +34,9 @@ def construct_dual(grid):
3434 # Get an array with the number of edges for each face
3535 n_edges_mask = node_face_connectivity != INT_FILL_VALUE
3636 n_edges = np .sum (n_edges_mask , axis = 1 )
37- max_edges = len ( node_face_connectivity [ 0 ])
37+ max_edges = node_face_connectivity . shape [ 1 ]
3838
39+ # Only nodes with 3+ edges can form valid dual faces
3940 valid_node_indices = np .where (n_edges >= 3 )[0 ]
4041
4142 construct_node_face_connectivity = np .full (
@@ -80,25 +81,25 @@ def construct_faces(
8081 Parameters
8182 ----------
8283 valid_node_indices: np.ndarray
83- number of valid nodes in the primal mesh
84+ Array of node indices with at least 3 connections in the primal mesh
8485 n_edges: np.ndarray
85- array of the number of edges for each dual face
86+ Array of the number of edges for each node in the primal mesh
8687 dual_node_x: np.ndarray
87- x node coordinates for the dual mesh
88+ x coordinates for the dual mesh nodes (face centers of primal mesh)
8889 dual_node_y: np.ndarray
89- y node coordinates for the dual mesh
90+ y coordinates for the dual mesh nodes (face centers of primal mesh)
9091 dual_node_z: np.ndarray
91- z node coordinates for the dual mesh
92+ z coordinates for the dual mesh nodes (face centers of primal mesh)
9293 node_face_connectivity: np.ndarray
93- `node_face_connectivity` of the primal mesh
94+ Node-to-face connectivity of the primal mesh
9495 node_x: np.ndarray
95- x node coordinates from the primal mesh
96+ x coordinates of nodes from the primal mesh
9697 node_y: np.ndarray
97- y node coordinates from the primal mesh
98+ y coordinates of nodes from the primal mesh
9899 node_z: np.ndarray
99- z node coordinates from the primal mesh
100+ z coordinates of nodes from the primal mesh
100101 construct_node_face_connectivity: np.ndarray
101- Empty array to store connectivity
102+ Pre-allocated array to store the dual mesh connectivity
102103 max_edges: int
103104 The max number of edges in a face
104105
@@ -107,6 +108,13 @@ def construct_faces(
107108 --------
108109 construct_node_face_connectivity : ndarray
109110 Constructed node_face_connectivity for the dual mesh
111+
112+ Notes
113+ -----
114+ In dual mesh construction, the "valid node indices" are face indices from
115+ the primal mesh's node_face_connectivity that are not fill values. These
116+ represent the actual faces that each primal node connects to, which become
117+ the nodes of the dual mesh faces.
110118 """
111119 n_valid = valid_node_indices .shape [0 ]
112120
@@ -118,14 +126,13 @@ def construct_faces(
118126 [INT_FILL_VALUE for _ in range (n_edges [i ])], dtype = INT_DTYPE
119127 )
120128
121- # Get a list of the valid non fill value nodes
129+ # Get the face indices this node connects to (these become dual face nodes)
122130 connected_faces = node_face_connectivity [i ][0 : n_edges [i ]]
123- index = 0
124131
125132 # Connect the face centers around the node to make dual face
126- for node_idx in connected_faces :
127- temp_face [ index ] = node_idx
128- index += 1
133+ for index , node_idx in enumerate ( connected_faces ) :
134+ if node_idx != INT_FILL_VALUE :
135+ temp_face [ index ] = node_idx
129136
130137 # Order the nodes using the angles so the faces have nodes in counter-clockwise sequence
131138 node_central = np .array ([node_x [i ], node_y [i ], node_z [i ]])
@@ -138,7 +145,7 @@ def construct_faces(
138145 )
139146
140147 # Order the face nodes properly in a counter-clockwise fashion
141- if temp_face [0 ] is not INT_FILL_VALUE :
148+ if temp_face [0 ] != INT_FILL_VALUE :
142149 _face = _order_nodes (
143150 temp_face ,
144151 node_0 ,
@@ -192,10 +199,18 @@ def _order_nodes(
192199 final_face : np.ndarray
193200 The face in proper counter-clockwise order
194201 """
202+ # Add numerical stability check for degenerate cases
203+ if n_edges < 3 :
204+ return np .full (max_edges , INT_FILL_VALUE , dtype = INT_DTYPE )
205+
195206 node_zero = node_0 - node_central
207+ node_zero_mag = np .linalg .norm (node_zero )
208+
209+ # Check for numerical stability
210+ if node_zero_mag < 1e-15 :
211+ return np .full (max_edges , INT_FILL_VALUE , dtype = INT_DTYPE )
196212
197213 node_cross = np .cross (node_0 , node_central )
198- node_zero_mag = np .linalg .norm (node_zero )
199214
200215 d_angles = np .zeros (n_edges , dtype = np .float64 )
201216 d_angles [0 ] = 0.0
@@ -214,11 +229,16 @@ def _order_nodes(
214229 node_diff = sub - node_central
215230 node_diff_mag = np .linalg .norm (node_diff )
216231
232+ # Skip if node difference is too small (numerical stability)
233+ if node_diff_mag < 1e-15 :
234+ d_angles [j ] = 0.0
235+ continue
236+
217237 d_side = np .dot (node_cross , node_diff )
218238 d_dot_norm = np .dot (node_zero , node_diff ) / (node_zero_mag * node_diff_mag )
219239
220- if d_dot_norm > 1.0 :
221- d_dot_norm = 1.0
240+ # Clamp to valid range for arccos to avoid numerical errors
241+ d_dot_norm = max ( - 1.0 , min ( 1.0 , d_dot_norm ))
222242
223243 d_angles [j ] = np .arccos (d_dot_norm )
224244
0 commit comments