Skip to content

Commit b6b1eb8

Browse files
authored
Optimized Geometry Functions using Numba (#1072)
* re-write bounds to use Numba * more numba * fix all tests but two * remove unused helper * fix all tests but one * recursion limit * recursion limit * cache and parallel * fix failing test * fix all tests but one * fix final test * Clean up commented out code * cleanup comments * more comment cleanup * update use of face bounds * fix failing test * update docstrings (1) * update API * update docstrings (2) * only use bounding box and raise warning for first computation * add exception for non-face-centered data variables * update docstring * pre-commit * update docstring and parameter * add warning when translating numba functions for the first time * update numba check * remove print statement
1 parent 282b697 commit b6b1eb8

16 files changed

+1213
-949
lines changed

Diff for: docs/api.rst

+2
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ Descriptors
126126
Grid.descriptors
127127
Grid.face_areas
128128
Grid.bounds
129+
Grid.face_bounds_lon
130+
Grid.face_bounds_lat
129131
Grid.edge_node_distances
130132
Grid.edge_face_distances
131133
Grid.antimeridian_face_indices

Diff for: test/test_arcs.py

+17-17
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import uxarray as ux
1111

1212
from uxarray.grid.coordinates import _lonlat_rad_to_xyz
13-
from uxarray.grid.arcs import point_within_gca
13+
from uxarray.grid.arcs import point_within_gca, _point_within_gca_cartesian
1414

1515
try:
1616
import constants
@@ -38,27 +38,27 @@ def test_pt_within_gcr(self):
3838

3939
pt_same_lon_in = _lonlat_rad_to_xyz(0.0, 0.0)
4040
with self.assertRaises(ValueError):
41-
point_within_gca(pt_same_lon_in, gcr_180degree_cart)
41+
_point_within_gca_cartesian(pt_same_lon_in, gcr_180degree_cart)
4242

4343
# Test when the point and the GCA all have the same longitude
4444
gcr_same_lon_cart = [
4545
_lonlat_rad_to_xyz(0.0, 1.5),
4646
_lonlat_rad_to_xyz(0.0, -1.5)
4747
]
4848
pt_same_lon_in = _lonlat_rad_to_xyz(0.0, 0.0)
49-
self.assertTrue(point_within_gca(pt_same_lon_in, gcr_same_lon_cart))
49+
self.assertTrue(_point_within_gca_cartesian(pt_same_lon_in, gcr_same_lon_cart))
5050

5151
pt_same_lon_out = _lonlat_rad_to_xyz(0.0, 1.500000000000001)
52-
res = point_within_gca(pt_same_lon_out, gcr_same_lon_cart)
52+
res = _point_within_gca_cartesian(pt_same_lon_out, gcr_same_lon_cart)
5353
self.assertFalse(res)
5454

5555
pt_same_lon_out_2 = _lonlat_rad_to_xyz(0.1, 1.0)
56-
res = point_within_gca(pt_same_lon_out_2, gcr_same_lon_cart)
56+
res = _point_within_gca_cartesian(pt_same_lon_out_2, gcr_same_lon_cart)
5757
self.assertFalse(res)
5858

5959
# And if we increase the digital place by one, it should be true again
6060
pt_same_lon_out_add_one_place = _lonlat_rad_to_xyz(0.0, 1.5000000000000001)
61-
res = point_within_gca(pt_same_lon_out_add_one_place, gcr_same_lon_cart)
61+
res = _point_within_gca_cartesian(pt_same_lon_out_add_one_place, gcr_same_lon_cart)
6262
self.assertTrue(res)
6363

6464
# Normal case
@@ -69,7 +69,7 @@ def test_pt_within_gcr(self):
6969
-0.997]])
7070
pt_cart_within = np.array(
7171
[0.25616109352676675, 0.9246590335292105, -0.010021496695000144])
72-
self.assertTrue(point_within_gca(pt_cart_within, gcr_cart_2, True))
72+
self.assertTrue(_point_within_gca_cartesian(pt_cart_within, gcr_cart_2, True))
7373

7474
# Test other more complicate cases : The anti-meridian case
7575

