Source code for regrid2.gsRegrid

# !/usr/bin/env python
# Alex Pletzer and Dave Kindig, Tech-X (2011)
# This code is provided with the hope that it will be useful.
# No guarantee is provided whatsoever. Use at your own risk.
"""
Regridding of curvilinear structured grids

"""
# standard python includes
# from re import search, sub
from ctypes import c_double, c_float, c_int, \
    c_wchar_p, CDLL, byref, POINTER
# import ctypes
import operator
import sys
import os
import copy
import numpy
import warnings
from regrid2 import RegridError
from functools import reduce
import fnmatch

C_DOUBLE_P = POINTER(c_double)

# libcf
try:
    from pycf import libCFConfig, __path__
except BaseException:
    raise ImportError('Error: could not import pycf')

LIBCFDIR = __path__[0] + "/pylibcf"

__FILE__ = sys._getframe().f_code.co_filename


[docs]def catchError(status, lineno): if status != 0: raise RegridError("ERROR in %s: status = %d at line %d" % (__FILE__, status, lineno))
[docs]def getTensorProduct(axis, dim, dims): """ Convert an axis into a curvilinear coordinate by applying a tensor product Parameters ---------- axis 1D array of coordinates dim dimensional index of the above coordinate dims sizes of all coordinates Returns ------- coordinate values obtained by tensor product """ return numpy.outer(numpy.outer(numpy.ones(dims[:dim], axis.dtype), axis), numpy.ones(dims[dim + 1:], axis.dtype)).reshape(dims)
[docs]def makeCurvilinear(coords): """ Turn a mixture of axes and curvilinear coordinates into full curvilinear coordinates Parameters ---------- coords list of coordinates _: None Returns ------- new list of coordinates and associated dimensions """ rank = len(coords) count1DAxes = 0 dims = [] for i in range(rank): coord = coords[i] if len(coord.shape) == 1: # axis dims.append(len(coord)) count1DAxes += 1 elif len(coord.shape) == rank: # fully curvilinear dims.append(coord.shape[i]) else: # assumption: all 1D axes preceed curvilinear # coordinates!!! dims.append(coord.shape[i - count1DAxes]) for i in range(rank): nd = len(coords[i].shape) if nd == rank: # already in curvilinear form, keep as is pass elif nd == 1: # it's an axis coords[i] = getTensorProduct(coords[i][:], i, dims) elif rank == 3 and nd == 2 and i > 0: # assume leading coordinate is an axis o1 = numpy.ones((len(coords[0]),), coords[i].dtype) coords[i] = numpy.ma.outer(o1, coords[i]).reshape(dims) else: raise RegridError("ERROR in %s: funky mixture of axes and curvilinear coords %s" % (__FILE__, str([x.shape for x in coords]))) return coords, dims
[docs]def makeCoordsCyclic(coords, dims): """ Make coordinates cyclic Parameters ---------- coords input coordinates dims input dimensions Returns ------- new, extended coordinates such that the longitudes cover the sphere and new dimensions """ # assume lon is the last coordinate!! # check if already extended eps = 1.e-3 # some models already overlap diff1 = abs(coords[-1][..., -2] - coords[-1][..., 0]) diff2 = abs(coords[-1][..., -2] - coords[-1][..., 0] - 360.0) diff3 = abs(coords[-1][..., -2] - coords[-1][..., 0] + 360.0) adiff = numpy.sum(numpy.minimum(diff1, numpy.minimum(diff2, diff3))) \ / float(dims[-1]) if adiff < eps: # cyclic, return input coordinates unchanged return coords, dims # some models are already periodic diff1 = abs(coords[-1][..., -1] - coords[-1][..., 0]) diff2 = abs(coords[-1][..., -1] - coords[-1][..., 0] - 360.0) diff3 = abs(coords[-1][..., -1] - coords[-1][..., 0] + 360.0) adiff = numpy.sum(numpy.minimum(diff1, numpy.minimum(diff2, diff3))) \ / float(dims[-1]) if adiff < eps: # cyclic, return input coordinates unchanged return coords, dims # make cyclic by appending a column to the coordinates newCoords = [] newDims = list(copy.copy(dims)) newDims[-1] += 1 # append to the right for i in range(len(coords)): newCoords.append(numpy.zeros(newDims, coords[i].dtype)) newCoords[i][..., 0:-1] = coords[i][...] newCoords[i][..., -1] = coords[i][..., 0] # add modulo term, want deltas ~ order of dlon otherwise add # or subtract a periodicity length nlon = dims[-1] dlon = 360.0 / float(nlon) # average resolution tol = 360.0 - min(5, nlon) * dlon mask1 = (newCoords[-1][..., -1] - newCoords[-1][..., -2] < -tol) mask2 = (newCoords[-1][..., -1] - newCoords[-1][..., -2] > +tol) newCoords[-1][..., -1] += 360.0 * mask1 newCoords[-1][..., -1] -= 360.0 * mask2 return newCoords, newDims
[docs]def checkForCoordCut(coords, dims): """ Look for a cut in a coordinate system (e.g. tri-polar grid) Assume latitude is next to last coordinate and longitude is last coordinate!!! Parameters ---------- coords input coordinates dims input dimensions Returns ------- True for cut found False for no cut """ # Assume latitude is next to last coordinate and longitude is last # coordinate!!! rank = len(dims) if rank < 2: # print 'no cut: dims < 2' return False if len(coords[-2].shape) < 2: # Is the 'lat' coordinate an axis? return False nlat, nlon = dims[-2], dims[-1] lat = coords[-2] eps = 1.e-7 # Check to see if the top row has already be dealt with by the modeling # agency. Last row is repeated in reverse. topRow = coords[-2][..., nlat - 1, :] revTop = coords[-2][..., nlat - 1, ::-1] nextRow = coords[-2][..., nlat - 2, :] # Reverse nextRow diffs = abs(revTop - nextRow) # If already accounted for all diffs are 0. if numpy.all(diffs < eps): # print "no cut: reversed" return False # Lon of max latitude -- Looking for a rotated pole maxLats = numpy.where(abs(lat - numpy.max(lat)) < eps) inTopRow = False if len(maxLats[0] > 0): inTopRow = numpy.all(maxLats[-2] == nlat - 1) if not inTopRow: # The max lats are not in the top row. The cut may already be handled # print 'no cut: max lat not in top row.' + \ # 'Either: it is a funky grid or rotated pole' return False # Only in top row. maxLatInd = lat[..., nlat - 1, :].argmax() maxLonInd = lat[..., maxLatInd].argmax() rowOfMaxLat = lat[..., maxLonInd, :] diffs = rowOfMaxLat - topRow if diffs.max() != 0: # Rotated Pole # print "no cut: rotated pole" return False # Find locale minima. # A rotated pole grid has only one minimum. A tripolar grid should # have two, though they may not be at the same latitude minInds = numpy.where(abs(topRow - topRow.min()) < eps) if len(minInds[0]) > 2: # Account for the end points matching # Multiple minima in the top row return True # Now if we have an offset tri-pole. The extra poles are not at the same # latitude minCount = 0 firstInd = topRow.argmin() diffs = numpy.diff(topRow) if firstInd == 0: if revTop.argmin() == 0: if topRow[firstInd] == revTop[0]: minCount += 1 else: minCount += 1 # Look for next Minima index = firstInd + 1 while diffs[index] > 0: index += 1 if index == nlon: break nextIndex = topRow[index + 1:].argmin() + index + 1 if nextIndex != index + 1 and nextIndex != nlon - 1: minCount += 1 if minCount == 1: # print "no cut: one pole" return False return True
[docs]def handleCoordsCut(coords, dims, bounds): """ Generate connectivity across a cut. e.g. from a tri-polar grid. Assume latitude is next to last coordinate and longitude is last coordinate!!! Parameters ---------- coords input coordinates list of rank dims input dimensions bounds boundaries for each coordinate Returns ------- extended coordinates such that there is an extra row containing connectivity information across the cut """ # Assume latitude is next to last coordinate and longitude is # last coordinate!!! dims = coords[-2].shape # Add row to top with connectivity information. This means rearranging # the top row def getIndices(array, nlon, newI): """ Find indices where a cell edge matches for two cells Parameters ---------- array Array of booleans nlon number of longitudes newI index row with connectivity to be updated Returns ------- new coordinates, new dimensions, index row """ for i in range(len(array)): # An edge if len(numpy.where(array[i, :] == True)[0]) >= 2: # noqa if newI[i] < 0: newI[i] = (nlon - 1) - i if newI[(nlon - 1) - i] < 0: newI[(nlon - 1) - i] = i # Assume mkCyclic == True newI = numpy.arange(nlon - 1, -1, -1) - 1 newI[nlon - 1] = 0 # Complete the rotation # Build new coordinate array and adjust dims newCoords = [] newDims = list(copy.copy(dims)) newDims[-2] += 1 for i in range(len(coords)): newCoords.append(numpy.zeros(newDims, coords[i].dtype)) newCoords[i][..., 0:dims[-2], :] = coords[i][...] newCoords[i][..., dims[-2], :] = coords[i][..., dims[-2] - 1, newI] return newCoords, newDims, newI
[docs]class Regrid: """ Constructor Parameters ---------- src_grid source grid, a list of [x, y, ...] coordinates or a cdms2.grid.Transient dst_grid destination grid, a list of [x, y, ...] coordinate src_bounds list of cell bounding coordinates (to be used when handling a cut in coordinates) mkCyclic Add a column to the right side of the grid to complete a cyclic grid handleCut Add a row to the top of grid to handle a cut for grids such as the tri-polar grid verbose print diagnostic messages Note ---- the grid coordinates can either be axes (rectilinear grid) or n-dimensional for curvilinear grids. Rectilinear grids will be converted to curvilinear grids. """ def __init__(self, src_grid, dst_grid, src_bounds=None, mkCyclic=False, handleCut=False, verbose=False): """ Constructor """ self.regridid = c_int(-1) self.src_gridid = c_int(-1) self.dst_gridid = c_int(-1) self.rank = 0 self.src_dims = [] self.dst_dims = [] self.src_coords = [] self.dst_coords = [] self.lib = None self.extendedGrid = False self.handleCut = False self.dst_Index = [] self.verbose = verbose self.weightsComputed = False self.maskSet = False # Open the shaped library dynLibFound = False for sosuffix in '.dylib', '.dll', '.DLL', '.so', '.a': if os.path.exists(LIBCFDIR + sosuffix): dynLibFound = True CFfile = self.find('pylibcf.*', __path__[0]) if os.path.exists(CFfile): try: self.lib = CDLL(CFfile) except BaseException: pass # for sosuffix in '.dylib', '.dll', '.DLL', '.so', '.a': # if os.path.exists(LIBCFDIR + sosuffix): # dynLibFound = True # try: # self.lib = CDLL(LIBCFDIR + sosuffix) # break # except: # pass if self.lib is None: if not dynLibFound: raise RegridError("ERROR in %s: could not find shared library %s.{so,dylib,dll,DLL}" % (__FILE__, LIBCFDIR)) raise RegridError("ERROR in %s: could not open shared library %s.{so,dylib,dll,DLL}" % (__FILE__, LIBCFDIR)) # Number of space dimensions self.rank = len(src_grid) if len(dst_grid) != self.rank: raise RegridError("ERROR in %s: len(dst_grid) = %d != %d" % (__FILE__, len(dst_grid), self.rank)) if self.rank <= 0: raise RegridError("ERROR in %s: must have at least one dimension, rank = %d" % (__FILE__, self.rank)) # Convert src_grid/dst_grid to curvilinear grid, if need be if self.rank > 1: src_grid, src_dims = makeCurvilinear(src_grid) dst_grid, dst_dims = makeCurvilinear(dst_grid) # Make sure coordinates wrap around if mkCyclic is True if mkCyclic: src_gridNew, src_dimsNew = makeCoordsCyclic(src_grid, src_dims) if self.verbose: aa, bb = str(src_dims), str(src_dimsNew) print(('... src_dims = %s, after making cyclic src_dimsNew = %s' % (aa, bb))) for i in range(self.rank): print(('...... src_gridNew[%d].shape = %s' % (i, str(src_gridNew[i].shape)))) # flag indicating that the grid was extended if reduce(lambda x, y: x + y, [src_dimsNew[i] - src_dims[i] for i in range(self.rank)]) > 0: self.extendedGrid = True # reset src_grid = src_gridNew src_dims = src_dimsNew # Handle a cut in the coordinate system. Run after mkCyclic. # e.g. a tri-polar grid if handleCut and src_bounds is not None: # Test for the presence of a cut. isCut = checkForCoordCut(src_grid, src_dims) if isCut: # No cut src_gridNew, src_dimsNew, dst_Index = handleCoordsCut(src_grid, src_dims, src_bounds) if dst_Index is not None: self.handleCut = True self.extendedGrid = self.extendedGrid else: self.handleCut = False self.extendedGrid = self.extendedGrid if self.verbose: aa, bb = str(src_dims), str(src_dimsNew) print(('... src_dims = %s, after making cyclic src_dimsNew = %s' % (aa, bb))) src_grid = src_gridNew src_dims = src_dimsNew self.dst_Index = dst_Index self.src_dims = (c_int * self.rank)() self.dst_dims = (c_int * self.rank)() # Build coordinate objects src_dimnames = (c_wchar_p * self.rank)() dst_dimnames = (c_wchar_p * self.rank)() for i in range(self.rank): src_dimnames[i] = 'src_n%d' % i dst_dimnames[i] = 'dst_n%d' % i self.src_dims[i] = src_dims[i] self.dst_dims[i] = dst_dims[i] self.src_coordids = (c_int * self.rank)() self.dst_coordids = (c_int * self.rank)() save = 0 standard_name = "" units = "" coordid = c_int(-1) for i in range(self.rank): data = numpy.array(src_grid[i], numpy.float64) self.src_coords.append(data) dataPtr = data.ctypes.data_as(C_DOUBLE_P) name = "src_coord%d" % i # assume [lev,] lat, lon ordering if i == self.rank - 2: standard_name = 'latitude' units = 'degrees_north' elif i == self.rank - 1: standard_name = 'longitude' units = 'degrees_east' status = self.lib.nccf_def_coord(self.rank, self.src_dims, src_dimnames, dataPtr, save, name, standard_name, units, byref(coordid)) catchError(status, sys._getframe().f_lineno) self.src_coordids[i] = coordid data = numpy.array(dst_grid[i], numpy.float64) self.dst_coords.append(data) dataPtr = data.ctypes.data_as(C_DOUBLE_P) name = "dst_coord%d" % i status = self.lib.nccf_def_coord(self.rank, self.dst_dims, dst_dimnames, dataPtr, save, name, standard_name, units, byref(coordid)) catchError(status, sys._getframe().f_lineno) self.dst_coordids[i] = coordid # Build grid objects status = self.lib.nccf_def_grid(self.src_coordids, "src_grid", byref(self.src_gridid)) catchError(status, sys._getframe().f_lineno) status = self.lib.nccf_def_grid(self.dst_coordids, "dst_grid", byref(self.dst_gridid)) catchError(status, sys._getframe().f_lineno) # Create regrid object status = self.lib.nccf_def_regrid(self.src_gridid, self.dst_gridid, byref(self.regridid)) catchError(status, sys._getframe().f_lineno)
[docs] def getPeriodicities(self): """ Get the periodicity lengths of the coordinates Returns ------- numpy array, values inf indicate no periodicity """ coord_periodicity = numpy.zeros((self.rank,), numpy.float64) status = self.lib.nccf_inq_grid_periodicity(self.src_gridid, coord_periodicity.ctypes.data_as(C_DOUBLE_P)) catchError(status, sys._getframe().f_lineno) return coord_periodicity
def __del__(self): """ Destructor, will be called automatically """ status = self.lib.nccf_free_regrid(self.regridid) catchError(status, sys._getframe().f_lineno) status = self.lib.nccf_free_grid(self.src_gridid) catchError(status, sys._getframe().f_lineno) status = self.lib.nccf_free_grid(self.dst_gridid) catchError(status, sys._getframe().f_lineno) for i in range(self.rank): status = self.lib.nccf_free_coord(self.src_coordids[i]) catchError(status, sys._getframe().f_lineno) status = self.lib.nccf_free_coord(self.dst_coordids[i]) catchError(status, sys._getframe().f_lineno)
[docs] def find(self, pattern, path): result = "" for root, dirs, files in os.walk(path): for name in files: if fnmatch.fnmatch(name, pattern): result = os.path.join(root, name) return result
[docs] def setValidMask(self, inMask): """ Set valid mask array for the grid Parameters ---------- inMask flat numpy array of type numpy.int32 or a valid cdms2 variable with its mask set. 0 - invalid, 1 - valid data _: None Note: This must be invoked before computing the weights, the mask is a property of the grid (not the data). """ if self.weightsComputed: raise RegridError('Must set mask before computing weights') mask = numpy.array(inMask, dtype=numpy.int32) # extend src data if grid was made cyclic and or had a cut accounted # for newMask = self._extend(mask) c_intmask = newMask.ctypes.data_as(POINTER(c_int)) status = self.lib.nccf_set_grid_validmask(self.src_gridid, c_intmask) catchError(status, sys._getframe().f_lineno) self.maskSet = True
[docs] def setMask(self, inDataOrMask): """ Set mask array. The mask is defined for nodes Parameters ---------- inDataOrMask cdms2 array or flat mask array, 0 - valid data 1 - invalid data _: None Note: this definition is compatible with the numpy masked arrays Note: note see setValidMask for the opposite definition Note: should be called before computing the weights """ mask = None if hasattr(inDataOrMask, 'getmask'): # cdms2 variable mask = inDataOrMask.getmask() else: # flat mask array mask = inDataOrMask # reversing the meaning 1 == valid, 0 == invalid mask = 1 - numpy.array(inDataOrMask, dtype=numpy.int32) # now calling our own mask setter self.setValidMask(mask)
[docs] def computeWeights(self, nitermax=100, tolpos=1.e-2): """ Compute the the interpolation weights Parameters ---------- nitermax max number of iterations tolpos max tolerance when locating destination positions in index space """ status = self.lib.nccf_compute_regrid_weights(self.regridid, nitermax, c_double(tolpos)) catchError(status, sys._getframe().f_lineno) self.weightsComputed = True
[docs] def apply(self, src_data_in, dst_data, missingValue=None): """ Apply interpolation Parameters ---------- src_data data on source grid dst_data data on destination grid missingValue value that should be set for points falling outside the src domain, pass None if these should not be touched. """ if not self.weightsComputed: raise RegridError('Weights must be set before applying the regrid') # extend src data if grid was made cyclic and or had a cut accounted # for src_data = self._extend(src_data_in) # Check if reduce(operator.iand, [src_data.shape[i] == self.src_dims[i] for i in range(self.rank)]) == False: # noqa raise RegridError(("ERROR in %s: supplied src_data have wrong shape " + "%s != %s") % (__FILE__, str(src_data.shape), str(tuple([d for d in self.src_dims])))) if reduce(operator.iand, [dst_data.shape[i] == self.dst_dims[i] for i in range(self.rank)]) == False: # noqa raise RegridError(("ERROR in %s: supplied dst_data have wrong shape " + "%s != %s") % (__FILE__, str(dst_data.shape), str(tuple([d for d in self.dst_dims])))) # Create temporary data objects src_dataid = c_int(-1) dst_dataid = c_int(-1) save = 0 standard_name = "" units = "" time_dimname = "" status = self.lib.nccf_def_data(self.src_gridid, "src_data", standard_name, units, time_dimname, byref(src_dataid)) catchError(status, sys._getframe().f_lineno) if src_data.dtype != dst_data.dtype: try: # try recasting warnings.warn("mismatch in src and dst data types (%s vs %s) we recasted src to dst" % (src_data.dtype, dst_data.dtype)) src_data = src_data.astype(dst_data.dtype) except BaseException: try: warnings.warn("mismatch in src and dst data types (%s vs %s) we recasted dst to src" % (src_data.dtype, dst_data.dtype)) dst_data = dst_data.astype(src_data.dtype) except BaseException: raise RegridError("ERROR in %s: mismatch in src and dst data types (%s vs %s)" % (__FILE__, src_data.dtype, dst_data.dtype)) # only float64 and float32 data types are supported for interpolation if src_data.dtype == numpy.float64: fill_value = c_double(libCFConfig.NC_FILL_DOUBLE) if missingValue is not None: fill_value = c_double(missingValue) ptr = src_data.ctypes.data_as(POINTER(c_double)) status = self.lib.nccf_set_data_double( src_dataid, ptr, save, fill_value) catchError(status, sys._getframe().f_lineno) elif src_data.dtype == numpy.float32: fill_value = c_float(libCFConfig.NC_FILL_FLOAT) if missingValue is not None: fill_value = c_float(missingValue) ptr = src_data.ctypes.data_as(POINTER(c_float)) status = self.lib.nccf_set_data_float( src_dataid, ptr, save, fill_value) catchError(status, sys._getframe().f_lineno) else: raise RegridError("ERROR in %s: invalid src_data type %s (neither float nor double)" % (__FILE__, src_data.dtype)) status = self.lib.nccf_def_data(self.dst_gridid, "dst_data", standard_name, units, time_dimname, byref(dst_dataid)) catchError(status, sys._getframe().f_lineno) if dst_data.dtype == numpy.float64: fill_value = c_double(libCFConfig.NC_FILL_DOUBLE) if missingValue is not None: fill_value = c_double(missingValue) dst_data[:] = missingValue ptr = dst_data.ctypes.data_as(POINTER(c_double)) status = self.lib.nccf_set_data_double( dst_dataid, ptr, save, fill_value) catchError(status, sys._getframe().f_lineno) elif dst_data.dtype == numpy.float32: fill_value = c_float(libCFConfig.NC_FILL_FLOAT) if missingValue is not None: fill_value = c_float(missingValue) dst_data[:] = missingValue ptr = dst_data.ctypes.data_as(POINTER(c_float)) status = self.lib.nccf_set_data_float( dst_dataid, ptr, save, fill_value) catchError(status, sys._getframe().f_lineno) else: raise RegridError("ERROR in %s: invalid dst_data type = %s" % (__FILE__, dst_data.dtype)) # Now apply weights status = self.lib.nccf_apply_regrid( self.regridid, src_dataid, dst_dataid) catchError(status, sys._getframe().f_lineno) # Clean up status = self.lib.nccf_free_data(src_dataid) catchError(status, sys._getframe().f_lineno) status = self.lib.nccf_free_data(dst_dataid) catchError(status, sys._getframe().f_lineno) return dst_data
def __call__(self, src_data, dst_data, missingValue=None): """ Apply interpolation (synonymous to apply method) Parameters ---------- src_data data on source grid dst_data data on destination grid missingValue value that should be set for points falling outside the src domain, pass None if these should not be touched. """ self.apply(src_data, dst_data, missingValue)
[docs] def getNumValid(self): """ Return the number of valid destination points. Destination points falling outside the source domain, more gnerally, points which could not be located on the source grid, reduce the number of valid points. Returns ------- number of points """ res = c_int(-1) status = self.lib.nccf_inq_regrid_nvalid(self.regridid, byref(res)) catchError(status, sys._getframe().f_lineno) return res.value
[docs] def getNumDstPoints(self): """ Return the number of points on the destination grid Returns ------- number of points """ res = c_int(-1) status = self.lib.nccf_inq_regrid_ntargets(self.regridid, byref(res)) catchError(status, sys._getframe().f_lineno) return res.value
[docs] def getSrcGrid(self): """ Return the source grid Returns ------- grid """ return self.src_coords
[docs] def getDstGrid(self): """ Return the destination grid Returns ------- grid """ return self.dst_coords
[docs] def getIndicesAndWeights(self, dst_indices): """ Get the indices and weights for a single target location Parameters ---------- dst_indices index set on the target grid _: None Returns ------- [index sets on original grid, weights] """ dinds = numpy.array(dst_indices) sinds = (c_int * 2**self.rank)() weights = numpy.zeros((2**self.rank,), numpy.float64) status = self.lib.nccf_inq_regrid_weights(self.regridid, dinds.ctypes.data_as( POINTER(c_double)), sinds, weights.ctypes.data_as(POINTER(c_double))) catchError(status, sys._getframe().f_lineno) # convert the flat indices to index sets ori_inds = [] for i in range(2**self.rank): inx = numpy.zeros((self.rank,), numpy.int32) self.lib.nccf_get_multi_index(self.rank, self.src_dims, sinds[i], inx.ctypes.data_as(POINTER(c_int))) ori_inds.append(inx) return ori_inds, weights
def _extend(self, src_data): """ Extend the data by padding a column and a row, depending on whether the grid was made cyclic and a fold was added or not Parameters ---------- src_data : input source data Returns ------- extended source data (or source input data of no padding was applied) """ # nlatX, nlonX = self.src_dims[-2], self.src_dims[-1] # original dimensions, before extension # assuming ..., lat, lon ordering nlat, nlon = src_data.shape[-2:] # no cut and no cyclic extension src_dataNew = src_data if self.handleCut or self.extendedGrid: # copy data into new, extended container src_dataNew = numpy.zeros(self.src_dims, src_data.dtype) # start filling in... src_dataNew[..., :nlat, :nlon] = src_data[...] if self.handleCut: # fill in polar cut (e.g. tripolar cut), top row # self.dst_Index[i] knows how to fold for i in range(nlon): src_dataNew[..., -1, i] = src_data[..., -2, self.dst_Index[i]] if self.extendedGrid: # make data periodic in longitudes src_dataNew[..., -1] = src_dataNew[..., 0] return src_dataNew def _findIndices(self, targetPos, nitermax, tolpos, dindicesGuess): """ Find the floating point indices Parameters ---------- targetPos numpy array of target positions nitermax max number of iterations tolpos max toelrance in positions dindicesGuess guess for the floating point indices Returns ------- indices, number of iterations, achieved tolerance """ posPtr = targetPos.ctypes.data_as(POINTER(c_double)) adjustFunc = None hit_bounds = numpy.zeros((self.rank), dtype=int).ctypes.data_as(POINTER(c_int)) # no periodicity coord_periodicity = float( 'inf') * numpy.ones((self.rank), targetPos.dtype) coord_periodicity_ptr = coord_periodicity.ctypes.data_as( POINTER(c_double)) res = copy.copy(dindicesGuess) resPtr = res.ctypes.data_as(POINTER(c_double)) src_coords = (POINTER(c_double) * self.rank)() niter = c_int(nitermax) tol = c_double(tolpos) for i in range(self.rank): ptr = self.src_coords[i].ctypes.data_as(POINTER(c_double)) src_coords[i] = ptr status = self.lib.nccf_find_indices_double(self.rank, self.src_dims, src_coords, coord_periodicity_ptr, posPtr, byref(niter), byref(tol), adjustFunc, resPtr, hit_bounds) catchError(status, sys._getframe().f_lineno) return resPtr.contents.value, niter.value, tol.value
######################################################################
[docs]def testMakeCyclic(): y = numpy.array([-90.0 + i * 30.0 for i in range(7)]) x = numpy.array([(i + 0.5) * 60.0 for i in range(6)]) yy = getTensorProduct(y, 0, [len(y), len(x)]) xx = getTensorProduct(x, 1, [len(y), len(x)]) coords = [yy, xx] dims = [len(y), len(x)] newCoords, newDims = makeCoordsCyclic(coords, dims)
# print 'cyclic lats' # print newCoords[0] # print 'cyclic lons' # print newCoords[1]
[docs]def testHandleCut(): import cdms2 # Need tripolar grid filename = "data/so_Omon_GFDL-ESM2M_1pctCO2_r1i1p2_000101-000512_2timesteps.nc" f = cdms2.open(filename) if not f: return # so = f.variables['so'][0, 0, :, :] if 'lon' in list(f.variables.keys()): alllat = f.variables['lat'] alllon = f.variables['lon'] else: alllat = f.getAxis("lat").getData() alllon = f.getAxis("lon").getData() bounds = [f.variables['bounds_lon'][:].data, f.variables['bounds_lat'][:].data] coords = [alllat[:].data, alllon[:].data] dims = alllat.shape newCoords, newDims = makeCoordsCyclic(coords, dims) newCoords, newDims = handleCoordsCut(newCoords, newDims, bounds)
# def testOuterProduct(): # 2d # x = numpy.array([1, 2, 3, 4]) # y = numpy.array([10, 20, 30]) # xx = getTensorProduct(x, 0, [len(x), len(y)]) # yy = getTensorProduct(y, 1, [len(x), len(y)]) # z = numpy.array([100, 200]) # Mixed coordinates and axes # aa = makeCurvilinear([z, yy, xx]) # for g in aa: # print g
[docs]def test(): def func1(coords): return coords[0] * coords[1] + coords[2] def func2(coords): return coords[0] * coords[1] # source grid, tensor product of axes src_x = numpy.array([1, 2, 3, 4, 5, 6]) src_y = numpy.array([10, 20, 30, 40, 50]) src_z = numpy.array([100, 200]) # destination grid, product of axes dst_x = numpy.array([1.5, 2.0, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5]) dst_y = numpy.array([15., 20., 25., 30., 40.]) dst_z = numpy.array([120.0, 180.0, 240.]) # regridding constructor rg = Regrid([src_x, src_y, src_z], [dst_x, dst_y, dst_z]) # rg = Regrid([src_x, src_y], # [dst_x, dst_y]) # initialIndexGuess = numpy.array([0.0, 0.0, 0.0]) # indices = rg._findIndices(numpy.array([1.5, 18.0, 140.0]), # 20, 1.e-2, initialIndexGuess) maxNumIters = 20 posTol = 1.e-3 rg.computeWeights(maxNumIters, posTol) # number of valid points (some destination points may fall # outside the domain) nvalid = rg.getNumValid() # number of destination points ndstpts = rg.getNumDstPoints() print(('nvalid = ', nvalid, ' ndstpts = ', ndstpts)) # get the indices and weights for a single target location dst_indices = [4, 2, 1] inds, weights = rg.getIndicesAndWeights(dst_indices) print(('indices and weights are: ', inds, weights)) # data src_coords = rg.getSrcGrid() dst_coords = rg.getDstGrid() # print 'src_coords = ', src_coords # print 'dst_coords = ', dst_coords src_data = numpy.array(func1(src_coords), numpy.float32) dst_data = -numpy.ones(dst_coords[0].shape, numpy.float32) # regrid rg(src_data, dst_data) print(('after interp: dst_data = ', dst_data)) # check error = numpy.sum(abs(dst_data - func1(dst_coords))) # print dst_data # print func(dst_coords) print(('error = ', error))
[docs]def testMasking(): import numpy.ma as ma def func1(coords): return coords[0] * coords[1] def func2(coords): return coords[0] * coords[1] # source grid, tensor product of axes src_x = ma.masked_array([1, 2, 3, 4, 5, 6], mask=[0, 0, 1, 0, 0, 0]) src_y = ma.masked_array([10, 20, 30, 40, 50], mask=[0, 0, 0, 1, 0]) # destination grid, product of axes dst_x = numpy.array([0.5, 1, 1.5, 2.0, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5]) dst_y = numpy.array([15., 20., 25., 30., 35., 40., 45., 50., 55]) # regridding constructor rg = Regrid([src_x, src_y], [dst_x, dst_y]) # initialIndexGuess = numpy.array([0.0, 0.0, 0.0]) # indices = rg._findIndices(numpy.array([1.5, 18.0, 140.0]), # 20, 1.e-2, initialIndexGuess) # Mask needs to be set before weights are computed mask = rg.getSrcGrid()[0] == 3 mask[:, 3] = True rg.setValidMask(mask) rg.setMask(mask) maxNumIters = 20 posTol = 1.e-2 rg.computeWeights(maxNumIters, posTol) # number of valid points (some destination points may fall # outside the domain) nvalid = rg.getNumValid() # number of destination points ndstpts = rg.getNumDstPoints() print(('nvalid = ', nvalid, ' ndstpts = ', ndstpts)) # get the indices and weights for a single target location dst_indices = [4, 2, 1] inds, weights = rg.getIndicesAndWeights(dst_indices) print(('indices and weights are: ', inds, weights)) # data src_coords = rg.getSrcGrid() dst_coords = rg.getDstGrid() # print 'src_coords = ', src_coords # print 'dst_coords = ', dst_coords src_data = numpy.array(func1(src_coords), numpy.float32) dst_data = -numpy.ones(dst_coords[0].shape, numpy.float32) # regrid rg(src_data, dst_data) print(('after interp: dst_data =\n', dst_data)) # check error = numpy.sum(abs(dst_data - func1(dst_coords))) # print dst_data # print func(dst_coords) print(('error = ', error))
if __name__ == '__main__': # testOuterProduct() test() testMasking() # testMakeCyclic() # testHandleCut()