DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
CartUtil.py
Go to the documentation of this file.
1import numpy as np
2
3# import numba
4import copy
5import timeit
6
7
8def segments_intersect_polygon_edge(polygon, x0, x1, y0, y1, closed=True):
9 assert polygon.ndim == 2
10
11 # Polygon edges
12 if closed:
13 xp0, yp0 = polygon
14 xp1, yp1 = np.roll(xp0, 1), np.roll(yp0, 1)
15 else:
16 xp0, yp0 = polygon[:, :-1]
17 xp1, yp1 = polygon[:, 1:]
18
19 # Broadcast query segments
20 x0 = np.asarray(x0)[..., None]
21 y0 = np.asarray(y0)[..., None]
22 x1 = np.asarray(x1)[..., None]
23 y1 = np.asarray(y1)[..., None]
24
25 # Vectorized segment to segment intersection
26 # Segments: (x0,y0)-(x1,y1) and (xp0,yp0)-(xp1,yp1)
27
28 # Segment directions
29 dx1 = x1 - x0
30 dy1 = y1 - y0
31 dx2 = xp1 - xp0
32 dy2 = yp1 - yp0
33
34 # Denominator for parametric equations
35 denom = dx1 * dy2 - dy1 * dx2
36 denominatorScale = dx2**2 + dy2**2 + dx1**2 + dy1**2
37
38 # Parallel segments (denom == 0) won't intersect
39 parallel = np.isclose(denom, 0, atol=denominatorScale * 1e-8)
40 denom[parallel] = 1e300
41
42 # Compute numerators
43 dx0 = xp0 - x0
44 dy0 = yp0 - y0
45
46 t = (dx0 * dy2 - dy0 * dx2) / denom
47 u = (dx0 * dy1 - dy0 * dx1) / denom
48
49 # Conditions for proper intersection: t, u in [0, 1]
50 intersects = (~parallel) & (t >= 0) & (t <= 1) & (u >= 0) & (u <= 1)
51
52 # Now, for each query segment, check if any intersection
53 any_intersect = np.any(intersects, axis=-1)
54 # print(polygon)
55 # print(x1)
56
57 return any_intersect
58
59
60def points_in_polygon_winding(polygon, x, y, closed=True):
61 assert polygon.ndim == 2
62 if closed:
63 xp0, yp0 = polygon
64 xp1, yp1 = np.roll(xp0, 1), np.roll(yp0, 1)
65 else:
66 xp0, yp0 = polygon[:, :-1]
67 xp1, yp1 = polygon[:, 1:]
68
69 x = np.asarray(x)[..., None] # Add axis for broadcasting
70 y = np.asarray(y)[..., None]
71
72 # Vectors from point to edge start and end
73 x0, y0 = xp0 - x, yp0 - y
74 x1, y1 = xp1 - x, yp1 - y
75
76 # Compute angles between vectors (x0, y0) and (x1, y1)
77 cross = x0 * y1 - x1 * y0 # determinant
78 dot = x0 * x1 + y0 * y1 # dot product
79 angles = np.arctan2(cross, dot)
80 # when some angle is close to ±π, or to say cross is very close to 0, it's on the edge
81 on_some_edge = np.isclose(np.abs(angles), np.pi, rtol=1e-9).any(axis=-1)
82
83 # Sum of angles gives winding number
84 winding_number = np.sum(angles, axis=-1)
85
86 # Inside if winding number is approximately ±2π
87 return (np.abs(winding_number) > 1e-8) | on_some_edge
88
89
91 origin: np.ndarray, h: float, ijks: np.ndarray, elem: np.ndarray, closed=True
92):
93 """
94 Parameters:
95 origin: (2,) array
96 h: float
97 ijks: (2, ...) integer array
98 elem: is a 2D polygon, 2D coords of shape (2+, n_point)
99 """
100 assert elem.ndim == 2
101 n_point = elem.shape[1]
102 originCS = np.ones(ijks.ndim, dtype=np.int64)
103 originCS[0] = 2
104 ll = origin.reshape(originCS) + ijks.astype(np.float64) * h
105 ru = origin.reshape(originCS) + (ijks.astype(np.float64) + 1) * h
106
107 cshapeB = np.array(ru.shape, dtype=np.int64)
108 cshapeB[0] = 1
109 eshapeB = np.ones(ijks.ndim, dtype=np.int64)
110 eshapeB[0] = n_point
111
112 # print(ru[0])
113
114 elemXViewB = elem[0].reshape(eshapeB)
115 elemYViewB = elem[1].reshape(eshapeB)
116
117 xMaxViewB = ru[0].reshape(cshapeB)
118 xMinViewB = ll[0].reshape(cshapeB)
119 yMaxViewB = ru[1].reshape(cshapeB)
120 yMinViewB = ll[1].reshape(cshapeB)
121
122 any_e_node_in_box = (
123 (elemXViewB <= xMaxViewB)
124 & (elemXViewB >= xMinViewB)
125 & (elemYViewB <= yMaxViewB)
126 & (elemYViewB >= yMinViewB)
127 ).any(axis=0)
128
129 any_b_node_in_elem = (
130 points_in_polygon_winding(elem, ll[0], ll[1], closed)
131 | points_in_polygon_winding(elem, ll[0], ru[1], closed)
132 | points_in_polygon_winding(elem, ru[0], ll[1], closed)
133 | points_in_polygon_winding(elem, ru[0], ru[1], closed)
134 )
135
136 any_b_edge_v_elem_edge = ( # mind the order: x0 x1 y0 y1
137 segments_intersect_polygon_edge(elem, ll[0], ru[0], ll[1], ll[1], closed)
138 | segments_intersect_polygon_edge(elem, ll[0], ru[0], ru[1], ru[1], closed)
139 | segments_intersect_polygon_edge(elem, ll[0], ll[0], ll[1], ru[1], closed)
140 | segments_intersect_polygon_edge(elem, ru[0], ru[0], ll[1], ru[1], closed)
141 )
142
143 # print(any_b_node_in_elem)
144 return any_b_node_in_elem | any_e_node_in_box | any_b_edge_v_elem_edge
145
146
148 origin: np.ndarray, h: float, ijks: np.ndarray, elem: np.ndarray, closed=True
149):
150 """
151 Parameters:
152 origin: (2,) array
153 h: float
154 ijks: (2, ...) integer array
155 elem: is a 2D polygon, 2D coords of shape (2+, n_point)
156 """
157 assert elem.ndim == 2
158 n_point = elem.shape[1]
159 originCS = np.ones(ijks.ndim, dtype=np.int64)
160 originCS[0] = 2
161 ll = origin.reshape(originCS) + ijks.astype(np.float64) * h
162
163 cshapeB = np.array(ll.shape, dtype=np.int64)
164 cshapeB[0] = 1
165 eshapeB = np.ones(ijks.ndim, dtype=np.int64)
166 eshapeB[0] = n_point
167
168 return points_in_polygon_winding(elem, ll[0], ll[1], closed)
169
170
172 origin: np.ndarray, h: float, elem: np.ndarray, grid_eps=1e-10
173):
174 xyzmin = elem.min(axis=1)
175 xyzmax = elem.max(axis=1)
176
177 lowerijk = np.floor((xyzmin - origin) / h - grid_eps)
178 upperijk = np.ceil((xyzmax - origin) / h + grid_eps) #add eps here to avoid problems when elem is precisely edge
179
180 assert np.all(
181 (origin + upperijk * h) + grid_eps >= xyzmax
182 ), f"{origin}, {upperijk}, {origin + upperijk * h},\n {elem}\n, {xyzmax}"
183 assert np.all((origin + lowerijk * h) - grid_eps <= xyzmin)
184
185 return (np.asarray(lowerijk, dtype=np.int64), np.asarray(upperijk, dtype=np.int64))
186
187
189 origin: np.ndarray, h: float, elem: np.ndarray, closed=True
190):
191 """get element intersecting boxes
192
193 Args:
194 origin (np.ndarray): _description_
195 h (float): _description_
196 elem (np.ndarray): _description_
197 closed (bool, optional): _description_. Defaults to True.
198
199 Returns:
200 np.ndarray: ijks of the touched cells
201 """
202 lowerijk, upperijk = single_elem_box_range_2D(origin, h, elem)
203 iss = np.arange(lowerijk[0], upperijk[0], dtype=np.int64)
204 jss = np.arange(lowerijk[1], upperijk[1], dtype=np.int64)
205 ism, jsm = np.meshgrid(iss, jss)
206 ijks = np.array([ism, jsm])
207 mask = single_elem_intersection_mask_2D(origin, h, ijks, elem, closed=closed)
208 return ijks[:, mask]
209
210
212 origin: np.ndarray, h: float, elem: np.ndarray, closed=True
213):
214 """
215 get grid point ijks inside the element
216 Args:
217 origin (np.ndarray): _description_
218 h (float): _description_
219 elem (np.ndarray): _description_
220 closed (bool, optional): _description_. Defaults to True.
221
222 Returns:
223 np.ndarray: _description_
224 """
225 lowerijk, upperijk = single_elem_box_range_2D(origin, h, elem)
226 iss = np.arange(lowerijk[0], upperijk[0] + 1, dtype=np.int64)
227 jss = np.arange(lowerijk[1], upperijk[1] + 1, dtype=np.int64)
228 ism, jsm = np.meshgrid(iss, jss)
229 ijks = np.array([ism, jsm])
230
231 mask = single_elem_grid_points_mask_2D(origin, h, ijks, elem, closed=closed)
232 return ijks[:, mask]
233
234
235def points_get_grid_cells_2D(origin: np.ndarray, h: float, coords: np.ndarray):
236 originCS = np.ones(coords.ndim, dtype=np.int64)
237 originCS[0] = coords.shape[0]
238 lowerijk = np.floor((coords - origin.reshape(originCS)) / h)
239 return np.array(lowerijk, dtype=np.int64)
240
241
242if __name__ == "__main__":
243
244 elem_test = np.array([[0.5, 2.5, 0.5], [0.5, 0.5, 2.5]], dtype=np.float64)
245 elem_test_1 = np.array([[0.5, 2.5, 0.5], [0.5, 0.5, 0.6]], dtype=np.float64)
246 origin_test = np.array([0.0, 0.0], dtype=np.float64)
247 print(
249 origin_test,
250 1.0,
251 np.array(
252 [[0, 1, 2, 3] * 4, [i // 4 for i in range(4 * 4)]], dtype=np.int64
253 ).reshape((2, 4, 4)),
254 elem_test,
255 )
256 )
257
258 assert (
260 origin_test,
261 1.0,
262 elem_test_1,
263 ).shape[1]
264 == 3
265 )
266
267 time_result = timeit.timeit(
268 lambda: single_elem_get_box_intersection_2D(origin_test, 1.0, elem_test),
269 number=100,
270 )
271 print(f"time: {time_result}")
272
273 print(single_elem_get_box_intersection_2D(origin_test, 1.0, elem_test))
274 print(single_elem_get_grid_point_2D(origin_test, 1.0, elem_test))
275
276 # print(
277 # single_elem_intersection_2D(
278 # np.array((0.0, 0.0)),
279 # 1.0,
280 # np.array([[0], [4]]).reshape((2, 1, 1)),
281 # np.array([[0.5, 2.5, 0.5], [0.5, 0.5, 2.5]]),
282 # )
283 # )
single_elem_get_box_intersection_2D(np.ndarray origin, float h, np.ndarray elem, closed=True)
Definition CartUtil.py:190
single_elem_grid_points_mask_2D(np.ndarray origin, float h, np.ndarray ijks, np.ndarray elem, closed=True)
Definition CartUtil.py:149
single_elem_get_grid_point_2D(np.ndarray origin, float h, np.ndarray elem, closed=True)
Definition CartUtil.py:213
single_elem_box_range_2D(np.ndarray origin, float h, np.ndarray elem, grid_eps=1e-10)
Definition CartUtil.py:173
points_get_grid_cells_2D(np.ndarray origin, float h, np.ndarray coords)
Definition CartUtil.py:235
single_elem_intersection_mask_2D(np.ndarray origin, float h, np.ndarray ijks, np.ndarray elem, closed=True)
Definition CartUtil.py:92
segments_intersect_polygon_edge(polygon, x0, x1, y0, y1, closed=True)
Definition CartUtil.py:8
points_in_polygon_winding(polygon, x, y, closed=True)
Definition CartUtil.py:60