@@ -80,16 +80,16 @@ def test_pt_within_gcr_antimeridian(self):
8080
gcr_cart = np.array([[0.351, -0.724, 0.593], [0.617, 0.672, 0.410]])
8181
pt_cart = np.array(
8282
[0.9438777657502077, 0.1193199333436068, 0.922714737029319])
83-
self.assertTrue(point_within_gca(pt_cart, gcr_cart, is_directed=True))
83+
self.assertTrue(_point_within_gca_cartesian(pt_cart, gcr_cart, is_directed=True))
8484
# If we swap the gcr, it should throw a value error since it's larger than 180 degree
8585
gcr_cart_flip = np.array([[0.617, 0.672, 0.410], [0.351, -0.724,
8686
0.593]])
8787
with self.assertRaises(ValueError):
88-
point_within_gca(pt_cart, gcr_cart_flip, is_directed=True)
88+
_point_within_gca_cartesian(pt_cart, gcr_cart_flip, is_directed=True)
8989

9090
# If we flip the gcr in the undirected mode, it should still work
9191
self.assertTrue(
92-
point_within_gca(pt_cart, gcr_cart_flip, is_directed=False))
92+
_point_within_gca_cartesian(pt_cart, gcr_cart_flip, is_directed=False))
9393

9494
# 2nd anti-meridian case
9595
# GCR vertex0 in radian : [4.104711496596806, 0.5352983676533828],
@@ -100,9 +100,9 @@ def test_pt_within_gcr_antimeridian(self):
100100
pt_cart_within = np.array(
101101
[0.6136726305712109, 0.28442243941920053, -0.365605190899831])
102102
self.assertFalse(
103-
point_within_gca(pt_cart_within, gcr_cart_1, is_directed=True))
103+
_point_within_gca_cartesian(pt_cart_within, gcr_cart_1, is_directed=True))
104104
self.assertFalse(
105-
point_within_gca(pt_cart_within, gcr_cart_1, is_directed=False))
105+
_point_within_gca_cartesian(pt_cart_within, gcr_cart_1, is_directed=False))
106106

107107
# The first case should not work and the second should work
108108
v1_rad = [0.1, 0.0]
@@ -112,10 +112,10 @@ def test_pt_within_gcr_antimeridian(self):
112112
gcr_cart = np.array([v1_cart, v2_cart])
113113
pt_cart = _lonlat_rad_to_xyz(0.01, 0.0)
114114
with self.assertRaises(ValueError):
115-
point_within_gca(pt_cart, gcr_cart, is_directed=True)
115+
_point_within_gca_cartesian(pt_cart, gcr_cart, is_directed=True)
116116
gcr_car_flipped = np.array([v2_cart, v1_cart])
117117
self.assertTrue(
118-
point_within_gca(pt_cart, gcr_car_flipped, is_directed=True))
118+
_point_within_gca_cartesian(pt_cart, gcr_car_flipped, is_directed=True))
119119

120120
def test_pt_within_gcr_cross_pole(self):
121121
gcr_cart = np.array([[0.351, 0.0, 0.3], [-0.351, 0.0, 0.3]])
@@ -125,7 +125,7 @@ def test_pt_within_gcr_cross_pole(self):
125125
# Normalize the point abd the GCA
126126
pt_cart = pt_cart / np.linalg.norm(pt_cart)
127127
gcr_cart = np.array([x / np.linalg.norm(x) for x in gcr_cart])
128-
self.assertTrue(point_within_gca(pt_cart, gcr_cart, is_directed=False))
128+
self.assertTrue(_point_within_gca_cartesian(pt_cart, gcr_cart, is_directed=False))
129129

