xbudget 0.5.0__tar.gz
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.
- xbudget-0.5.0/.github/workflows/publish-to-pypi.yml +26 -0
- xbudget-0.5.0/.gitignore +2 -0
- xbudget-0.5.0/LICENSE +21 -0
- xbudget-0.5.0/PKG-INFO +38 -0
- xbudget-0.5.0/README.md +21 -0
- xbudget-0.5.0/ci/environment.yml +10 -0
- xbudget-0.5.0/docs/environment.yml +13 -0
- xbudget-0.5.0/pyproject.toml +36 -0
- xbudget-0.5.0/xbudget/__init__.py +4 -0
- xbudget-0.5.0/xbudget/collect.py +381 -0
- xbudget-0.5.0/xbudget/conventions/MOM6.yaml +349 -0
- xbudget-0.5.0/xbudget/conventions/MOM6_3Donly.yaml +230 -0
- xbudget-0.5.0/xbudget/conventions/MOM6_drift.yaml +70 -0
- xbudget-0.5.0/xbudget/conventions/MOM6_surface.yaml +214 -0
- xbudget-0.5.0/xbudget/presets.py +42 -0
- xbudget-0.5.0/xbudget/version.py +3 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build-and-publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v2
|
|
12
|
+
- name: Set up Python
|
|
13
|
+
uses: actions/setup-python@v2
|
|
14
|
+
with:
|
|
15
|
+
python-version: '3.x'
|
|
16
|
+
- name: Install dependencies
|
|
17
|
+
run: |
|
|
18
|
+
python -m pip install --upgrade pip
|
|
19
|
+
pip install build twine
|
|
20
|
+
- name: Build package
|
|
21
|
+
run: python -m build
|
|
22
|
+
- name: Publish package
|
|
23
|
+
env:
|
|
24
|
+
TWINE_USERNAME: __token__
|
|
25
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
26
|
+
run: twine upload dist/*
|
xbudget-0.5.0/.gitignore
ADDED
xbudget-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Henri F. Drake
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
xbudget-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xbudget
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Helper functions and meta-data conventions for wrangling finite-volume ocean model budgets
|
|
5
|
+
Project-URL: Homepage, https://github.com/hdrake/xbudget
|
|
6
|
+
Project-URL: Bugs/Issues/Features, https://github.com/hdrake/xbudget/issues
|
|
7
|
+
Author-email: "Henri F. Drake" <hfdrake@uci.edu>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: xarray
|
|
15
|
+
Requires-Dist: xgcm>=0.9.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# xbudget
|
|
19
|
+
Helper functions and meta-data conventions for wrangling finite-volume ocean model budgets.
|
|
20
|
+
|
|
21
|
+
Quick Start Guide
|
|
22
|
+
-----------------
|
|
23
|
+
|
|
24
|
+
**For users: minimal installation within an existing environment**
|
|
25
|
+
```bash
|
|
26
|
+
pip install git+https://github.com/hdrake/xbudget.git@main
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**For developers: installing from scratch using `conda`**
|
|
30
|
+
```bash
|
|
31
|
+
git clone git@github.com:hdrake/xbudget.git
|
|
32
|
+
cd xbudget
|
|
33
|
+
conda env create -f docs/environment.yml
|
|
34
|
+
conda activate docs_env_xbudget
|
|
35
|
+
pip install -e .
|
|
36
|
+
python -m ipykernel install --user --name docs_env_xbudget --display-name "docs_env_xbudget"
|
|
37
|
+
jupyter-lab
|
|
38
|
+
```
|
xbudget-0.5.0/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# xbudget
|
|
2
|
+
Helper functions and meta-data conventions for wrangling finite-volume ocean model budgets.
|
|
3
|
+
|
|
4
|
+
Quick Start Guide
|
|
5
|
+
-----------------
|
|
6
|
+
|
|
7
|
+
**For users: minimal installation within an existing environment**
|
|
8
|
+
```bash
|
|
9
|
+
pip install git+https://github.com/hdrake/xbudget.git@main
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**For developers: installing from scratch using `conda`**
|
|
13
|
+
```bash
|
|
14
|
+
git clone git@github.com:hdrake/xbudget.git
|
|
15
|
+
cd xbudget
|
|
16
|
+
conda env create -f docs/environment.yml
|
|
17
|
+
conda activate docs_env_xbudget
|
|
18
|
+
pip install -e .
|
|
19
|
+
python -m ipykernel install --user --name docs_env_xbudget --display-name "docs_env_xbudget"
|
|
20
|
+
jupyter-lab
|
|
21
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "xbudget"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
authors = [
|
|
5
|
+
{name="Henri F. Drake", email="hfdrake@uci.edu"},
|
|
6
|
+
]
|
|
7
|
+
description = "Helper functions and meta-data conventions for wrangling finite-volume ocean model budgets"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy",
|
|
17
|
+
"xarray",
|
|
18
|
+
"xgcm >= 0.9.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
"Homepage" = "https://github.com/hdrake/xbudget"
|
|
23
|
+
"Bugs/Issues/Features" = "https://github.com/hdrake/xbudget/issues"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["hatchling"]
|
|
27
|
+
build-backend = "hatchling.build"
|
|
28
|
+
|
|
29
|
+
[tool.hatch.metadata]
|
|
30
|
+
allow-direct-references = true
|
|
31
|
+
|
|
32
|
+
[tool.hatch.version]
|
|
33
|
+
path = "xbudget/version.py"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build]
|
|
36
|
+
exclude = ["examples/**", "data/**"]
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
from operator import mul
|
|
2
|
+
from functools import reduce
|
|
3
|
+
import copy
|
|
4
|
+
import numpy as np
|
|
5
|
+
import numbers
|
|
6
|
+
import xarray as xr
|
|
7
|
+
import xgcm
|
|
8
|
+
|
|
9
|
+
import warnings
|
|
10
|
+
|
|
11
|
+
def aggregate(xbudget_dict, decompose=[]):
|
|
12
|
+
"""Aggregate xbudget dictionary into simpler root-level budgets.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
xbudget_dict : dictionary in xbudget-compatible format
|
|
17
|
+
decompose : str or list (default: [])
|
|
18
|
+
Name of variable type(s) to decompose into the summed parts
|
|
19
|
+
|
|
20
|
+
Examples
|
|
21
|
+
--------
|
|
22
|
+
>>> xbudget_dict = {
|
|
23
|
+
"heat": {
|
|
24
|
+
"rhs": {
|
|
25
|
+
"sum": {
|
|
26
|
+
"advection": {
|
|
27
|
+
"var":"advective_tendency"
|
|
28
|
+
},
|
|
29
|
+
"var": "heat_rhs_sum"
|
|
30
|
+
},
|
|
31
|
+
"var": "heat_rhs",
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
>>> xbudget.aggregate(xbudget_dict)
|
|
36
|
+
{'heat': {'rhs': {'advection': 'advective_tendency'}}}
|
|
37
|
+
|
|
38
|
+
>>>xbudget_dict = {
|
|
39
|
+
"heat": {
|
|
40
|
+
"rhs": {
|
|
41
|
+
"sum": {
|
|
42
|
+
"advection": {
|
|
43
|
+
"var":"advective_tendency",
|
|
44
|
+
"sum": {
|
|
45
|
+
"horizontal": {
|
|
46
|
+
"var":"advective_tendency_h",
|
|
47
|
+
},
|
|
48
|
+
"vertical": {
|
|
49
|
+
"var":"advective_tendency_v"
|
|
50
|
+
},
|
|
51
|
+
"var":"heat_rhs_sum_advection_sum"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"var": "heat_rhs_sum"
|
|
55
|
+
},
|
|
56
|
+
"var": "heat_rhs",
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
>>> xbudget.aggregate(xbudget_dict)
|
|
61
|
+
{'heat': {'rhs': {'advection': 'advective_tendency'}}}
|
|
62
|
+
|
|
63
|
+
>>> xbudget.aggregate(xbudget_dict, decompose="advection")
|
|
64
|
+
{'heat': {'rhs': {'advection_horizontal': 'advective_tendency_h',
|
|
65
|
+
'advection_vertical': 'advective_tendency_v'}}}
|
|
66
|
+
|
|
67
|
+
See also
|
|
68
|
+
--------
|
|
69
|
+
disaggregate, deep_search, _deep_search
|
|
70
|
+
"""
|
|
71
|
+
new_budgets = copy.deepcopy(xbudget_dict)
|
|
72
|
+
for tr, tr_xbudget_dict in xbudget_dict.items():
|
|
73
|
+
for side,terms in tr_xbudget_dict.items():
|
|
74
|
+
if side in ["lhs", "rhs"]:
|
|
75
|
+
new_budgets[tr][side] = deep_search(
|
|
76
|
+
disaggregate(tr_xbudget_dict[side], decompose=decompose)
|
|
77
|
+
)
|
|
78
|
+
return new_budgets
|
|
79
|
+
|
|
80
|
+
def disaggregate(b, decompose=[]):
|
|
81
|
+
"""Disaggregate variable's provenance dictionary into summed parts
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
b : xbudget sub-dictionary for a variable
|
|
86
|
+
decompose : str or list (default: [])
|
|
87
|
+
Name of variable type(s) to decompose into the summed parts
|
|
88
|
+
|
|
89
|
+
Examples
|
|
90
|
+
--------
|
|
91
|
+
>>> b = {
|
|
92
|
+
"sum": {
|
|
93
|
+
"advection": {
|
|
94
|
+
"var":"advective_tendency",
|
|
95
|
+
"sum": {
|
|
96
|
+
"horizontal": {
|
|
97
|
+
"var":"advective_tendency_h",
|
|
98
|
+
},
|
|
99
|
+
"vertical": {
|
|
100
|
+
"var":"advective_tendency_v"
|
|
101
|
+
},
|
|
102
|
+
"var":"heat_rhs_sum_advection_sum"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"var": "heat_rhs_sum"
|
|
106
|
+
},
|
|
107
|
+
"var": "heat_rhs",
|
|
108
|
+
}
|
|
109
|
+
>>> {'advection': 'advective_tendency'}
|
|
110
|
+
{'advection': 'advective_tendency'}
|
|
111
|
+
|
|
112
|
+
>>> xbudget.disaggregate(b, decompose="advection")
|
|
113
|
+
{'advection': {'horizontal': 'advective_tendency_h',
|
|
114
|
+
'vertical': 'advective_tendency_v'}}
|
|
115
|
+
|
|
116
|
+
See also
|
|
117
|
+
--------
|
|
118
|
+
aggregate
|
|
119
|
+
"""
|
|
120
|
+
if "sum" in b:
|
|
121
|
+
bsum_novar = {k:v for (k,v) in b["sum"].items() if (k!="var") and (v is not None)}
|
|
122
|
+
sum_dict = dict((k,v["var"]) if ("var" in v) else (k,v) for k,v in bsum_novar.items())
|
|
123
|
+
b_recurse = {}
|
|
124
|
+
for (k,v) in sum_dict.items():
|
|
125
|
+
if k not in decompose:
|
|
126
|
+
b_recurse[k] = v
|
|
127
|
+
else:
|
|
128
|
+
v_dict = disaggregate(b["sum"][k], decompose=decompose)
|
|
129
|
+
if "product" in v_dict.keys():
|
|
130
|
+
b_recurse[k] = v_dict["var"]
|
|
131
|
+
else:
|
|
132
|
+
b_recurse[k] = v_dict
|
|
133
|
+
return b_recurse
|
|
134
|
+
return b
|
|
135
|
+
|
|
136
|
+
def deep_search(b):
|
|
137
|
+
"""Utility function for searching for variables in xbudget dictionary.
|
|
138
|
+
|
|
139
|
+
See also
|
|
140
|
+
--------
|
|
141
|
+
aggregate, _deep_search
|
|
142
|
+
"""
|
|
143
|
+
return _deep_search(b, new_b={}, k_last=None)
|
|
144
|
+
|
|
145
|
+
def _deep_search(b, new_b={}, k_last=None):
|
|
146
|
+
"""Recursive function for searching for variables in xbudget dictionary.
|
|
147
|
+
|
|
148
|
+
See also
|
|
149
|
+
--------
|
|
150
|
+
aggregate, deep_search
|
|
151
|
+
"""
|
|
152
|
+
if type(b) is str:
|
|
153
|
+
new_b[k_last] = b
|
|
154
|
+
elif type(b) is dict:
|
|
155
|
+
for (k, v) in b.items():
|
|
156
|
+
if k_last is not None:
|
|
157
|
+
k = f"{k_last}_{k}"
|
|
158
|
+
_deep_search(v, new_b=new_b, k_last=k)
|
|
159
|
+
return new_b
|
|
160
|
+
|
|
161
|
+
def collect_budgets(ds, xbudget_dict):
|
|
162
|
+
"""Fills xbudget dictionary with all tracer content tendencies
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
ds : xr.Dataset containing budget diagnostics
|
|
167
|
+
xbudget_dict : dictionary in xbudget-compatible format
|
|
168
|
+
Example format:
|
|
169
|
+
>>> xbudget_dict = {
|
|
170
|
+
"heat": {
|
|
171
|
+
"rhs": {
|
|
172
|
+
"sum": {
|
|
173
|
+
"advection": {
|
|
174
|
+
"var":"advective_tendency"
|
|
175
|
+
},
|
|
176
|
+
"var": "heat_rhs_sum"
|
|
177
|
+
},
|
|
178
|
+
"var": "heat_rhs",
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
"""
|
|
183
|
+
for eq, v in xbudget_dict.items():
|
|
184
|
+
for side in ["lhs", "rhs"]:
|
|
185
|
+
if side in v:
|
|
186
|
+
budget_fill_dict(ds, v[side], f"{eq}_{side}")
|
|
187
|
+
|
|
188
|
+
def budget_fill_dict(data, xbudget_dict, namepath):
|
|
189
|
+
"""Recursively fill xbudget dictionary
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
data : xgcm.grid or xr.Dataset
|
|
194
|
+
xbudget_dict : dictionary in xbudget-compatible format containing variable in namepath
|
|
195
|
+
namepath : name of variable in dataset (data._ds or data)
|
|
196
|
+
"""
|
|
197
|
+
if type(data)==xgcm.grid.Grid:
|
|
198
|
+
grid = data
|
|
199
|
+
ds = grid._ds
|
|
200
|
+
else:
|
|
201
|
+
ds = data
|
|
202
|
+
grid = None
|
|
203
|
+
|
|
204
|
+
var_pref = None
|
|
205
|
+
|
|
206
|
+
if ((xbudget_dict["var"] is not None) and
|
|
207
|
+
(xbudget_dict["var"] in ds) and
|
|
208
|
+
(namepath not in ds)):
|
|
209
|
+
var_rename = ds[xbudget_dict["var"]].rename(namepath)
|
|
210
|
+
var_rename.attrs['provenance'] = xbudget_dict["var"]
|
|
211
|
+
ds[namepath] = ds[xbudget_dict["var"]]
|
|
212
|
+
var_pref = ds[namepath]
|
|
213
|
+
|
|
214
|
+
for k,v in xbudget_dict.items():
|
|
215
|
+
if k in ['sum', 'product']:
|
|
216
|
+
op_list = []
|
|
217
|
+
for k_term, v_term in v.items():
|
|
218
|
+
if isinstance(v_term, dict): # recursive call to get this variable
|
|
219
|
+
v_term_recursive = budget_fill_dict(data, v_term, f"{namepath}_{k}_{k_term}")
|
|
220
|
+
if v_term_recursive is not None:
|
|
221
|
+
op_list.append(v_term_recursive)
|
|
222
|
+
elif isinstance(v_term, numbers.Number):
|
|
223
|
+
op_list.append(v_term)
|
|
224
|
+
elif isinstance(v_term, str):
|
|
225
|
+
if v_term in ds:
|
|
226
|
+
op_list.append(ds[v_term])
|
|
227
|
+
else:
|
|
228
|
+
warnings.warn(f"Variable {v_term} is missing from the dataset `ds`, so it is being skipped. To suppress this warning, remove {v_term} from the `xbudget_dict`.")
|
|
229
|
+
if k=="product":
|
|
230
|
+
op_list.append(0.)
|
|
231
|
+
|
|
232
|
+
# Compute variable from sum or product operation
|
|
233
|
+
if (
|
|
234
|
+
(len(op_list) == 0) |
|
|
235
|
+
all([e is None for e in op_list]) |
|
|
236
|
+
any([e is None for e in op_list])
|
|
237
|
+
):
|
|
238
|
+
return None
|
|
239
|
+
else:
|
|
240
|
+
var = sum(op_list) if k=="sum" else reduce(mul, op_list, 1)
|
|
241
|
+
if not isinstance(var, xr.DataArray):
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
# Variable metadata
|
|
245
|
+
var_name = f"{namepath}_{k}"
|
|
246
|
+
var = var.rename(var_name)
|
|
247
|
+
var_provenance = [o.name if isinstance(o, xr.DataArray) else o for o in op_list]
|
|
248
|
+
var.attrs["provenance"] = var_provenance
|
|
249
|
+
ds[var_name] = var
|
|
250
|
+
if (xbudget_dict[k]["var"] is None):
|
|
251
|
+
xbudget_dict[k]["var"] = var_name
|
|
252
|
+
|
|
253
|
+
if (xbudget_dict["var"] is None):
|
|
254
|
+
var_copy = var.copy()
|
|
255
|
+
var_copy.attrs["provenance"] = var_name
|
|
256
|
+
xbudget_dict["var"] = namepath
|
|
257
|
+
if namepath not in ds:
|
|
258
|
+
ds[namepath] = var_copy
|
|
259
|
+
|
|
260
|
+
# keep record of the first-listed variable
|
|
261
|
+
if var_pref is None:
|
|
262
|
+
var_pref = var.copy()
|
|
263
|
+
|
|
264
|
+
if k == "difference":
|
|
265
|
+
if grid is not None:
|
|
266
|
+
staggered_axes = {
|
|
267
|
+
axn:c for axn,ax in grid.axes.items()
|
|
268
|
+
for pos,c in ax.coords.items()
|
|
269
|
+
if pos!="center"
|
|
270
|
+
}
|
|
271
|
+
v_term = [v_term for k_term,v_term in v.items() if k_term!="var"][0]
|
|
272
|
+
if v_term not in ds:
|
|
273
|
+
warnings.warn(f"Variable {v_term} is missing from the dataset `ds`, so it is being skipped. To suppress this warning, remove {v_term} from the `xbudget_dict`.")
|
|
274
|
+
continue
|
|
275
|
+
candidate_axes = [axn for (axn,c) in staggered_axes.items() if c in ds[v_term].dims]
|
|
276
|
+
if len(candidate_axes) == 1:
|
|
277
|
+
axis = candidate_axes[0]
|
|
278
|
+
else:
|
|
279
|
+
raise ValueError("Flux difference inconsistent with finite volume discretization.")
|
|
280
|
+
var = grid.diff(ds[v_term].fillna(0.), axis)
|
|
281
|
+
var_name = f"{namepath}_difference"
|
|
282
|
+
var = var.rename(var_name)
|
|
283
|
+
var_provenance = v_term
|
|
284
|
+
var.attrs["provenance"] = var_provenance
|
|
285
|
+
ds[var_name] = var
|
|
286
|
+
if var_pref is None:
|
|
287
|
+
var_pref = var.copy()
|
|
288
|
+
else:
|
|
289
|
+
raise ValueError("Input `ds` must be `xgcm.Grid` instance if using `difference` operations.")
|
|
290
|
+
|
|
291
|
+
return var_pref
|
|
292
|
+
|
|
293
|
+
def get_vars(xbudget_dict, terms):
|
|
294
|
+
"""Get xbudget sub-dictionaries for specified terms.
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
xbudget_dict : dictionary in xbudget-compatible format
|
|
299
|
+
terms : str or list of str
|
|
300
|
+
|
|
301
|
+
Examples
|
|
302
|
+
-------
|
|
303
|
+
>>> xbudget_dict = {
|
|
304
|
+
"heat": {
|
|
305
|
+
"rhs": {
|
|
306
|
+
"sum": {
|
|
307
|
+
"advection": {
|
|
308
|
+
"var":"advective_tendency"
|
|
309
|
+
},
|
|
310
|
+
"var": "heat_rhs_sum"
|
|
311
|
+
},
|
|
312
|
+
"var": "heat_rhs",
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
>>> xbudget.get_vars(xbudget_dict, "heat_rhs_sum")
|
|
317
|
+
{'var': 'heat_rhs_sum', 'sum': ['advective_tendency']}
|
|
318
|
+
"""
|
|
319
|
+
return _get_vars(xbudget_dict, terms)
|
|
320
|
+
|
|
321
|
+
def _get_vars(b, terms, k_long=""):
|
|
322
|
+
"""Recursive version of _get_vars for determining variable provenance tree.
|
|
323
|
+
|
|
324
|
+
Parameters
|
|
325
|
+
----------
|
|
326
|
+
b : dictionary
|
|
327
|
+
terms : str or list of str
|
|
328
|
+
k_long : variable name suffix
|
|
329
|
+
|
|
330
|
+
See also
|
|
331
|
+
--------
|
|
332
|
+
get_vars
|
|
333
|
+
"""
|
|
334
|
+
if isinstance(terms, (list, np.ndarray)):
|
|
335
|
+
return [_get_vars(b, term) for term in terms]
|
|
336
|
+
elif type(terms) is str:
|
|
337
|
+
for k,v in b.items():
|
|
338
|
+
if type(v) is str:
|
|
339
|
+
k_short = k_long.replace("_sum", "").replace("_product", "")
|
|
340
|
+
if v==terms:
|
|
341
|
+
decomps = {"var": v}
|
|
342
|
+
if len(terms) > len("_sum"):
|
|
343
|
+
if (terms[-len("_sum"):] == "_sum") and ("sum" in b):
|
|
344
|
+
ts = {kk:vv for (kk,vv) in b["sum"].items() if kk!="var"}
|
|
345
|
+
decomps["sum"] = [vv["var"] if type(vv) is dict else vv for (kk,vv) in ts.items()]
|
|
346
|
+
elif (terms[-len("_sum"):] == "_sum"):
|
|
347
|
+
ts = {kk:vv for (kk,vv) in b.items() if kk!="var"}
|
|
348
|
+
decomps["sum"] = [vv["var"] if type(vv) is dict else vv for (kk,vv) in ts.items()]
|
|
349
|
+
if len(terms) > len("_product"):
|
|
350
|
+
if (terms[-len("_product"):] == "_product") and ("product" in b):
|
|
351
|
+
ts = {kk:vv for (kk,vv) in b["product"].items() if kk!="var"}
|
|
352
|
+
decomps["product"] = [vv["var"] if type(vv) is dict else vv for (kk,vv) in ts.items()]
|
|
353
|
+
elif (terms[-len("_product"):] == "_product"):
|
|
354
|
+
ts = {kk:vv for (kk,vv) in b.items() if kk!="var"}
|
|
355
|
+
decomps["product"] = [vv["var"] if type(vv) is dict else vv for (kk,vv) in ts.items()]
|
|
356
|
+
return decomps
|
|
357
|
+
|
|
358
|
+
if k!="var":
|
|
359
|
+
k_short+="_"+k
|
|
360
|
+
if k_short==terms:
|
|
361
|
+
return v
|
|
362
|
+
elif type(v) is dict:
|
|
363
|
+
if k_long=="":
|
|
364
|
+
new_k = k
|
|
365
|
+
elif len(k_long)>0:
|
|
366
|
+
new_k = f"{k_long}_{k}"
|
|
367
|
+
var = _get_vars(v, terms, k_long=new_k)
|
|
368
|
+
if var is not None:
|
|
369
|
+
return var
|
|
370
|
+
|
|
371
|
+
def flatten(container):
|
|
372
|
+
for i in container:
|
|
373
|
+
if isinstance(i, (list,tuple)):
|
|
374
|
+
for j in flatten(i):
|
|
375
|
+
yield j
|
|
376
|
+
else:
|
|
377
|
+
yield i
|
|
378
|
+
|
|
379
|
+
def flatten_lol(lol):
|
|
380
|
+
"""Flatten a list of lists into a single list."""
|
|
381
|
+
return list(flatten(lol))
|