TRACK-pylib 0.1.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.
- pyTRACK/__init__.py +4 -0
- pyTRACK/_lib/libtrack.so +0 -0
- pyTRACK/data/CMAP.1074 +46 -0
- pyTRACK/data/CMAP.dat.claire +16 -0
- pyTRACK/data/CMAP.dat.john +13458 -0
- pyTRACK/data/CMAP.dat.mask +3129 -0
- pyTRACK/data/CMAP.dat.mine +3001 -0
- pyTRACK/data/CMAP.dat.orig +3001 -0
- pyTRACK/data/adapt.dat +2 -0
- pyTRACK/data/constraints.dat +38 -0
- pyTRACK/data/constraints.dat.proto +38 -0
- pyTRACK/data/constraints.dat.reg +38 -0
- pyTRACK/data/constraints.dat.sphery +45 -0
- pyTRACK/data/gridT63.nc +0 -0
- pyTRACK/data/initial.T42_NH +19 -0
- pyTRACK/data/initial.T42_SH +19 -0
- pyTRACK/data/initial.T63_NH +19 -0
- pyTRACK/data/initial.T63_SH +19 -0
- pyTRACK/data/initial.st +16 -0
- pyTRACK/data/zone.dat +4 -0
- pyTRACK/data/zone.dat.new +10 -0
- pyTRACK/indat/RSPLICE.in +22 -0
- pyTRACK/indat/RSPLICE_GFS.in +20 -0
- pyTRACK/indat/RUNDATIN.GFS_MSLP.in +53 -0
- pyTRACK/indat/RUNDATIN.GFS_MSLP_A.in +54 -0
- pyTRACK/indat/RUNDATIN.GFS_VOR.in +53 -0
- pyTRACK/indat/RUNDATIN.GFS_VOR_A.in +54 -0
- pyTRACK/indat/RUNDATIN.era_MSLP.in +49 -0
- pyTRACK/indat/RUNDATIN.era_MSLP_A.in +50 -0
- pyTRACK/indat/RUNDATIN.era_VOR.in +50 -0
- pyTRACK/indat/RUNDATIN.era_VOR_A.in +51 -0
- pyTRACK/indat/SPACE_FILTER.in +38 -0
- pyTRACK/indat/STATS.era2_NH.in +110 -0
- pyTRACK/indat/STATS.era2_NH_wght.in +147 -0
- pyTRACK/indat/STATS.era2_SH.in +110 -0
- pyTRACK/indat/STATS.era2_SH_wght.in +143 -0
- pyTRACK/indat/calcvor_onelev.in +34 -0
- pyTRACK/indat/specfilt.in +31 -0
- pyTRACK/track.py +70 -0
- pyTRACK/track_master.py +245 -0
- pyTRACK/utils.py +425 -0
- track_pylib-0.1.0.dist-info/METADATA +28 -0
- track_pylib-0.1.0.dist-info/RECORD +45 -0
- track_pylib-0.1.0.dist-info/WHEEL +5 -0
- track_pylib-0.1.0.dist-info/top_level.txt +1 -0
pyTRACK/track_master.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python reimplementation of the TRACK DJF workflow originally written in csh.
|
|
3
|
+
|
|
4
|
+
Design principles:
|
|
5
|
+
- Never change working directory
|
|
6
|
+
- TRACK always writes to current working directory
|
|
7
|
+
- Python immediately moves outputs to structured folders
|
|
8
|
+
- All paths are explicit and configurable
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import shutil
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------
|
|
17
|
+
# USER CONFIGURATION
|
|
18
|
+
# ---------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
EXT = "year"
|
|
21
|
+
|
|
22
|
+
# You will set these paths explicitly
|
|
23
|
+
SRCDIR = Path.cwd()
|
|
24
|
+
INDAT = SRCDIR / "indat"
|
|
25
|
+
OUTDAT = SRCDIR / "outdat"
|
|
26
|
+
OUTPUT = SRCDIR / "outputd" / "ioputd"
|
|
27
|
+
|
|
28
|
+
RUNDT = "RUNDATIN"
|
|
29
|
+
RUNOUT = "RUNDATOUT"
|
|
30
|
+
|
|
31
|
+
INPUT_FILE = "input.nc"
|
|
32
|
+
INITIAL = "!INITIAL!"
|
|
33
|
+
|
|
34
|
+
# Loop control (matches csh defaults)
|
|
35
|
+
NN = 1
|
|
36
|
+
EE = 12
|
|
37
|
+
|
|
38
|
+
ST = 1
|
|
39
|
+
FN = 21
|
|
40
|
+
BACK = 2
|
|
41
|
+
TERMFR = -1
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------
|
|
45
|
+
# TRACK WRAPPER (UNCHANGED LOGIC, CWD-BASED)
|
|
46
|
+
# ---------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def track(input_file="input.nc", namelist=None):
|
|
49
|
+
import os
|
|
50
|
+
import ctypes
|
|
51
|
+
|
|
52
|
+
_LIB = Path(__file__).parent / "_lib" / "libtrack.so"
|
|
53
|
+
if not _LIB.exists():
|
|
54
|
+
raise FileNotFoundError(f"libtrack.so not found at {_LIB}")
|
|
55
|
+
|
|
56
|
+
lib = ctypes.CDLL(str(_LIB))
|
|
57
|
+
|
|
58
|
+
lib.track_main.argtypes = [
|
|
59
|
+
ctypes.c_int,
|
|
60
|
+
ctypes.POINTER(ctypes.c_char_p)
|
|
61
|
+
]
|
|
62
|
+
lib.track_main.restype = ctypes.c_int
|
|
63
|
+
|
|
64
|
+
args = [
|
|
65
|
+
b"track",
|
|
66
|
+
b"-d", str(Path.cwd()).encode(),
|
|
67
|
+
b"-i", input_file.encode(),
|
|
68
|
+
b"-f", EXT.encode(),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
argc = len(args)
|
|
72
|
+
argv = (ctypes.c_char_p * argc)(*args)
|
|
73
|
+
|
|
74
|
+
if namelist is not None:
|
|
75
|
+
old_stdin_fd = os.dup(0)
|
|
76
|
+
try:
|
|
77
|
+
with open(namelist, "rb") as f:
|
|
78
|
+
os.dup2(f.fileno(), 0)
|
|
79
|
+
lib.track_main(argc, argv)
|
|
80
|
+
finally:
|
|
81
|
+
os.dup2(old_stdin_fd, 0)
|
|
82
|
+
os.close(old_stdin_fd)
|
|
83
|
+
else:
|
|
84
|
+
lib.track_main(argc, argv)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------
|
|
88
|
+
# UTILITIES
|
|
89
|
+
# ---------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
TRACK_OUTPUTS = [
|
|
92
|
+
"objout.new",
|
|
93
|
+
"tdump",
|
|
94
|
+
"idump",
|
|
95
|
+
"objout",
|
|
96
|
+
"throut",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def render_input(text, S, F, initial, i_flag="i", drop_n=False):
|
|
101
|
+
text = re.sub(r"^[0-9]*#", str(S), text, flags=re.M)
|
|
102
|
+
text = re.sub(r"^[0-9]*!", str(F), text, flags=re.M)
|
|
103
|
+
text = text.replace("%INITIAL%", initial)
|
|
104
|
+
|
|
105
|
+
if i_flag == "i":
|
|
106
|
+
text = re.sub(r"^i%", "i", text, flags=re.M)
|
|
107
|
+
else:
|
|
108
|
+
text = re.sub(r"^i%", "y", text, flags=re.M)
|
|
109
|
+
|
|
110
|
+
if drop_n:
|
|
111
|
+
text = re.sub(r"^n~.*$", "", text, flags=re.M)
|
|
112
|
+
else:
|
|
113
|
+
text = re.sub(r"^n~", "n", text, flags=re.M)
|
|
114
|
+
|
|
115
|
+
return text
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def run_track(namelist_text, workdir):
|
|
119
|
+
workdir.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
|
|
121
|
+
namelist = workdir / "namelist.in"
|
|
122
|
+
namelist.write_text(namelist_text)
|
|
123
|
+
|
|
124
|
+
track(input_file=INPUT_FILE, namelist=str(namelist))
|
|
125
|
+
|
|
126
|
+
for name in TRACK_OUTPUTS:
|
|
127
|
+
p = Path(f"{name}.{EXT}")
|
|
128
|
+
if p.exists():
|
|
129
|
+
shutil.move(p, workdir / name)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ---------------------------------------------------------------------
|
|
133
|
+
# MAIN WORKFLOW
|
|
134
|
+
# ---------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
def main():
|
|
137
|
+
OUTPUT.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
|
|
139
|
+
template = (INDAT / f"{RUNDT}.in").read_text()
|
|
140
|
+
template_a = (INDAT / f"{RUNDT}_A.in").read_text()
|
|
141
|
+
|
|
142
|
+
splice_max = []
|
|
143
|
+
splice_min = []
|
|
144
|
+
|
|
145
|
+
S = ST
|
|
146
|
+
F = FN
|
|
147
|
+
I = F - S
|
|
148
|
+
|
|
149
|
+
N = NN
|
|
150
|
+
E = EE
|
|
151
|
+
|
|
152
|
+
while N <= E:
|
|
153
|
+
# ---------- MAX ----------
|
|
154
|
+
max_dir = OUTPUT / f"DJF_MAX_{N}"
|
|
155
|
+
|
|
156
|
+
max_input = render_input(
|
|
157
|
+
template,
|
|
158
|
+
S,
|
|
159
|
+
F,
|
|
160
|
+
INITIAL,
|
|
161
|
+
i_flag="i" if N == 1 else "y",
|
|
162
|
+
drop_n=(N != 1),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
run_track(max_input, max_dir)
|
|
166
|
+
|
|
167
|
+
splice_max.append((max_dir / "objout.new", max_dir / "tdump", 1 if N == 1 else 3))
|
|
168
|
+
|
|
169
|
+
# ---------- MIN ----------
|
|
170
|
+
min_dir = OUTPUT / f"DJF_MIN_{N}"
|
|
171
|
+
|
|
172
|
+
min_input = render_input(
|
|
173
|
+
template_a,
|
|
174
|
+
S,
|
|
175
|
+
F,
|
|
176
|
+
INITIAL,
|
|
177
|
+
i_flag="i",
|
|
178
|
+
drop_n=False,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
run_track(min_input, min_dir)
|
|
182
|
+
|
|
183
|
+
splice_min.append((min_dir / "objout.new", min_dir / "tdump", 1 if N == 1 else 3))
|
|
184
|
+
|
|
185
|
+
# ---------- LOOP CONTROL ----------
|
|
186
|
+
N += 1
|
|
187
|
+
S = F - BACK
|
|
188
|
+
|
|
189
|
+
if N < E:
|
|
190
|
+
F += I
|
|
191
|
+
else:
|
|
192
|
+
F += I + 15
|
|
193
|
+
|
|
194
|
+
if TERMFR > 0 and F > TERMFR:
|
|
195
|
+
F = TERMFR
|
|
196
|
+
E = N
|
|
197
|
+
break
|
|
198
|
+
|
|
199
|
+
# -----------------------------------------------------------------
|
|
200
|
+
# SPLICING
|
|
201
|
+
# -----------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
def write_splice(entries, path):
|
|
204
|
+
with open(path, "w") as f:
|
|
205
|
+
for o, t, flag in entries:
|
|
206
|
+
f.write(f"{o}\n{t}\n{flag}\n")
|
|
207
|
+
|
|
208
|
+
rs_template = (INDAT / "RSPLICE.in").read_text()
|
|
209
|
+
|
|
210
|
+
for label, entries in [("pos", splice_max), ("neg", splice_min)]:
|
|
211
|
+
splice_file = OUTDAT / f"splice_{label}.{EXT}"
|
|
212
|
+
write_splice(entries, splice_file)
|
|
213
|
+
|
|
214
|
+
text = rs_template
|
|
215
|
+
text = re.sub(r"^[0-9]*!", str(len(entries)), text, flags=re.M)
|
|
216
|
+
text = re.sub(r"^[0-9]*!", splice_file.read_text(), text, flags=re.M)
|
|
217
|
+
text = text.replace("initial", str(OUTPUT / "initial"))
|
|
218
|
+
|
|
219
|
+
rs_file = OUTDAT / f"RSPLICE_{label}.{EXT}"
|
|
220
|
+
rs_file.write_text(text)
|
|
221
|
+
|
|
222
|
+
track(input_file=INPUT_FILE, namelist=str(rs_file))
|
|
223
|
+
|
|
224
|
+
for name in ["tr_trs", "tr_grid", "ff_trs"]:
|
|
225
|
+
p = Path(f"{name}.{EXT}")
|
|
226
|
+
if p.exists():
|
|
227
|
+
shutil.move(p, OUTPUT / f"{name}_{label}")
|
|
228
|
+
|
|
229
|
+
# -----------------------------------------------------------------
|
|
230
|
+
# FINAL TIDY
|
|
231
|
+
# -----------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
for f in OUTDAT.glob(f"*.{EXT}"):
|
|
234
|
+
f.unlink(missing_ok=True)
|
|
235
|
+
|
|
236
|
+
for f in ["initial", "user_tavg", "user_tavg_var", "user_tavg_varfil"]:
|
|
237
|
+
p = OUTDAT / f"{f}.{EXT}"
|
|
238
|
+
if p.exists():
|
|
239
|
+
shutil.move(p, OUTPUT / f)
|
|
240
|
+
|
|
241
|
+
shutil.make_archive(str(OUTPUT), "gztar", root_dir=OUTPUT)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
if __name__ == "__main__":
|
|
245
|
+
main()
|
pyTRACK/utils.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from netCDF4 import Dataset
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from math import ceil
|
|
5
|
+
import subprocess
|
|
6
|
+
import xarray as xr
|
|
7
|
+
import shutil
|
|
8
|
+
from .track import track
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from cdo import *
|
|
12
|
+
cdo = Cdo()
|
|
13
|
+
except Exception as e:
|
|
14
|
+
cdo = None
|
|
15
|
+
print("WARNING: CDO not available — CDO-dependent functionality will be disabled.")
|
|
16
|
+
|
|
17
|
+
__all__ = ['track_uv', 'calc_vorticity']
|
|
18
|
+
|
|
19
|
+
class cmip6_indat(object):
|
|
20
|
+
"""Class to obtain basic information about the CMIP6 input data."""
|
|
21
|
+
def __init__(self, filename):
|
|
22
|
+
"""
|
|
23
|
+
Reads the netCDF file and scans its variables.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
filename : string
|
|
28
|
+
Filename of a .nc file containing CMIP6 sea level pressure or wind
|
|
29
|
+
velocity data.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
self.filename = filename
|
|
33
|
+
self.data = Dataset(filename, 'r')
|
|
34
|
+
self.vars = [var for var in self.data.variables]
|
|
35
|
+
|
|
36
|
+
def get_nx_ny(self):
|
|
37
|
+
# returns number of latitudes and longitudes in the grid
|
|
38
|
+
return str(len(self.data.variables['lon'][:])), \
|
|
39
|
+
str(len(self.data.variables['lat'][:]))
|
|
40
|
+
|
|
41
|
+
def get_grid_type(self):
|
|
42
|
+
# returns the grid type
|
|
43
|
+
return cdo.griddes(input=self.filename)[3]
|
|
44
|
+
|
|
45
|
+
def get_variable_type(self):
|
|
46
|
+
# returns the variable type
|
|
47
|
+
return self.vars[-1]
|
|
48
|
+
|
|
49
|
+
def get_timesteps(self):
|
|
50
|
+
# returns the number of timesteps
|
|
51
|
+
return int(len(self.data.variables['time'][:]))
|
|
52
|
+
|
|
53
|
+
class data_indat(object):
|
|
54
|
+
"""Class to obtain basic information about the CMIP6/ERA input data."""
|
|
55
|
+
def __init__(self, filename, data_type='cmip6'):
|
|
56
|
+
"""
|
|
57
|
+
Reads the netCDF file and scans its variables.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
filename : string
|
|
64
|
+
Filename of a .nc file containing CMIP6 sea level pressure or wind
|
|
65
|
+
velocity data.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
self.filename = filename
|
|
69
|
+
self.data_type = data_type
|
|
70
|
+
self.data = Dataset(filename, 'r')
|
|
71
|
+
self.vars = [var for var in self.data.variables]
|
|
72
|
+
|
|
73
|
+
def get_nx_ny(self):
|
|
74
|
+
# returns number of latitudes and longitudes in the grid
|
|
75
|
+
if self.data_type == 'era5':
|
|
76
|
+
return str(len(self.data.variables['longitude'][:])), \
|
|
77
|
+
str(len(self.data.variables['latitude'][:]))
|
|
78
|
+
elif self.data_type == 'cmip6':
|
|
79
|
+
return str(len(self.data.variables['lon'][:])), \
|
|
80
|
+
str(len(self.data.variables['lat'][:]))
|
|
81
|
+
|
|
82
|
+
def get_grid_type(self):
|
|
83
|
+
# returns the grid type
|
|
84
|
+
return cdo.griddes(input=self.filename)[3]
|
|
85
|
+
|
|
86
|
+
def get_variable_type(self):
|
|
87
|
+
# returns the variable type
|
|
88
|
+
return self.vars[-1]
|
|
89
|
+
|
|
90
|
+
def get_timesteps(self):
|
|
91
|
+
# returns the number of timesteps
|
|
92
|
+
return int(len(self.data.variables['time'][:]))
|
|
93
|
+
|
|
94
|
+
def has_equator(self):
|
|
95
|
+
# check if the data has an equator
|
|
96
|
+
if self.data_type == 'era5':
|
|
97
|
+
if 0 in self.data.variables['latitude'][:]:
|
|
98
|
+
return True
|
|
99
|
+
else:
|
|
100
|
+
return False
|
|
101
|
+
elif self.data_type == 'cmip6':
|
|
102
|
+
if 0 in self.data.variables['lat'][:]:
|
|
103
|
+
return True
|
|
104
|
+
else:
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
def has_nh_pole(self):
|
|
108
|
+
# check if the data has an NH
|
|
109
|
+
if self.data_type == 'era5':
|
|
110
|
+
if 90 in self.data.variables['latitude'][:]:
|
|
111
|
+
return True
|
|
112
|
+
else:
|
|
113
|
+
return False
|
|
114
|
+
elif self.data_type == 'cmip6':
|
|
115
|
+
if 90 in self.data.variables['lat'][:]:
|
|
116
|
+
return True
|
|
117
|
+
else:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
def has_sh_pole(self):
|
|
121
|
+
# check if the data has an NH
|
|
122
|
+
if self.data_type == 'era5':
|
|
123
|
+
if -90 in self.data.variables['latitude'][:]:
|
|
124
|
+
return True
|
|
125
|
+
else:
|
|
126
|
+
return False
|
|
127
|
+
elif self.data_type == 'cmip6':
|
|
128
|
+
if -90 in self.data.variables['lat'][:]:
|
|
129
|
+
return True
|
|
130
|
+
else:
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def merge_uv(file1, file2, outfile,uname,vname):
|
|
134
|
+
"""
|
|
135
|
+
Merge U and V files into a UV file.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
|
|
140
|
+
file1 : string
|
|
141
|
+
Path to .nc file containing either U or V data
|
|
142
|
+
|
|
143
|
+
file2 : string
|
|
144
|
+
Path to .nc file containing either V or U data, opposite of file1
|
|
145
|
+
|
|
146
|
+
outfile : string
|
|
147
|
+
Path of desired output file
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
data1 = cmip6_indat(file1)
|
|
152
|
+
data2 = cmip6_indat(file2)
|
|
153
|
+
|
|
154
|
+
if data1.get_variable_type() == uname:
|
|
155
|
+
u_file = file1
|
|
156
|
+
v_file = file2
|
|
157
|
+
|
|
158
|
+
elif data1.get_variable_type() == vname:
|
|
159
|
+
u_file = file2
|
|
160
|
+
v_file = file1
|
|
161
|
+
|
|
162
|
+
else:
|
|
163
|
+
raise Exception("Invalid input variable type. Please input ERA5 \
|
|
164
|
+
u or v file.")
|
|
165
|
+
|
|
166
|
+
dir_path = os.path.dirname(file1)
|
|
167
|
+
|
|
168
|
+
outfile = os.path.join(dir_path, os.path.basename(outfile))
|
|
169
|
+
|
|
170
|
+
print("Merging u&v files")
|
|
171
|
+
cdo.merge(input=" ".join((u_file, v_file)), output=outfile)
|
|
172
|
+
print("Merged U and V files into UV file named: ", outfile)
|
|
173
|
+
|
|
174
|
+
return outfile
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def regrid_cmip6(input, outfile):
|
|
178
|
+
"""
|
|
179
|
+
Detect grid of input data and regrid to gaussian grid if necessary.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
|
|
184
|
+
input : string
|
|
185
|
+
Path to .nc file containing input data
|
|
186
|
+
|
|
187
|
+
outfile : string
|
|
188
|
+
Desired path of regridded file
|
|
189
|
+
|
|
190
|
+
"""
|
|
191
|
+
data = cmip6_indat(input)
|
|
192
|
+
|
|
193
|
+
gridtype = data.get_grid_type()
|
|
194
|
+
|
|
195
|
+
# check if regridding is needed, do nothing if already gaussian
|
|
196
|
+
if gridtype == 'gridtype = gaussian':
|
|
197
|
+
print("No regridding needed.")
|
|
198
|
+
|
|
199
|
+
# check for resolution and regrid
|
|
200
|
+
else:
|
|
201
|
+
nx, ny = data.get_nx_ny()
|
|
202
|
+
if int(ny) <= 80:
|
|
203
|
+
cdo.remapcon("n32", input=input, output=outfile)
|
|
204
|
+
grid = 'n32'
|
|
205
|
+
elif int(ny) <= 112:
|
|
206
|
+
cdo.remapcon("n48", input=input, output=outfile)
|
|
207
|
+
grid = 'n48'
|
|
208
|
+
elif int(ny) <= 150:
|
|
209
|
+
cdo.remapcon("n64", input=input, output=outfile)
|
|
210
|
+
grid = 'n64'
|
|
211
|
+
else:
|
|
212
|
+
cdo.remapcon("n80", input=input, output=outfile)
|
|
213
|
+
grid = 'n80'
|
|
214
|
+
print("Regridded to " + grid + " Gaussian grid.")
|
|
215
|
+
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
def calc_vorticity(uv_file, outfile='vorticity_out.dat'):
|
|
219
|
+
|
|
220
|
+
# gather information about data
|
|
221
|
+
year = cdo.showyear(input=uv_file)[0]
|
|
222
|
+
uv = cmip6_indat(uv_file)
|
|
223
|
+
nx, ny = uv.get_nx_ny()
|
|
224
|
+
u_name = uv.vars[-2]
|
|
225
|
+
v_name = uv.vars[-1]
|
|
226
|
+
|
|
227
|
+
indat=os.path.join(os.path.dirname(__file__), "indat", "calcvor_onelev.in")
|
|
228
|
+
curr = os.getcwd()
|
|
229
|
+
|
|
230
|
+
print(indat, curr)
|
|
231
|
+
|
|
232
|
+
# generate input file and calculate vorticity using TRACK
|
|
233
|
+
os.system(
|
|
234
|
+
'sed -e "s/VAR1/{u}/;s/VAR2/{v}/;s/NX/{nx}/;s/NY/{ny}/;s/LEV/85000/;s/VORFILE/{out}/" {indat} > {curr}/calcvor_onelev_spec.in'
|
|
235
|
+
.format(u=u_name, v=v_name, nx=nx, ny=ny, out=outfile, indat=indat, curr=curr))
|
|
236
|
+
track(input_file=uv_file, namelist='calcvor_onelev_spec.in')
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def track_uv(infile, outdirectory, NH=True, ysplit=False):
|
|
240
|
+
|
|
241
|
+
# set outdir -- full path the output track directory
|
|
242
|
+
outdir = os.path.abspath(os.path.expanduser(outdirectory))
|
|
243
|
+
print(outdir)
|
|
244
|
+
|
|
245
|
+
# read data charactheristics
|
|
246
|
+
data = data_indat(infile,'cmip6')
|
|
247
|
+
gridtype = data.get_grid_type()
|
|
248
|
+
if ("va" not in data.vars) or ("ua" not in data.vars):
|
|
249
|
+
raise Exception("Invalid input variable type. Please input eithe " +
|
|
250
|
+
"a combined uv file or both ua and va")
|
|
251
|
+
|
|
252
|
+
print("Remove unnecessary variables.")
|
|
253
|
+
|
|
254
|
+
infile_e = infile[:-3] + "_processed.nc"
|
|
255
|
+
if "time_bnds" in data.vars:
|
|
256
|
+
ncks = "time_bnds"
|
|
257
|
+
if "lat_bnds" in data.vars:
|
|
258
|
+
ncks += ",lat_bnds,lon_bnds"
|
|
259
|
+
os.system("ncks -C -O -x -v " + ncks + " " + input + " " + infile_e)
|
|
260
|
+
elif "lat_bnds" in data.vars:
|
|
261
|
+
os.system("ncks -C -O -x -v lat_bnds,lon_bnds " + input + " " + infile_e)
|
|
262
|
+
else:
|
|
263
|
+
os.system("cp " + infile + " " + infile_e)
|
|
264
|
+
|
|
265
|
+
# interpolate, if not gaussian
|
|
266
|
+
infile_eg = infile_e[:-3] + "_gaussian.nc"
|
|
267
|
+
if gridtype == 'gridtype = gaussian':
|
|
268
|
+
print("No regridding needed.")
|
|
269
|
+
else:
|
|
270
|
+
# regrid
|
|
271
|
+
regrid_cmip6(infile_e, infile_eg)
|
|
272
|
+
os.system("mv " + infile_eg + " " + infile_e)
|
|
273
|
+
|
|
274
|
+
# fill missing values, modified to be xarray for now - ASh
|
|
275
|
+
infile_egf = infile_e[:-3] + "_filled.nc"
|
|
276
|
+
|
|
277
|
+
os.system("cdo setmisstoc,0 " + infile_e +
|
|
278
|
+
" " + infile_egf)
|
|
279
|
+
|
|
280
|
+
# check if ncatted exists
|
|
281
|
+
if shutil.which("ncatted") is None:
|
|
282
|
+
raise RuntimeError("Error: 'ncatted' command not found. Please install NCO before running this script.")
|
|
283
|
+
|
|
284
|
+
os.system("ncatted -a _FillValue,,d,, -a missing_value,,d,, " + infile_egf)
|
|
285
|
+
os.system("mv " + infile_egf + " " + infile_e)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# get final data info
|
|
289
|
+
data = cmip6_indat(infile_e)
|
|
290
|
+
nx, ny = data.get_nx_ny()
|
|
291
|
+
|
|
292
|
+
################## END OF PROCESSING ####################################################################
|
|
293
|
+
|
|
294
|
+
# Years
|
|
295
|
+
years = cdo.showyear(input=infile_e)[0].split()
|
|
296
|
+
print("Years: ", years)
|
|
297
|
+
|
|
298
|
+
if not ysplit:
|
|
299
|
+
years = ["all"]
|
|
300
|
+
|
|
301
|
+
if NH == True:
|
|
302
|
+
hemisphere = "NH"
|
|
303
|
+
else:
|
|
304
|
+
hemisphere = "SH"
|
|
305
|
+
|
|
306
|
+
# do tracking for one year at a time
|
|
307
|
+
|
|
308
|
+
for year in years:
|
|
309
|
+
print("Running TRACK for year: " + year + "...")
|
|
310
|
+
|
|
311
|
+
# select year from data
|
|
312
|
+
if ysplit:
|
|
313
|
+
print("Splitting: " + year)
|
|
314
|
+
year_file = infile_e[:-3] + "_" + year + ".nc"
|
|
315
|
+
cdo.selyear(year, input=infile_e, output=year_file)
|
|
316
|
+
else:
|
|
317
|
+
year_file=infile_e
|
|
318
|
+
|
|
319
|
+
# directory containing year specific track output
|
|
320
|
+
c_input = hemisphere + "_" + year
|
|
321
|
+
|
|
322
|
+
# get number of timesteps and number of chunks for tracking
|
|
323
|
+
data = cmip6_indat(year_file)
|
|
324
|
+
ntime = data.get_timesteps()
|
|
325
|
+
nchunks = ceil(ntime/62)
|
|
326
|
+
|
|
327
|
+
# calculate vorticity from UV
|
|
328
|
+
vor850_name = "vor850y"+year+".dat"
|
|
329
|
+
calc_vorticity(year_file, outfile=vor850_name)
|
|
330
|
+
|
|
331
|
+
fname = "T42filt_" + vor850_name
|
|
332
|
+
indat=os.path.join(os.path.dirname(__file__), "indat", "specfilt.in")
|
|
333
|
+
os.system(
|
|
334
|
+
'sed -e "s/NX/{nx}/;s/NY/{ny}/;s/TRUNC/42/" {indat} > spec_T42_nx{nx}_ny{ny}.in'
|
|
335
|
+
.format(nx=nx, ny=ny, indat=indat)
|
|
336
|
+
)
|
|
337
|
+
track(input_file=vor850_name, namelist=outdir+"/spec_T42_nx" + nx + "_ny" + ny + ".in")
|
|
338
|
+
os.system("mv "+ outdir+"/specfilt_band001.year_band001 " + fname)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# line_4 = "master -c=" + c_input + " -e=track.linux -d=now -i=" + \
|
|
342
|
+
# fname + " -f=y" + year + \
|
|
343
|
+
# " -j=RUN_AT.in -k=initial.T42_" + hemisphere + \
|
|
344
|
+
# " -n=1,62," + \
|
|
345
|
+
# str(nchunks) + " -o='" + outdir + \
|
|
346
|
+
# "' -r=RUN_AT_ -s=RUNDATIN.VOR"
|
|
347
|
+
|
|
348
|
+
# # executing the lines to run TRACK
|
|
349
|
+
# print("Spectral filtering...")
|
|
350
|
+
# # os.system(line_1)
|
|
351
|
+
# # os.system(line_2)
|
|
352
|
+
# # os.system(line_3)
|
|
353
|
+
|
|
354
|
+
# # print("Running TRACK...")
|
|
355
|
+
# # os.system(line_4)
|
|
356
|
+
|
|
357
|
+
# print("Turning track output to netCDF...")
|
|
358
|
+
|
|
359
|
+
# ### extract start date and time from data file
|
|
360
|
+
# filename="indat/"+year_file
|
|
361
|
+
# sdate = subprocess.check_output(f"cdo showdate {filename} | head -n 1 | awk '{{print $1}}'", shell=True)
|
|
362
|
+
# sdate = sdate.decode('utf-8').strip()
|
|
363
|
+
# stime1 = subprocess.check_output(f"cdo showtime {filename} | head -n 1 | awk '{{print $1}}'", shell=True)
|
|
364
|
+
# stime1 = stime1.decode('utf-8').strip()
|
|
365
|
+
|
|
366
|
+
# # hotfix for start year before 1979
|
|
367
|
+
# if sdate[0]=='0':
|
|
368
|
+
# sdate='2'+sdate[1:]
|
|
369
|
+
|
|
370
|
+
# # convert initial date to string for util/count, in format YYYYMMDDHH
|
|
371
|
+
# timestring=sdate[0:4]+sdate[5:7]+sdate[8:10]+stime1[0:2]
|
|
372
|
+
# datetime=sdate[0:4]+'-'+sdate[5:7]+'-'+sdate[8:10]+' '+stime1[0:2]
|
|
373
|
+
# timedelta=6
|
|
374
|
+
|
|
375
|
+
# if shift:
|
|
376
|
+
# sdate=str(int(sdate[0:4])-1)+'-11-'+sdate[8:10]
|
|
377
|
+
# print('shifted start date :', sdate)
|
|
378
|
+
# timestring=sdate[0:4]+sdate[5:7]+sdate[8:10]+stime1[0:2]
|
|
379
|
+
# datetime=sdate[0:4]+'-'+sdate[5:7]+'-'+sdate[8:10]+' '+stime1[0:2]
|
|
380
|
+
|
|
381
|
+
# # tr2nc - turn tracks into netCDF files
|
|
382
|
+
# os.system("gunzip '" + outdir + "'/" + c_input + "/ff_trs_*")
|
|
383
|
+
# os.system("gunzip '" + outdir + "'/" + c_input + "/tr_trs_*")
|
|
384
|
+
# tr2nc_vor(outdir + "/" + c_input + "/ff_trs_pos", timestring, datetime, timedelta)
|
|
385
|
+
# tr2nc_vor(outdir + "/" + c_input + "/ff_trs_neg", timestring, datetime, timedelta)
|
|
386
|
+
# tr2nc_vor(outdir + "/" + c_input + "/tr_trs_pos", timestring, datetime, timedelta)
|
|
387
|
+
# tr2nc_vor(outdir + "/" + c_input + "/tr_trs_neg", timestring, datetime, timedelta)
|
|
388
|
+
|
|
389
|
+
# ### cleanup fortran files ###########################
|
|
390
|
+
|
|
391
|
+
# if True: ## Change to false to keep files for debugging
|
|
392
|
+
# os.system("rm outdat/specfil*")
|
|
393
|
+
# os.system("rm outdat/ff_trs*")
|
|
394
|
+
# os.system("rm outdat/tr_trs*")
|
|
395
|
+
# os.system("rm outdat/interp_*")
|
|
396
|
+
# os.system("rm indat/"+year_file)
|
|
397
|
+
# os.system("rm indat/"+fname)
|
|
398
|
+
# os.system("rm indat/"+vor850_name)
|
|
399
|
+
# # os.system("rm indat/calcvor_onelev_" + ext + ".in")
|
|
400
|
+
|
|
401
|
+
return
|
|
402
|
+
|
|
403
|
+
def tr2nc_vor(input, timestring, datetime, timedelta):
|
|
404
|
+
"""
|
|
405
|
+
Convert vorticity tracks from ASCII to NetCDF using TR2NC utility
|
|
406
|
+
|
|
407
|
+
Parameters
|
|
408
|
+
----------
|
|
409
|
+
|
|
410
|
+
input : string
|
|
411
|
+
Path to ASCII file containing tracks
|
|
412
|
+
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
## ASh -- to get the right date range, modify the tr2nc.meta.elinor file with the right details
|
|
416
|
+
|
|
417
|
+
fullpath = os.path.abspath(input)
|
|
418
|
+
cwd = os.getcwd()
|
|
419
|
+
os.chdir(str(Path.home()) + "/TRACK/utils/TR2NC")
|
|
420
|
+
os.system("sed -e \"s/START/"+ str(timestring) + "/;s/DATE_TIME/" + str(datetime) + "/;s/STEP/" + str(timedelta) + "/\" tr2nc.meta.elinor > tr2nc.meta.elinor_mod")
|
|
421
|
+
os.chdir(str(Path.home()) + "/TRACK/utils/bin")
|
|
422
|
+
os.system("tr2nc '" + fullpath + "' s ../TR2NC/tr2nc.meta.elinor_mod")
|
|
423
|
+
os.chdir(cwd)
|
|
424
|
+
return
|
|
425
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: TRACK-pylib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python wrapper for the TRACK tracking system
|
|
5
|
+
Author-email: Abel Shibu <abels2000@gmail.com>
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: netCDF4
|
|
9
|
+
Requires-Dist: xarray
|
|
10
|
+
|
|
11
|
+
### pyTRACK
|
|
12
|
+
|
|
13
|
+
This is a python-wrapped implementation of the TRACK software.
|
|
14
|
+
|
|
15
|
+
To install, git clone this repository and from its base folder, run
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
pip install -e .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then from a Python terminal, run
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
from pyTRACK import *
|
|
25
|
+
track()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This should start the TRACK namelist.
|