130130
gcr_cart = np.array([[0.351, 0.0, 0.3], [-0.351, 0.0, -0.6]])
131131
pt_cart = np.array(
@@ -134,6 +134,6 @@ def test_pt_within_gcr_cross_pole(self):
134134
# When the point is not within the GCA
135135
pt_cart = pt_cart / np.linalg.norm(pt_cart)
136136
gcr_cart = np.array([x / np.linalg.norm(x) for x in gcr_cart])
137-
self.assertFalse(point_within_gca(pt_cart, gcr_cart, is_directed=False))
137+
self.assertFalse(_point_within_gca_cartesian(pt_cart, gcr_cart, is_directed=False))
138138
with self.assertRaises(ValueError):
139-
point_within_gca(pt_cart, gcr_cart, is_directed=True)
139+
_point_within_gca_cartesian(pt_cart, gcr_cart, is_directed=True)

Diff for: test/test_cross_sections.py

+26-33
Original file line numberDiff line numberDiff line change
@@ -35,103 +35,98 @@ class TestQuadHex:
3535
All four faces intersect a constant latitude of 0.0
3636
"""
3737

38-
@pytest.mark.parametrize("use_spherical_bounding_box", [True, False])
39-
def test_constant_lat_cross_section_grid(self, use_spherical_bounding_box):
40-
41-
38+
def test_constant_lat_cross_section_grid(self):
4239

4340
uxgrid = ux.open_grid(quad_hex_grid_path)
4441

45-
grid_top_two = uxgrid.cross_section.constant_latitude(lat=0.1, use_spherical_bounding_box=use_spherical_bounding_box)
42+
grid_top_two = uxgrid.cross_section.constant_latitude(lat=0.1, )
4643

4744
assert grid_top_two.n_face == 2
4845

49-
grid_bottom_two = uxgrid.cross_section.constant_latitude(lat=-0.1, use_spherical_bounding_box=use_spherical_bounding_box)
46+
grid_bottom_two = uxgrid.cross_section.constant_latitude(lat=-0.1, )
5047

5148
assert grid_bottom_two.n_face == 2
5249

53-
grid_all_four = uxgrid.cross_section.constant_latitude(lat=0.0, use_spherical_bounding_box=use_spherical_bounding_box)
50+
grid_all_four = uxgrid.cross_section.constant_latitude(lat=0.0, )
5451

5552
assert grid_all_four.n_face == 4
5653

5754
with pytest.raises(ValueError):
5855
# no intersections found at this line
59-
uxgrid.cross_section.constant_latitude(lat=10.0, use_spherical_bounding_box=use_spherical_bounding_box)
56+
uxgrid.cross_section.constant_latitude(lat=10.0, )
6057

61-
@pytest.mark.parametrize("use_spherical_bounding_box", [False])
62-
def test_constant_lon_cross_section_grid(self, use_spherical_bounding_box):
58+
def test_constant_lon_cross_section_grid(self):
6359
uxgrid = ux.open_grid(quad_hex_grid_path)
6460

65-
grid_left_two = uxgrid.cross_section.constant_longitude(lon=-0.1, use_spherical_bounding_box=use_spherical_bounding_box)
61+
grid_left_two = uxgrid.cross_section.constant_longitude(lon=-0.1, )
6662

6763
assert grid_left_two.n_face == 2
6864

69-
grid_right_two = uxgrid.cross_section.constant_longitude(lon=0.2, use_spherical_bounding_box=use_spherical_bounding_box)
65+
grid_right_two = uxgrid.cross_section.constant_longitude(lon=0.2, )
7066

7167
assert grid_right_two.n_face == 2
7268

7369
with pytest.raises(ValueError):
7470
# no intersections found at this line
7571
uxgrid.cross_section.constant_longitude(lon=10.0)
7672

77-
@pytest.mark.parametrize("use_spherical_bounding_box", [False])
78-
def test_constant_lat_cross_section_uxds(self, use_spherical_bounding_box):
73+
def test_constant_lat_cross_section_uxds(self):
7974
uxds = ux.open_dataset(quad_hex_grid_path, quad_hex_data_path)
8075
uxds.uxgrid.normalize_cartesian_coordinates()
8176

82-
da_top_two = uxds['t2m'].cross_section.constant_latitude(lat=0.1, use_spherical_bounding_box=use_spherical_bounding_box)
77+
da_top_two = uxds['t2m'].cross_section.constant_latitude(lat=0.1, )
8378

8479
nt.assert_array_equal(da_top_two.data, uxds['t2m'].isel(n_face=[1, 2]).data)
8580

86-
da_bottom_two = uxds['t2m'].cross_section.constant_latitude(lat=-0.1, use_spherical_bounding_box=use_spherical_bounding_box)
81+
da_bottom_two = uxds['t2m'].cross_section.constant_latitude(lat=-0.1, )
8782

8883
nt.assert_array_equal(da_bottom_two.data, uxds['t2m'].isel(n_face=[0, 3]).data)
8984

90-
da_all_four = uxds['t2m'].cross_section.constant_latitude(lat=0.0, use_spherical_bounding_box=use_spherical_bounding_box)
85+
da_all_four = uxds['t2m'].cross_section.constant_latitude(lat=0.0, )
9186

9287
nt.assert_array_equal(da_all_four.data , uxds['t2m'].data)
9388

9489
with pytest.raises(ValueError):
9590
# no intersections found at this line
96-
uxds['t2m'].cross_section.constant_latitude(lat=10.0, use_spherical_bounding_box=use_spherical_bounding_box)
91+
uxds['t2m'].cross_section.constant_latitude(lat=10.0, )
92+
9793

98-
@pytest.mark.parametrize("use_spherical_bounding_box", [False])
99-
def test_constant_lon_cross_section_uxds(self, use_spherical_bounding_box):
94+
def test_constant_lon_cross_section_uxds(self):
10095
uxds = ux.open_dataset(quad_hex_grid_path, quad_hex_data_path)
10196
uxds.uxgrid.normalize_cartesian_coordinates()
10297

103-
da_left_two = uxds['t2m'].cross_section.constant_longitude(lon=-0.1, use_spherical_bounding_box=use_spherical_bounding_box)
98+
da_left_two = uxds['t2m'].cross_section.constant_longitude(lon=-0.1, )
10499

105100
nt.assert_array_equal(da_left_two.data, uxds['t2m'].isel(n_face=[0, 2]).data)
106101

107-
da_right_two = uxds['t2m'].cross_section.constant_longitude(lon=0.2, use_spherical_bounding_box=use_spherical_bounding_box)
102+
da_right_two = uxds['t2m'].cross_section.constant_longitude(lon=0.2, )
108103

109104
nt.assert_array_equal(da_right_two.data, uxds['t2m'].isel(n_face=[1, 3]).data)
110105

111106
with pytest.raises(ValueError):
112107
# no intersections found at this line
113-
uxds['t2m'].cross_section.constant_longitude(lon=10.0, use_spherical_bounding_box=use_spherical_bounding_box)
108+
uxds['t2m'].cross_section.constant_longitude(lon=10.0, )
114109

115110

116111
class TestCubeSphere:
117-
@pytest.mark.parametrize("use_spherical_bounding_box", [True, False])
118-
def test_north_pole(self, use_spherical_bounding_box):
112+
113+
def test_north_pole(self):
119114
uxgrid = ux.open_grid(cube_sphere_grid)
120115

121116
lats = [89.85, 89.9, 89.95, 89.99]
122117

123118
for lat in lats:
124-
cross_grid = uxgrid.cross_section.constant_latitude(lat=lat, use_spherical_bounding_box=use_spherical_bounding_box)
119+
cross_grid = uxgrid.cross_section.constant_latitude(lat=lat, )
125120
# Cube sphere grid should have 4 faces centered around the pole
126121
assert cross_grid.n_face == 4
127-
@pytest.mark.parametrize("use_spherical_bounding_box", [True, False])
128-
def test_south_pole(self, use_spherical_bounding_box):
122+
123+
def test_south_pole(self):
129124
uxgrid = ux.open_grid(cube_sphere_grid)
130125

131126
lats = [-89.85, -89.9, -89.95, -89.99]
132127

133128
for lat in lats:
134-
cross_grid = uxgrid.cross_section.constant_latitude(lat=lat, use_spherical_bounding_box=use_spherical_bounding_box)
129+
cross_grid = uxgrid.cross_section.constant_latitude(lat=lat, )
135130
# Cube sphere grid should have 4 faces centered around the pole
136131
assert cross_grid.n_face == 4
137132

@@ -152,8 +147,7 @@ def test_constant_lat(self):
152147

153148
candidate_faces = constant_lat_intersections_face_bounds(
154149
lat=const_lat,
155-
face_min_lat_rad=bounds_rad[:, 0, 0],
156-
face_max_lat_rad=bounds_rad[:, 0, 1],
150+
face_bounds_lat=bounds_rad[:, 0],
157151
)
158152

159153
# Expected output
@@ -176,8 +170,7 @@ def test_constant_lat_out_of_bounds(self):
176170

177171
candidate_faces = constant_lat_intersections_face_bounds(
178172
lat=const_lat,
179-
face_min_lat_rad=bounds_rad[:, 0, 0],
180-
face_max_lat_rad=bounds_rad[:, 0, 1],
173+
face_bounds_lat=bounds_rad[:, 0],
181174
)
182175

183176
assert len(candidate_faces) == 0

0 commit comments

Comments
 (0)