sectionate 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,120 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+ import xgcm
4
+
5
+ # define simple xgcm grid
6
+ def initialize_minimal_outer_grid():
7
+ xh = np.array([0.5])
8
+ yh = np.array([0.5])
9
+ xq = np.array([0., 1.])
10
+ yq = np.array([0., 1.])
11
+
12
+ lon, lat = np.meshgrid(xh, yh)
13
+ lon_c, lat_c = np.meshgrid(xq, yq)
14
+ ds = xr.Dataset({}, coords={
15
+ "xh":xr.DataArray(xh, dims=("xh",)),
16
+ "yh":xr.DataArray(yh, dims=("yh",)),
17
+ "xq":xr.DataArray(xq, dims=("xq",)),
18
+ "yq":xr.DataArray(yq, dims=("yq",)),
19
+ "geolon":xr.DataArray(lon, dims=("yh", "xh")),
20
+ "geolat":xr.DataArray(lat, dims=("yh", "xh")),
21
+ "geolon_c":xr.DataArray(lon_c, dims=("yq", "xq",)),
22
+ "geolat_c":xr.DataArray(lat_c, dims=("yq", "xq",))
23
+ })
24
+ coords = {
25
+ 'X': {'outer': 'xq', 'center': 'xh'},
26
+ 'Y': {'outer': 'yq', 'center': 'yh'}
27
+ }
28
+ boundary = {
29
+ 'X': "extend", # Specifying None causes this to default to `periodic` for some reason
30
+ 'Y': "extend" # Specifying None causes this to default to `periodic` for some reason
31
+ }
32
+ grid = xgcm.Grid(ds, coords=coords, boundary=boundary, autoparse_metadata=False)
33
+ return grid
34
+
35
+ def test_convergent_transport():
36
+ from sectionate.section import grid_section
37
+ from sectionate.transports import convergent_transport
38
+ grid = initialize_minimal_outer_grid()
39
+ grid._ds['u'] = xr.DataArray(np.array([[1., -np.sqrt(2.)]]), dims=("yh","xq",))
40
+ grid._ds['v'] = xr.DataArray(np.array([[0], [np.pi]]), dims=("yq","xh",))
41
+
42
+ # closed path around the whole square domain
43
+ lonseg = np.array([0, 1, 1, 0, 0])
44
+ latseg = np.array([0, 0, 1, 1, 0])
45
+ i, j, lons, lats = grid_section(grid, lonseg, latseg)
46
+
47
+ conv = convergent_transport(
48
+ grid,
49
+ i,
50
+ j,
51
+ utr="u",
52
+ vtr="v",
53
+ layer=None,
54
+ geometry="cartesian"
55
+ )['conv_mass_transport'].sum().values
56
+
57
+ assert np.isclose(1. + 0. + np.sqrt(2.) - np.pi, conv, rtol=1.e-14)
58
+
59
+
60
+ def initialize_minimal_spherical_grid():
61
+ xq = np.array([0., 120., 240., 360.])
62
+ yq = np.array([-80, 0., 80.])
63
+ xh = np.array([60., 180., 300.])
64
+ yh = np.array([-40., 40.])
65
+
66
+ lon, lat = np.meshgrid(xh, yh)
67
+ lon_c, lat_c = np.meshgrid(xq, yq)
68
+ ds = xr.Dataset({}, coords={
69
+ "xh":xr.DataArray(xh, dims=("xh",)),
70
+ "yh":xr.DataArray(yh, dims=("yh",)),
71
+ "xq":xr.DataArray(xq, dims=("xq",)),
72
+ "yq":xr.DataArray(yq, dims=("yq",)),
73
+ "geolon":xr.DataArray(lon, dims=("yh", "xh")),
74
+ "geolat":xr.DataArray(lat, dims=("yh", "xh")),
75
+ "geolon_c":xr.DataArray(lon_c, dims=("yq", "xq",)),
76
+ "geolat_c":xr.DataArray(lat_c, dims=("yq", "xq",))
77
+ })
78
+ coords = {
79
+ 'X': {'outer': 'xq', 'center': 'xh'},
80
+ 'Y': {'outer': 'yq', 'center': 'yh'}
81
+ }
82
+ boundary = {
83
+ 'X': "periodic",
84
+ 'Y': "extend"
85
+ }
86
+ grid = xgcm.Grid(ds, coords=coords, boundary=boundary, autoparse_metadata=False)
87
+ return grid
88
+
89
+ def test_convergent_transport_convention():
90
+ from sectionate.section import grid_section
91
+ from sectionate.transports import convergent_transport
92
+ grid = initialize_minimal_spherical_grid()
93
+ u = np.zeros((grid._ds.yh.size, grid._ds.xq.size))
94
+ v = np.ones((grid._ds.yq.size, grid._ds.xh.size))
95
+ grid._ds['u'] = xr.DataArray(u, dims=("yh", "xq"))
96
+ grid._ds['v'] = xr.DataArray(v, dims=("yq", "xh"))
97
+
98
+ lonseg = np.array([0., -120., -240., -360.])
99
+ latseg = np.array([0., 0., 0., 0.])
100
+
101
+ i, j, lons, lats = grid_section(grid, lonseg, latseg)
102
+ conv = convergent_transport(
103
+ grid,
104
+ i,
105
+ j,
106
+ utr="u",
107
+ vtr="v",
108
+ layer=None
109
+ )['conv_mass_transport'].sum().values
110
+
111
+ conv_rev = convergent_transport(
112
+ grid,
113
+ i[::-1],
114
+ j[::-1],
115
+ utr="u",
116
+ vtr="v",
117
+ layer=None
118
+ )['conv_mass_transport'].sum().values
119
+
120
+ assert np.equal(-3., conv) and np.equal(-3, conv_rev)
@@ -0,0 +1,92 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+
4
+
5
+ # define simple lat-lon grid
6
+ lon, lat = np.meshgrid(np.arange(360), np.arange(-80, 81))
7
+ ds = xr.Dataset()
8
+ ds["lon"] = xr.DataArray(lon, dims=("y", "x"))
9
+ ds["lat"] = xr.DataArray(lat, dims=("y", "x"))
10
+
11
+ def test_distance_on_unit_sphere():
12
+ from sectionate.section import distance_on_unit_sphere
13
+
14
+ # test of few points with unit radius
15
+ d = distance_on_unit_sphere(0, 0, 1.e-20, 0, R=1.)
16
+ assert np.isclose(d, 0., atol=1.e-14)
17
+ d = distance_on_unit_sphere(0, 0, 360, 0, R=1.)
18
+ assert np.isclose(d, 0., atol=1.e-14)
19
+ d = distance_on_unit_sphere(0, 45, 0, -45, R=1.)
20
+ assert np.isclose(d, np.pi/2, atol=1.e-14)
21
+ d = distance_on_unit_sphere(0, 0, 180, 0, R=1.)
22
+ assert np.isclose(d, np.pi, atol=1.e-14)
23
+ d = distance_on_unit_sphere(180, 0, 90, 0, R=1.)
24
+ assert np.isclose(d, np.pi/2, atol=1.e-14)
25
+ d = distance_on_unit_sphere(180, 45, 180, 0, R=1.)
26
+ assert np.isclose(d, np.pi/4, atol=1.e-14)
27
+
28
+
29
+ def test_find_closest_grid_point():
30
+ from sectionate.section import find_closest_grid_point
31
+
32
+ # check it works with numpy arrays
33
+ i, j = find_closest_grid_point(0, 0, lon, lat)
34
+ assert np.equal(i, 0)
35
+ assert np.equal(j, 80)
36
+
37
+ # and xarray
38
+ i, j = find_closest_grid_point(0, 0, ds["lon"], ds["lat"])
39
+ assert np.equal(i, 0)
40
+ assert np.equal(j, 80)
41
+
42
+ i, j = find_closest_grid_point(180, 80, ds["lon"], ds["lat"])
43
+ assert np.equal(i, 180)
44
+ assert np.equal(j, 160)
45
+
46
+
47
+ def test_grid_path():
48
+ from sectionate.section import infer_grid_path
49
+
50
+ # test zonal line
51
+ isec, jsec, lonsec, latsec = infer_grid_path(0, 80, 179, 80, lon, lat)
52
+ assert len(isec) == 180
53
+ assert lonsec[0] == 0.0
54
+ assert lonsec[-1] == 179.0
55
+ assert latsec[0] == 0.0
56
+ assert latsec[-1] == 0.0
57
+
58
+ # test merid line
59
+ isec, jsec, lonsec, latsec = infer_grid_path(0, 0, 0, 160, lon, lat)
60
+ assert len(isec) == 161
61
+ assert lonsec[0] == 0.
62
+ assert lonsec[-1] == 0.
63
+ assert latsec[0] == -80.0
64
+ assert latsec[-1] == 80.0
65
+
66
+ # test diagonal
67
+ isec, jsec, lonsec, latsec = infer_grid_path(0, 0, 100, 100, lon, lat)
68
+ assert len(isec) == 201 # expect ni+nj+1 values
69
+ isec, jsec, lonsec, latsec = infer_grid_path(0, 0, 50, 100, lon, lat)
70
+ assert len(isec) == 151 # expect ni+nj+1 values
71
+ isec, jsec, lonsec, latsec = infer_grid_path(10, 10, 100, 50, lon, lat)
72
+ assert len(isec) == 131 # expect ni+nj+1 values
73
+
74
+
75
+ def test_infer_grid_path_from_geo():
76
+ from sectionate.section import infer_grid_path_from_geo
77
+
78
+ # test zonal line
79
+ isec, jsec, lonsec, latsec = infer_grid_path_from_geo(0, 0, 179, 0, lon, lat)
80
+ assert len(isec) == 180
81
+ assert lonsec[0] == 0.0
82
+ assert lonsec[-1] == 179.0
83
+ assert latsec[0] == 0.0
84
+ assert latsec[-1] == 0.0
85
+
86
+ # test merid line
87
+ isec, jsec, lonsec, latsec = infer_grid_path_from_geo(180, -80, 180, 80, lon, lat)
88
+ assert len(isec) == 161
89
+ assert lonsec[0] == 180.0
90
+ assert lonsec[-1] == 180.0
91
+ assert latsec[0] == -80.0
92
+ assert latsec[-1] == 80.0
@@ -0,0 +1,59 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+ import xgcm
4
+
5
+ # define simple xgcm grid
6
+ xq = np.array([0., 60, 120, 180, 240, 300., 360.])
7
+ yq = np.array([-80., -40, 0, 40, 80.])
8
+
9
+ lon_c, lat_c = np.meshgrid(xq, yq)
10
+ ds = xr.Dataset({}, coords={
11
+ "xq":xr.DataArray(xq, dims=("xq",)),
12
+ "yq":xr.DataArray(yq, dims=("yq",)),
13
+ "lon_c":xr.DataArray(lon_c, dims=("yq", "xq",)),
14
+ "lat_c":xr.DataArray(lat_c, dims=("yq", "xq",))
15
+ })
16
+ coords = {
17
+ 'X': {'outer': 'xq'},
18
+ 'Y': {'outer': 'yq'}
19
+ }
20
+ boundary = {
21
+ 'X': 'periodic',
22
+ 'Y': 'extend'
23
+ }
24
+ grid = xgcm.Grid(ds, coords=coords, boundary=boundary, autoparse_metadata=False)
25
+
26
+ def modequal(a,b):
27
+ return np.equal(np.mod(a, 360.), np.mod(b, 360.))
28
+
29
+ def test_open_gridded_section():
30
+ from sectionate.section import Section, GriddedSection
31
+ lonseg = np.array([0., 120, 120, 0])
32
+ latseg = np.array([-80., -80, 0, 0])
33
+ sec = Section("testsec", (lonseg, latseg))
34
+ sec_gridded = GriddedSection(sec, grid)
35
+
36
+ assert np.all([
37
+ modequal(sec_gridded.i_c, np.array([0, 1, 2, 2, 2, 1, 0])),
38
+ modequal(sec_gridded.j_c, np.array([0, 0, 0, 1, 2, 2, 2])),
39
+ modequal(sec_gridded.lons_c, np.array([0., 60., 120., 120., 120., 60., 0.])),
40
+ modequal(sec_gridded.lats_c, np.array([-80., -80., -80., -40., 0., 0., 0.]))
41
+ ])
42
+
43
+ def test_closed_gridded_parent_section():
44
+ from sectionate.section import Section, join_sections, GriddedSection
45
+ lonseg = np.array([ 0., 120, 120, 0, 0])
46
+ latseg = np.array([-80., -80, 0, 0, -80.])
47
+ # Test join_sections and children/parent relationships
48
+ sec1 = Section("sec1", (lonseg[0:3], latseg[0:3]))
49
+ sec2 = Section("sec2", (lonseg[2: ], latseg[2: ]))
50
+ sec = join_sections("sec", sec1, sec2)
51
+ assert isinstance(sec.children["sec1"], Section)
52
+ # Test results from join_section
53
+ sec_gridded = GriddedSection(sec, grid)
54
+ assert np.all([
55
+ modequal(sec_gridded.i_c, np.array([0, 1, 2, 2, 2, 1, 0, 0, 0])),
56
+ modequal(sec_gridded.j_c, np.array([0, 0, 0, 1, 2, 2, 2, 1, 0])),
57
+ modequal(sec_gridded.lons_c, np.array([0., 60., 120., 120., 120., 60., 0., 0., 0.])),
58
+ modequal(sec_gridded.lats_c, np.array([-80., -80., -80., -40., 0., 0., 0., -40., -80.]))
59
+ ])
@@ -0,0 +1,63 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+ import xgcm
4
+
5
+ # define simple xgcm grid
6
+ xq = np.array([0., 60, 120, 180, 240, 300., 360.])
7
+ yq = np.array([-80., -40, 0, 40, 80.])
8
+
9
+ lon_c, lat_c = np.meshgrid(xq, yq)
10
+ ds = xr.Dataset({}, coords={
11
+ "xq":xr.DataArray(xq, dims=("xq",)),
12
+ "yq":xr.DataArray(yq, dims=("yq",)),
13
+ "lon_c":xr.DataArray(lon_c, dims=("yq", "xq",)),
14
+ "lat_c":xr.DataArray(lat_c, dims=("yq", "xq",))
15
+ })
16
+ coords = {
17
+ 'X': {'outer': 'xq'},
18
+ 'Y': {'outer': 'yq'}
19
+ }
20
+ boundary = {
21
+ 'X': 'periodic',
22
+ 'Y': 'extend'
23
+ }
24
+ grid = xgcm.Grid(ds, coords=coords, boundary=boundary, autoparse_metadata=False)
25
+
26
+ def modequal(a,b):
27
+ return np.equal(np.mod(a, 360.), np.mod(b, 360.))
28
+
29
+ def test_open_grid_section():
30
+ from sectionate.section import grid_section
31
+ lonseg = np.array([0., 120, 120, 0])
32
+ latseg = np.array([-80., -80, 0, 0])
33
+ i, j, lons, lats = grid_section(grid, lonseg, latseg)
34
+ assert np.all([
35
+ modequal(i, np.array([0, 1, 2, 2, 2, 1, 0])),
36
+ modequal(j, np.array([0, 0, 0, 1, 2, 2, 2])),
37
+ modequal(lons, np.array([0., 60., 120., 120., 120., 60., 0.])),
38
+ modequal(lats, np.array([-80., -80., -80., -40., 0., 0., 0.]))
39
+ ])
40
+
41
+ def test_closed_grid_section():
42
+ from sectionate.section import grid_section
43
+ lonseg = np.array([0., 120, 120, 0, 0])
44
+ latseg = np.array([-80., -80, 0, 0, -80.])
45
+ i, j, lons, lats = grid_section(grid, lonseg, latseg)
46
+ assert np.all([
47
+ modequal(i, np.array([0, 1, 2, 2, 2, 1, 0, 0, 0])),
48
+ modequal(j, np.array([0, 0, 0, 1, 2, 2, 2, 1, 0])),
49
+ modequal(lons, np.array([0., 60., 120., 120., 120., 60., 0., 0., 0.])),
50
+ modequal(lats, np.array([-80., -80., -80., -40., 0., 0., 0., -40., -80.]))
51
+ ])
52
+
53
+ def test_periodic_grid_section():
54
+ from sectionate.section import grid_section
55
+ lonseg = np.array([300, 60])
56
+ latseg = np.array([0, 0])
57
+ i, j, lons, lats = grid_section(grid, lonseg, latseg)
58
+ assert np.all([
59
+ modequal(i, np.array([5, 0, 1])),
60
+ modequal(j, np.array([2, 2, 2])),
61
+ modequal(lons, np.array([300., 0., 60.])),
62
+ modequal(lats, np.array([0., 0., 0.]))
63
+ ])
@@ -0,0 +1,4 @@
1
+ def test_load_section():
2
+ from sectionate.utils import get_all_section_names, load_section
3
+ section_names = get_all_section_names()
4
+ load_section(section_names[0])
sectionate/tracers.py ADDED
@@ -0,0 +1,76 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+ from .transports import (
4
+ uvindices_from_qindices
5
+ )
6
+
7
+ from .gridutils import (
8
+ check_symmetric,
9
+ coord_dict
10
+ )
11
+
12
+ def extract_tracer(
13
+ name,
14
+ grid,
15
+ i_c,
16
+ j_c,
17
+ sect_coord="sect"
18
+ ):
19
+ """
20
+ Extract tracer data on cell thickness grid along the grid path
21
+ of (i_c, j_c) for plotting.
22
+
23
+ PARAMETERS:
24
+ -----------
25
+ name:
26
+ name of variable in `grid._ds`
27
+ grid: xgcm.Grid
28
+ grid describing model and containing data
29
+ i_c: int
30
+ vorticity point indices along 'X' dimension
31
+ j_c: int
32
+ vorticity point indices along 'Y' dimension
33
+ sect_coord: str
34
+ Name of the dimension describing along-section data in the output. Default: 'sect'.
35
+
36
+ RETURNS:
37
+ --------
38
+
39
+ xarray.DataArray with data interpolated to the U and V points along the section.
40
+ """
41
+
42
+ da=grid._ds[name]
43
+ coords = coord_dict(grid)
44
+ symmetric = check_symmetric(grid)
45
+
46
+ # get indices of UV points from broken line
47
+ uvindices = uvindices_from_qindices(grid, i_c, j_c)
48
+
49
+ section = xr.Dataset()
50
+ section["i"] = xr.DataArray(uvindices["i"], dims=sect_coord)
51
+ section["j"] = xr.DataArray(uvindices["j"], dims=sect_coord)
52
+ section["Umask"] = xr.DataArray(uvindices["var"]=="U", dims=sect_coord)
53
+ section["Vmask"] = xr.DataArray(uvindices["var"]=="V", dims=sect_coord)
54
+
55
+ increment = 1 if symmetric else 0
56
+ usel_left = {coords["X"]["center"]: np.mod(section["i"]-increment , da[coords["X"]["center"]].size),
57
+ coords["Y"]["center"]: np.mod(section["j"] , da[coords["Y"]["center"]].size)}
58
+ usel_right = {coords["X"]["center"]: np.mod(section["i"]-increment+1, da[coords["X"]["center"]].size),
59
+ coords["Y"]["center"]: np.mod(section["j"] , da[coords["Y"]["center"]].size)}
60
+
61
+ vsel_left = {coords["X"]["center"]: np.mod(section["i"] , da[coords["X"]["center"]].size),
62
+ coords["Y"]["center"]: np.mod(section["j"]-increment , da[coords["Y"]["center"]].size)}
63
+ vsel_right = {coords["X"]["center"]: np.mod(section["i"] , da[coords["X"]["center"]].size),
64
+ coords["Y"]["center"]: np.mod(section["j"]-increment+1, da[coords["Y"]["center"]].size)}
65
+
66
+ tracer = sum([
67
+ xr.where(~np.isnan(da.isel(usel_right)), 0.5*da.isel(usel_left), da.isel(usel_left) ).fillna(0.) * section["Umask"],
68
+ xr.where(~np.isnan(da.isel(usel_left )), 0.5*da.isel(usel_right), da.isel(usel_right)).fillna(0.) * section["Umask"],
69
+ xr.where(~np.isnan(da.isel(vsel_right)), 0.5*da.isel(vsel_left), da.isel(vsel_left) ).fillna(0.) * section["Vmask"],
70
+ xr.where(~np.isnan(da.isel(vsel_left )), 0.5*da.isel(vsel_right), da.isel(vsel_right)).fillna(0.) * section["Vmask"],
71
+ ])
72
+ tracer = tracer.where(tracer!=0., np.nan)
73
+ tracer.name = da.name
74
+ tracer.attrs = da.attrs
75
+
76
+ return tracer