-
Notifications
You must be signed in to change notification settings - Fork 2
/
plot_cmtxxx_xxx-xx-outputs.py
executable file
·373 lines (286 loc) · 12.4 KB
/
plot_cmtxxx_xxx-xx-outputs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
#!/usr/bin/env python
# July 2014
# Tobey Carman
# Spatial Ecology Lab
# University of Alaska Fairbanks
# Script to plot "cmt***_***_**.nc" files - netcdf files with a certain set of
# dimensions - that are created by dvmdostem.
# - The first set of stars denotes the type of variables: env, bgc, or dim
# - The second set of stars denotes the time resolution: dly, mly, yly
# - The third set of starts denotes the run stage: eq, sp, tr, sc
import sys # for exit()
import os # for path, basename
import argparse
import logging
import warnings
import textwrap
import netCDF4 as nc
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.ticker import MaxNLocator
from IPython import embed
class FPlogger:
'''A mini class for logging floating point warnings.'''
def write(self, s):
s = s[0:-1] # strip newline that comes from
logging.warn("%s" % s)
def main(args):
# Make numpy report all floating point errors to the log
# NOTE: warnings were coming up due to tiny data ranges (below machine epsilon)
np.seterr(all='log')
np.seterrcall(FPlogger()) # pass an object that implements the write method
# define some variable sets to plot
varsets = {
'N': [
'NMOBIL', 'NRESORB', 'NUPTAKESALL', 'NUPTAKEL', 'VEGNSUM', 'NETNMIN',
'LTRFALNALL', 'AVLNSUM', 'AVLNINPUT', 'AVLNLOST', 'ORGNLOST'
],
'C': [
'NEP', 'NPPALL', 'GPPALL', 'VEGCSUM', 'LTRFALCALL', 'SOMCSHLW',
'SOMCDEEP', 'SOMCMINEA', 'SOMCMINEC', 'RHMOIST', 'RHQ10', 'SOILLTRFCN'
],
'E': [
'SNOWFALL','RAINFALL','EETTOTAL','PETTOTAL','TAIR','SOILTAVE',
'SOILVWC','RZTHAWPCT','ALD',
]
}
if args.report:
report_on_file(args)
sys.exit(0)
if 'E' == args.varset:
logging.info("Make sure the cmt file is the right one!")
logging.debug("Loading netcdf file (%s)..." % args.inputfile)
dsA = nc.Dataset(args.inputfile)
titlestring = "%s" % (args.inputfile)
if args.stitch:
logging.info("Attempting to stitch stages %s onto inputfile before displaying..." % (args.stitch))
logging.debug("Create a temporary file to concatenate everything into...")
tmpdata = nc.Dataset('/tmp/junk.nc', 'w')
logging.info("Make sure the right files exist...")
inputdir = os.path.split(args.inputfile)[0]
bn = os.path.basename(args.inputfile) # e.g.: 'cmtbgc_yly-eq.nc'
for stage in args.stitch:
sn = "%s-%s.nc" % (bn[0:10], stage)
if not os.path.exists(os.path.join(inputdir,sn)):
logging.error("File %s does not exist in %s!" % (sn, inputdir))
logging.error("Cannot perform file stitching. Quitting...")
sys.exit(-1)
else:
logging.info("File exists for stitching...")
titlestring += "\n%s" % (os.path.join(inputdir,sn))
logging.debug("Copy the dimensions from dataset A in to the temporary file...")
for d in dsA.dimensions:
if 'tstep' == d:
tmpdata.createDimension(d, None)
else:
tmpdata.createDimension(d, len(dsA.dimensions[d]))
logging.info("Copy values from first input file to tmpdata...")
if not 'YEAR' in tmpdata.variables:
tmpdata.createVariable('YEAR', 'i', dsA.variables['YEAR'].dimensions)
tmpdata.variables['YEAR'][:] = dsA.variables['YEAR'][:]
for v in varsets[args.varset]:
print v
if not v in tmpdata.variables:
tmpdata.createVariable(v, 'f', dsA.variables[v].dimensions)
if dsA.variables[v].dimensions[0] == 'tstep':
tmpdata.variables[v][:] = dsA.variables[v][:]
logging.info("tmpdata.variables[%s].shape: %s" % (v, tmpdata.variables[v].shape))
logging.debug("Process each requested stage to stitch together...")
stage_end_indices = ''
for stage in args.stitch:
seidx = len(tmpdata.dimensions['tstep'])
stage_end_indices += '%i ' % seidx # make space delimited string of end of stage
# indices
logging.info("First getting time axis data...")
if not 'YEAR' in tmpdata.variables:
tmpdata.createVariable('YEAR', 'f', dsA.variables['YEAR'].dimensions)
tmpdata.variables['YEAR'][seidx:] = get_more_data(stage, 'YEAR', args)
logging.info("Next, getting all other variables in the var set...")
for v in varsets[args.varset]:
if not v in tmpdata.variables:
tmpdata.createVariable(v, 'f', dsA.variables[v].dimensions)
if dsA.variables[v].dimensions[0] == 'tstep':
tmpdata.variables[v][seidx:] = get_more_data(stage, v, args)
logging.info("tmpdata.variables[%s].shape: %s" % (v, tmpdata.variables[v].shape))
del dsA
dsA = tmpdata
logging.debug("Accquiring figure and subplot objects...")
fig, axar = plt.subplots(6, 2, sharex=True)
logging.debug("Setup figure title...")
fig.suptitle(titlestring)
# Would like to label xaxis with these:
xdata = dsA.variables['YEAR'][:]
logging.info("%s years: [%s ... %s]" % (len(xdata), xdata[0:3], xdata[-3:]))
logging.debug("Plot each variable in the variable set...")
for i, v in enumerate(varsets[args.varset]):
row = i % 6
col = 0 if i < 6 else 1
ax = axar[row, col]
logging.debug( "subplot [%s, %s] %s, dims: %s" % (row, col, v, dsA.variables[v].dimensions))
#logging.debug("choose data to plot based on variable's dimensions...")
if dsA.variables[v].dimensions == ('tstep','pft','vegpart'):
data2plot = round_tiny_range(dsA.variables[v][:,args.pft,0])
linecollection = ax.plot(data2plot)
ax.set_title('%s %s %s '%(v, 'PFT', args.pft), fontdict={'fontsize':8})
elif dsA.variables[v].dimensions == ('tstep','pft'):
data2plot = round_tiny_range(dsA.variables[v][:,args.pft])
linecollection = ax.plot(data2plot)
ax.set_title('%s %s %s '%(v, 'PFT', args.pft), fontdict={'fontsize':8})
elif dsA.variables[v].dimensions == ('tstep',):
data2plot = round_tiny_range(dsA.variables[v][:])
linecollection = ax.plot(data2plot)
ax.set_title(v, fontdict={'fontsize':8})
elif dsA.variables[v].dimensions == ('tstep','soilayer'):
data2plot = round_tiny_range(dsA.variables[v][:,0])
linecollection = ax.plot(data2plot)
ax.set_title('%s %s %s '%(v, 'layer', 0), fontdict={'fontsize':8})
else:
logging.error("unknown dimensions for variable %s." % v)
logging.debug("setting the max number of ticks for x and y axes...")
ax.yaxis.set_major_locator(MaxNLocator(nbins=4, prune='both'))
ax.xaxis.set_major_locator(MaxNLocator(nbins=6, prune='both'))
if args.stitch:
logging.debug("Setting the end-of-stage marker lines...")
for seidx in stage_end_indices.split():
ax.axvline(seidx, color='red')
for row in axar:
for ax in row:
if 0 == len(ax.lines):
logging.debug("Turn off empty axes...")
ax.set_visible(False)
logging.debug("Adjust font size for axes tick labels")
plt.setp(ax.get_yticklabels(), fontsize=8)
plt.setp(ax.get_xticklabels(), fontsize=8)
topadj = 0.92 - 0.025*len(titlestring.splitlines())
print topadj
fig.subplots_adjust(top=topadj)
fig.subplots_adjust(hspace=.5)
if args.save:
saved_file_name = "plot_cmt.png"
print "Savging plot as '%s'..." % saved_file_name
plt.savefig(saved_file_name, dpi=72)
if args.display:
print "Showing plot..."
plt.show()
# if args.dumpcsv:
# possible psuedo code:
# for each subplot
# make file name, comments, etc
# make np array to accumulate data
# for each line in each subplots
# get label
# get xydata, add to np array
# np.savetxt()
#
# Utility functions...
#
def round_tiny_range(data):
# http://stackoverflow.com/questions/10555659/python-arithmetic-with-small-numbers
mn = min(data)
mx = max(data)
logging.debug("min: %s, max: %s " % (mn, mx))
datarange = np.abs(mx - mn)
if (datarange < np.finfo(np.float).eps):
logging.warn("The data range is tiny! Less than this machine's epsilopn!")
logging.warn("Replacing data with new values of %s size, rounded to 14 decimals" % mn)
adj = np.empty(len(data))
adj.fill(np.around(mn, decimals=14))
return adj
else:
# range is larger than epsilon, just return original data
return data
def validate_outputnc_file(file):
ds = nc.Dataset(file)
try:
# check correct dimensions
assert set(ds.dimensions.keys()) == set(['PFTS', 'CHTID', 'YEAR', 'YYYYMM'])
except AssertionError as e:
print "Problem with NetCDF file shape! Quitting."
def determine_stage(filename):
stage = os.path.basename(filename)[-5:-3]
if stage in ['eq','sp','tr','sc']:
return stage
else:
return 'unknown'
def determine_timeres(filename):
timeres = os.path.basename(filename)[-9:-6]
if timeres in ['yly','mly','dly']:
return timeres
else:
return 'unknown'
def report_on_file(args):
ds = nc.Dataset(args.inputfile)
print "-- File report for: %s" % args.inputfile
print "- runstage: ", determine_stage(args.inputfile)
print "- timestep: ", determine_timeres(args.inputfile)
print "- dimensions (size)"
for d in ds.dimensions:
print " ", d, "(%s)" % len(ds.dimensions[d])
# deprecated...
def set_bg_tag_txt(ax, tag, num):
#logging.debug("set the background 'PFTx' text for axes that are plotting pft specific variables.")
font = {
'family' : 'sans-serif',
'color' : 'black',
'weight' : 'bold',
'size' : 18,
'alpha' : 0.5,
}
ax.text(
0.5, 0.5,
"%s %s" % (tag, num),
fontdict=font,
horizontalalignment='center',
#verticalalignment='top',
transform=ax.transAxes,
#bbox=dict(facecolor='red', alpha=0.2)
)
def get_more_data(stage, var, args):
'''Based on stage and variable, returns an array of data from a cmt file.'''
inputdir = os.path.split(args.inputfile)[0]
bn = os.path.basename(args.inputfile) # e.g.: 'cmtbgc_yly-eq.nc'
sn = "%s-%s.nc" % (bn[0:10], stage)
logging.info("Looking for %s in file %s" % (var, os.path.join(inputdir, sn)))
d = nc.Dataset(os.path.join(inputdir,sn))
logging.info("Found! Returning dataset for %s. (Shape: %s)" % (var, d.variables[var].shape))
return d.variables[var][:]
if __name__ == '__main__':
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
parser = argparse.ArgumentParser(description=textwrap.dedent('''\
Plots a cmt***_***-**.nc file from dvmdostem. The script has three
different "variable sets" it can plot (C, N, E) related to Carbon,
Nitrogen, Environmental variables respectively. The script can optionally
stitch data from other cmt***_***-**.nc files onto the end of the plot
(plot data from multiple files consecutively on one time series/axis).'''))
parser.add_argument('-r', '--report', action='store_true',
help=textwrap.dedent('''\
Read the input netCDF file, reporting on the runstage, time resolution,
and dimensions of the file. Deduces runstage and time resolution from
file name.'''))
parser.add_argument('-p', '--pft', default=0, required=False, type=int, metavar='N',
help="For pft variables, which pft to show (0-9)")
parser.add_argument('-v', '--varset', default='C', choices=['C','N','E'],
help="Choose the 'variable set' to plot (Carbon, Nitrogen, Environmental")
parser.add_argument('-d', '--display', action='store_true',
help="Display the plot")
parser.add_argument('-s', '--save', action='store_true',
help=textwrap.dedent('''\
Save the plot with generic name. Warning: will overwrite existing file
with same name!!'''))
# parser.add_argument('-s', '--startyr', default=0, required=False, type=int, metavar='N',
# help="Which year to start with. Defaults to 0, for all years. (will show env only warmup)")
# parser.add_argument('-e', '--endyr', default=None, required=False, type=int, metavar='N',
# help="Which year to end with. Defaults to None, for all years. (will read everything in the file)")
parser.add_argument('inputfile',
help=textwrap.dedent('''\
Path to a cmtxxx_xxx-xx.nc file produced by dvmdostem.'''))
parser.add_argument('--stitch', required=False,
nargs='+',
help=textwrap.dedent('''\
Look for other files of the subsequent runstages in the same directory
as the inputfile. If found, stitch the data together along the time
dimension, and plot the resulting timeseries.'''))
logging.debug("Parsing command line...")
args = parser.parse_args()
main(args)