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.
Files changed (45) hide show
  1. pyTRACK/__init__.py +4 -0
  2. pyTRACK/_lib/libtrack.so +0 -0
  3. pyTRACK/data/CMAP.1074 +46 -0
  4. pyTRACK/data/CMAP.dat.claire +16 -0
  5. pyTRACK/data/CMAP.dat.john +13458 -0
  6. pyTRACK/data/CMAP.dat.mask +3129 -0
  7. pyTRACK/data/CMAP.dat.mine +3001 -0
  8. pyTRACK/data/CMAP.dat.orig +3001 -0
  9. pyTRACK/data/adapt.dat +2 -0
  10. pyTRACK/data/constraints.dat +38 -0
  11. pyTRACK/data/constraints.dat.proto +38 -0
  12. pyTRACK/data/constraints.dat.reg +38 -0
  13. pyTRACK/data/constraints.dat.sphery +45 -0
  14. pyTRACK/data/gridT63.nc +0 -0
  15. pyTRACK/data/initial.T42_NH +19 -0
  16. pyTRACK/data/initial.T42_SH +19 -0
  17. pyTRACK/data/initial.T63_NH +19 -0
  18. pyTRACK/data/initial.T63_SH +19 -0
  19. pyTRACK/data/initial.st +16 -0
  20. pyTRACK/data/zone.dat +4 -0
  21. pyTRACK/data/zone.dat.new +10 -0
  22. pyTRACK/indat/RSPLICE.in +22 -0
  23. pyTRACK/indat/RSPLICE_GFS.in +20 -0
  24. pyTRACK/indat/RUNDATIN.GFS_MSLP.in +53 -0
  25. pyTRACK/indat/RUNDATIN.GFS_MSLP_A.in +54 -0
  26. pyTRACK/indat/RUNDATIN.GFS_VOR.in +53 -0
  27. pyTRACK/indat/RUNDATIN.GFS_VOR_A.in +54 -0
  28. pyTRACK/indat/RUNDATIN.era_MSLP.in +49 -0
  29. pyTRACK/indat/RUNDATIN.era_MSLP_A.in +50 -0
  30. pyTRACK/indat/RUNDATIN.era_VOR.in +50 -0
  31. pyTRACK/indat/RUNDATIN.era_VOR_A.in +51 -0
  32. pyTRACK/indat/SPACE_FILTER.in +38 -0
  33. pyTRACK/indat/STATS.era2_NH.in +110 -0
  34. pyTRACK/indat/STATS.era2_NH_wght.in +147 -0
  35. pyTRACK/indat/STATS.era2_SH.in +110 -0
  36. pyTRACK/indat/STATS.era2_SH_wght.in +143 -0
  37. pyTRACK/indat/calcvor_onelev.in +34 -0
  38. pyTRACK/indat/specfilt.in +31 -0
  39. pyTRACK/track.py +70 -0
  40. pyTRACK/track_master.py +245 -0
  41. pyTRACK/utils.py +425 -0
  42. track_pylib-0.1.0.dist-info/METADATA +28 -0
  43. track_pylib-0.1.0.dist-info/RECORD +45 -0
  44. track_pylib-0.1.0.dist-info/WHEEL +5 -0
  45. track_pylib-0.1.0.dist-info/top_level.txt +1 -0
@@ -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.