2 Useful functions that don't belong elsewhere.
4 A collection of functions for analysing model output. These include functions to extract surfaces at a constant value of some variable and extract a variable on a surface.
6 Each function has a detailed docstring.
13 import matplotlib.pyplot
as plt
15 import scipy.interpolate
21 def calc_surface(input_array,surface_value,axis_values,method='linear'):
22 """! Calculate an isosurface of input_array. This function is fast, but it is not designed to deal well with non-monotonicity in the search direction.
24 There are two search options:
25 * 'nearest' finds the index just before the search value
26 * 'linear' uses linear interpolation to find the location between grid points.
28 May give silly answers if input_array is not monotonic in the search direction."""
32 monoton_test = np.diff(input_array,axis=axis)
34 if np.all(monoton_test <= 0)
or np.all(monoton_test >= 0):
37 warnings.warn(
"input field is not strictly monotonic in search direction. Strange results may occur.", RuntimeWarning)
39 indsz = np.repeat(np.arange(input_array.shape[0]-1).reshape((input_array.shape[0]-1,1,1)),input_array.shape[1],axis=1)
40 indsz = np.repeat(indsz.reshape((input_array.shape[0]-1,input_array.shape[1],1)),input_array.shape[2],axis=2)
42 dist = (input_array - surface_value)
46 indices_min = np.where(np.diff(sign,axis=axis),indsz,0)
47 indices_min = (np.argmax(indices_min,axis=axis))
49 if method ==
'nearest':
50 z_surface = np.take(axis_values,indices_min[:,:])
52 elif method ==
'linear':
53 z_nearest = np.take(axis_values,indices_min[:,:])
55 indsy = np.repeat(np.arange(indices_min.shape[0]).reshape((indices_min.shape[0],1)),indices_min.shape[1],axis=1)
56 indsx = np.repeat(np.arange(indices_min.shape[1]).reshape((1,indices_min.shape[1])),indices_min.shape[0],axis=0)
58 above = input_array[indices_min[:,:]-1,indsy,indsx]
59 nearest = input_array[indices_min[:,:],indsy,indsx]
60 below = input_array[indices_min[:,:]+1,indsy,indsx]
62 direction = np.zeros(indices_min.shape,dtype=
'int64')
65 test1 = above > surface_value
66 test2 = surface_value > nearest
67 direction[test1 == test2] = -1
69 test1 = above < surface_value
70 test2 = surface_value < nearest
71 direction[test1 == test2] = -1
73 test1 = nearest > surface_value
74 test2 = surface_value > below
75 direction[test1 == test2] = 1
77 test1 = nearest < surface_value
78 test2 = surface_value < below
79 direction[test1 == test2] = 1
83 z_surface = np.take(axis_values,indices_min[:,:] + direction) + np.nan_to_num(
84 ((z_nearest - np.take(axis_values,indices_min[:,:] + direction))/
85 (input_array[indices_min[:,:],indsy,indsx] -
86 input_array[indices_min[:,:] + direction,indsy,indsx]))*
87 (surface_value - input_array[indices_min[:,:] + direction,indsy,indsx]))
89 raise RuntimeError(str(method),
' not set correctly. Must be "nearest" or "linear".')
92 input_array_masked = np.ma.masked_where(input_array==0,input_array)
95 test1 = np.nanmax(input_array_masked,axis=0) < surface_value
96 test2 = np.nanmin(input_array_masked,axis=0) > surface_value
98 mask_condition = test1 + test2
100 z_surface = np.ma.masked_where(mask_condition, z_surface)
108 """!Extract the value of a 3D field on a 2D surface.
109 This function takes an 3 dimensional matrix 'input_array' and an 2 dimensional
110 matrix 'surface_locations' and returns a 2 dimensional matrix that contains the
111 values of input_array at the location specified by surface_locations along the third dimension using
112 the values for that axis contained in 'axis_values'. Linear interpolation is used to find the values.
115 if hasattr(surface_locations,
"__len__"):
118 surface_locations = surface_locations*np.ones((input_array.shape[1],input_array.shape[2]))
120 axis_array = np.repeat(axis_values.reshape((axis_values.shape[0],1,1)),surface_locations.shape[0],axis=1)
121 axis_array = np.repeat(axis_array.reshape((axis_array.shape[0],axis_array.shape[1],1)),surface_locations.shape[1],axis=2)
123 indsz = np.repeat(np.arange(input_array.shape[0]-1).reshape((input_array.shape[0]-1,1,1)),input_array.shape[1],axis=1)
124 indsz = np.repeat(indsz.reshape((input_array.shape[0]-1,input_array.shape[1],1)),input_array.shape[2],axis=2)
126 dist = (axis_array - surface_locations)
130 indices_above = np.where(np.diff(sign,axis=0),indsz,0)
131 indices_above = (np.nanmax(indices_above,axis=0))
133 indsy = np.repeat(np.arange(indices_above.shape[0]).reshape((indices_above.shape[0],1)),indices_above.shape[1],axis=1)
134 indsx = np.repeat(np.arange(indices_above.shape[1]).reshape((1,indices_above.shape[1])),indices_above.shape[0],axis=0)
136 values_above = input_array[indices_above[:,:],indsy,indsx]
137 values_below = input_array[indices_above[:,:]+1,indsy,indsx]
139 axis_above = axis_array[indices_above[:,:],indsy,indsx]
140 axis_below = axis_array[indices_above[:,:]+1,indsy,indsx]
142 surface_values = values_above + ((values_below - values_above)/(axis_below - axis_above))*(surface_locations - axis_above)
145 return surface_values
148 def layer_integrate(upper_contour, lower_contour, grid_obect, integrand = 'none', interp_method='none'):
149 """!Integrate between two non-trivial surfaces, 'upper_contour' and 'lower_contour'.
150 At the moment this only works if all the inputs are defined at the same grid location.
152 The input array 'integrand' is optional. If it is not included then the output is the volume per unit area (difference in depth) between the two surfaces at each grid point,
156 * upper_contour - the higher surface
157 * lower_contour - the lower surface
158 * axis_values - the vertical axis.
159 ** defined at the cell centres if using interp_method = 'linear'
160 ** defined at the cell faces if using interp_method = 'none'
161 * interp_method - defines whether the function interpolates values of the integrand. Possible options are 'none' or 'linear'.
162 * integrand - the field to be integrated between the contours
165 if interp_method ==
'none':
166 axis_values = grid_obect[
'Zl'][:]
167 elif interp_method ==
'linear':
168 axis_values = grid_obect[
'Z'][:]
170 total = np.ma.zeros((upper_contour.shape))
173 if integrand ==
'none':
174 total = np.absolute(upper_contour - lower_contour)
177 axis_array = np.repeat(axis_values.reshape((axis_values.shape[0],1,1)),upper_contour.shape[0],axis=1)
178 axis_array = np.repeat(axis_array.reshape((axis_array.shape[0],axis_array.shape[1],1)),upper_contour.shape[1],axis=2)
180 indsz = np.repeat(np.arange(integrand.shape[0]-1).reshape((integrand.shape[0]-1,1,1)),integrand.shape[1],axis=1)
181 indsz = np.repeat(indsz.reshape((integrand.shape[0]-1,integrand.shape[1],1)),integrand.shape[2],axis=2)
184 dist = (axis_array - upper_contour)
187 indices_upper = np.where(np.diff(sign,axis=0),indsz,0)
188 indices_upper = (np.nanmax(indices_upper,axis=0))
190 dist = (axis_array - lower_contour)
193 indices_lower = np.where(np.diff(sign,axis=0),indsz,0)
194 indices_lower = (np.nanmax(indices_lower,axis=0))
200 if interp_method ==
'none':
201 for j
in xrange(0,upper_contour.shape[0]):
202 for i
in xrange(0,upper_contour.shape[1]):
203 if (values_upper[j,i]
is not np.ma.masked)
and (values_lower[j,i]
is not np.ma.masked):
204 if indices_upper[j,i] == indices_lower[j,i]:
206 total[j,i] = (integrand[indices_upper[j,i],j,i])*(upper_contour[j,i] - lower_contour[j,i])
211 upper_partial = (integrand[indices_upper[j,i],j,i]*
212 (upper_contour[j,i] - axis_values[indices_upper[j,i]+1]))
213 lower_partial = (integrand[indices_lower[j,i],j,i]*
214 (axis_values[indices_lower[j,i]] - lower_contour[j,i]))
216 total[j,i] = upper_partial + lower_partial
220 if indices_lower[j,i] - indices_upper[j,i] > 1:
222 for k
in xrange(indices_upper[j,i],indices_lower[j,i]-1):
223 total[j,i] += integrand[k,j,i]*(axis_values[k] - axis_values[k+1])
226 elif interp_method ==
'linear':
227 for j
in xrange(0,upper_contour.shape[0]):
228 for i
in xrange(0,upper_contour.shape[1]):
229 if (values_upper[j,i]
is not np.ma.masked)
and (values_lower[j,i]
is not np.ma.masked):
231 if indices_upper[j,i] == indices_lower[j,i]:
233 total[j,i] = (values_upper[j,i] + values_lower[j,i])*(upper_contour[j,i] - lower_contour[j,i])/2
236 upper_partial = ((values_upper[j,i] + integrand[indices_upper[j,i]+1,j,i])*
237 (upper_contour[j,i] - axis_values[indices_upper[j,i]+1]))/2
238 lower_partial = ((values_lower[j,i] + integrand[indices_lower[j,i],j,i])*
239 (axis_values[indices_lower[j,i]] - lower_contour[j,i]))/2
241 total[j,i] = upper_partial + lower_partial
243 if indices_lower[j,i] - indices_upper[j,i] > 1:
245 for k
in xrange(indices_upper[j,i]+1,indices_lower[j,i]):
246 total[j,i] += (integrand[k,j,i] + integrand[k+1,j,i])*(axis_values[k] - axis_values[k+1])/2
248 elif (values_upper[j,i]
is np.ma.masked)
and (values_lower[j,i]
is not np.ma.masked):
251 total[j,i] += integrand[0,j,i]*grid_obect[
'drF'][0]/2
253 lower_partial = ((values_lower[j,i] + integrand[indices_lower[j,i],j,i])*
254 (axis_values[indices_lower[j,i]] - lower_contour[j,i]))/2
256 total[j,i] += lower_partial
258 for k
in xrange(indices_lower[j,i]):
260 total[j,i] += (integrand[k,j,i] + integrand[k+1,j,i])*(axis_values[k] - axis_values[k+1])/2
263 elif (values_upper[j,i]
is not np.ma.masked)
and (values_lower[j,i]
is np.ma.masked):
265 total[j,i] += integrand[-1,j,i]*grid_obect[
'drF'][-1]/2
268 upper_partial = ((values_upper[j,i] + integrand[indices_upper[j,i]+1,j,i])*
269 (upper_contour[j,i] - axis_values[indices_upper[j,i]+1]))/2
271 total[j,i] += upper_partial
273 for k
in xrange(indices_upper[j,i],len(axis_values)-1):
274 total[j,i] += (integrand[k,j,i] + integrand[k+1,j,i])*(axis_values[k] - axis_values[k+1])/2
282 """Test function to check that the layer integrate function is working correctly."""
283 upper = -1 * np.array([[1,1,1],[1,1,1],[1,1,1]])
284 lower = -1 * (np.array([[-0.9,1,1],[1,1,1],[1,1,1]]) + 2)
285 axis = -1 * np.array([0.5,1.2,1.6,2.1,2.6,3.1])
286 integrand = np.ones((len(axis),upper.shape[0],upper.shape[1]))
287 assert np.sum(
layer_integrate(upper,lower,axis,integrand=integrand)) == np.sum(upper - lower)
295 def interp_field(field,old_x,old_y,new_x,new_y,interp_order,fill_nans='no',max_its=5):
296 """!Interpolate a given field onto a different grid. Only performs interpolation in the horizontal directions.
300 * field - the variable to be interpolated
301 * old_x, old_y - the axis on which the original field is defined.
302 * new_x, new_y - the axis onto which the field will be interpolated.
303 * interp_order - the order of the interpolation function, integer between 1 and 5 inclusive. 1 -> linear, 3 -> cubic, &c..
304 * fill_nans - if 'no' values in field are not altered. If 'yes', then NaNs in 'field'
305 are replace with the mean of surrounding non-NaNs.
306 * max_its - maximum number of iterations to perform when healing NaNs in field."""
309 if len(field.shape) == 2:
310 field = field.reshape((1,field.shape[0],field.shape[1]))
311 elif len(field.shape) == 3:
314 error_message =
'Input field is must be either 2D or 3D.'
315 raise RuntimeError(error_message)
318 field_interp = np.zeros((field.shape[0],
322 for k
in xrange(field.shape[0]):
323 if fill_nans ==
'yes':
324 field_slice =
replace_nans(field[k,:,:], max_its,0.5,1,
'localmean')
326 while (field_slice != field_slice).any():
327 field_slice =
replace_nans(field_slice[:,:], max_its,0.5,1,
'localmean')
331 error_message =
'Tried ' + str(max_its) +
' iterations to heal NaNs in the input field, and failed.'
332 raise RuntimeError(error_message)
335 elif fill_nans ==
'no':
336 field_slice = field[k,:,:]
338 raise ValueError(
'fill_nans not set correctly. Should be "yes" or "no". You gave "' + str(fill_nans) +
'"')
340 interp_object = scipy.interpolate.RectBivariateSpline(old_y,old_x,field_slice,kx=interp_order,ky=interp_order)
341 field_interp[k,:,:] = interp_object(new_y,new_x)
350 """!Export binary files that can be imported into the MITgcm.
351 The files are big endian, and the datatype can either be 'float64' (= double precision), or 'float32' (=single precision).
353 Might not work for very large datasets."""
355 data = np.array(field,dtype=dtype)
356 if sys.byteorder ==
'little': data.byteswap(
True)
357 fid = open(filename,
"wb")
364 """!A shortcut function to display all of the variables contained within a netcdf file.
366 If the filename contains wildcards, they'll be expanded and only the first file used."""
367 files = glob.glob(netcdf_filename)
368 netcdf_file = netCDF4.Dataset(files[0])
369 print netcdf_file.variables.keys()
376 variables=[
'advcfl_uvel_max',
'advcfl_vvel_max',
'advcfl_wvel_max'],
378 output_filename=
None):
379 """!Plot some monitor file variables. 'netcdf_filename' can contain shell wild cards, but only the first matching file will be used.
398 files = glob.glob(netcdf_filename)
399 monitor_output = netCDF4.Dataset(files[0])
401 if time_units ==
'days':
402 time = monitor_output.variables[
'time_secondsf'][:]/(86400)
403 elif time_units ==
'years':
404 time = monitor_output.variables[
'time_secondsf'][:]/(86400*365)
406 raise ValueError(str(time_units) +
' is not a valid option for time_units')
408 for stat
in variables:
409 data[stat] = monitor_output.variables[stat][:]
410 plt.plot(time,data[stat],label=stat)
411 plt.xlabel(
'Model '+ time_units)
416 if output_filename !=
None:
417 plt.savefig(output_filename)
422 def replace_nans(array, max_iter, tol, kernel_size, method='localmean'):
423 """!Replace NaN elements in an array using an iterative image inpainting algorithm.
425 The algorithm is the following:
427 1) For each element in the input array, replace it by a weighted average
428 of the neighbouring elements which are not NaN themselves. The weights depends
429 of the method type. If ``method=localmean`` weight are equal to 1/( (2*kernel_size+1)**2 -1 )
431 2) Several iterations are needed if there are adjacent NaN elements.
432 If this is the case, information is "spread" from the edges of the missing
433 regions iteratively, until the variation is below a certain threshold.
438 array : 2d np.ndarray
439 an array containing NaN elements that have to be replaced
442 the number of iterations
445 the difference between subsequent iterations for stopping the algorithm
448 the size of the kernel, default is 1
451 the method used to replace invalid values. Valid options are
457 filled : 2d np.ndarray
458 a copy of the input array, where NaN elements have been replaced.
465 Code for this function is (very slightly modified) from
466 https://github.com/gasagna/openpiv-python/commit/81038df6d218b893b044193a739026630238fb22#diff-9b2f4f9bb8180e4451e8f85164df7217
467 which is part of the OpenPIV project.
468 * docs here: http://alexlib.github.io/openpiv-python/index.html
469 * code here: https://github.com/gasagna/openpiv-python
473 filled = np.empty( [array.shape[0], array.shape[1]], dtype=np.float64)
474 kernel = np.empty( (2*kernel_size+1, 2*kernel_size+1), dtype=np.float64 )
477 inans, jnans = np.nonzero( np.isnan(array) )
483 replaced_new = np.zeros( n_nans, dtype=np.float64)
484 replaced_old = np.zeros( n_nans, dtype=np.float64)
488 if method ==
'localmean':
489 for i
in range(2*kernel_size+1):
490 for j
in range(2*kernel_size+1):
493 raise ValueError(
'method not valid. Should be one of `localmean`.')
496 for i
in range(array.shape[0]):
497 for j
in range(array.shape[1]):
498 filled[i,j] = array[i,j]
504 for it
in range(max_iter):
507 for k
in range(n_nans):
516 for I
in range(2*kernel_size+1):
517 for J
in range(2*kernel_size+1):
520 if i+I-kernel_size < filled.shape[0]
and i+I-kernel_size >= 0:
521 if j+J-kernel_size < filled.shape[1]
and j+J-kernel_size >= 0:
524 if filled[i+I-kernel_size, j+J-kernel_size] == filled[i+I-kernel_size, j+J-kernel_size] :
527 if I-kernel_size != 0
and J-kernel_size != 0:
530 filled[i,j] += filled[i+I-kernel_size, j+J-kernel_size]*kernel[I, J]
535 filled[i,j] = filled[i,j] / n
536 replaced_new[k] = filled[i,j]
542 if np.mean( (replaced_new-replaced_old)**2 ) < tol:
545 for l
in range(n_nans):
546 replaced_old[l] = replaced_new[l]
550 inans, jnans = np.nonzero( np.isnan(filled) )
553 fill_value = np.nanmean(filled)
554 for k
in range(n_nans):
558 filled[i,j] = fill_value
613 for it
in range(max_iter):
616 for k
in range(n_nans):
625 for I
in range(2*kernel_size+1):
626 for J
in range(2*kernel_size+1):
629 if i+I-kernel_size < filled.shape[0]
and i+I-kernel_size >= 0:
630 if j+J-kernel_size < filled.shape[1]
and j+J-kernel_size >= 0:
633 if filled[i+I-kernel_size, j+J-kernel_size] == filled[i+I-kernel_size, j+J-kernel_size] :
636 if I-kernel_size != 0
and J-kernel_size != 0:
639 filled[i,j] += filled[i+I-kernel_size, j+J-kernel_size]*kernel[I, J]
644 filled[i,j] = filled[i,j] / n
645 replaced_new[k] = filled[i,j]
651 if np.mean( (replaced_new-replaced_old)**2 ) < tol:
654 for l
in range(n_nans):
655 replaced_old[l] = replaced_new[l]
663 """! Shift the array from vorticity points to the corresponding tracer point."""
664 shifted = (array[...,0:-1] + array[...,1:])/2
665 shifted = (shifted[...,0:-1,:] + shifted[...,1:,:])/2
669 """! Shift the array from UVEL points to the corresponding tracer point."""
670 shifted = (array[...,0:-1] + array[...,1:])/2
675 """! Shift the array from VVEL points to the corresponding tracer point."""
676 shifted = (array[...,0:-1,:] + array[...,1:,:])/2
681 """! Shift the array from WVEL points to the corresponding tracer point."""
682 shifted = (array[...,0:-1,:,:] + array[...,1:,:,:])/2
def interp_field
Interpolate a given field onto a different grid.
def shift_vort_to_T(array)
Shift the array from vorticity points to the corresponding tracer point.
def show_variables(netcdf_filename)
A shortcut function to display all of the variables contained within a netcdf file.
def test_layer_integrate()
def plt_mon_stats
Plot some monitor file variables.
def shift_V_to_T(array)
Shift the array from VVEL points to the corresponding tracer point.
def extract_on_surface(input_array, surface_locations, axis_values)
Extract the value of a 3D field on a 2D surface.
def shift_W_to_T(array)
Shift the array from WVEL points to the corresponding tracer point.
def export_binary
Export binary files that can be imported into the MITgcm.
def layer_integrate
Integrate between two non-trivial surfaces, 'upper_contour' and 'lower_contour'.
def numerics_replace_nans(max_iter, n_nans, inans, jnans, filled, kernel, kernel_size, tol, replaced_new, replaced_old)
def calc_surface
Calculate an isosurface of input_array.
def shift_U_to_T(array)
Shift the array from UVEL points to the corresponding tracer point.
def replace_nans
Replace NaN elements in an array using an iterative image inpainting algorithm.