spharm.spharm

Package spharm :: Module spharm |

# Source Code for Module spharm.spharm

       1  """
       2  Introduction
       3  ============
       4
       5  This module provides a python interface to the NCAR
       6  U{SPHEREPACK<https://www2.cisl.ucar.edu/resources/legacy/spherepack>} library.
       7  It is not a one-to-one wrapper for the SPHEREPACK routines, rather
       8  it provides a simple interface oriented toward working with
       9  atmospheric general circulation model (GCM) data.
      10
      11  Requirements
      12  ============
      13   - U{numpy<http://numeric.scipy.org>}, and a fortran compiler
      14   supported by numpy.f2py.
      15
      16  Installation
      17  ============
      18   - U{Download<http://code.google.com/p/pyspharm/downloads/list>} module source,
      19   untar.
      20   - run C{python setup.py install} (as root if necessary).
      21   The SPHERPACK fortran source files will be downloaded automatically by the
      22   setup.py script, since the SPHEREPACK license prohibits redistribution.
      23   To specify the fortran compiler to use (e.g. g95) run
      24   C{python setup.py config_fc --fcompiler=g95 install}. C{f2py -c --help-fcompiler}
      25   will show you what fortran compilers are available.
      26
      27  Usage
      28  =====
      29
      30  >>> import spharm
      31  >>> x=spharm.Spharmt(144,72,rsphere=8e6,gridtype='gaussian',legfunc='computed')
      32
      33  creates a class instance for spherical harmonic calculations on a 144x72
      34  gaussian grid on a sphere with radius 8000 km. The associated legendre
      35  functions are recomputed on the fly (instead of pre-computed and stored).
      36  Default values of rsphere, gridtype and legfunc are 6.3712e6, 'regular'
      37  and 'stored'. Real-world examples are included in the source distribution.
      38
      39  Class methods
      40  =============
      41   - grdtospec: grid to spectral transform (spherical harmonic analysis).
      42   - spectogrd: spectral to grid transform (spherical harmonic synthesis).
      43   - getuv:  compute u and v winds from spectral coefficients of vorticity
      44   and divergence.
      45   - getvrtdivspec: get spectral coefficients of vorticity and divergence
      46   from u and v winds.
      47   - getgrad: compute the vector gradient given spectral coefficients.
      48   - getpsichi: compute streamfunction and velocity potential from winds.
      49   - specsmooth:  isotropic spectral smoothing.
      50
      51  Functions
      52  =========
      53   - regrid:  spectral re-gridding, with optional spectral smoothing and/or
      54   truncation.
      55   - gaussian_lats_wts: compute gaussian latitudes and weights.
      56   - getspecindx: compute indices of zonal wavenumber and degree
      57   for complex spherical harmonic coefficients.
      58   - legendre: compute associated legendre functions.
      59   - getgeodesicpts: computes the points on the surface of the sphere
      60   corresponding to a twenty-sided (icosahedral) geodesic.
      61   - specintrp: spectral interpolation to an arbitrary point on the sphere.
      62
      63  Conventions
      64  ===========
      65
      66  The gridded data is assumed to be oriented such that i=1 is the
      67  Greenwich meridian and j=1 is the northernmost point. Grid indices
      68  increase eastward and southward. If nlat is odd the equator is included.
      69  If nlat is even the equator will lie half way between points nlat/2
      70  and (nlat/2)+1. nlat must be at least 3. For regular grids
      71  (gridtype='regular') the poles will be included when nlat is odd.
      72  The grid increment in longitude is 2*pi/nlon radians. For example,
      73  nlon = 72 for a five degree grid. nlon must be greater than or
      74  equal to 4. The efficiency of the computation is improved when nlon
      75  is a product of small prime numbers.
      76
      77  The spectral data is assumed to be in a complex array of dimension
      78  (ntrunc+1)*(ntrunc+2)/2. ntrunc is the triangular truncation limit
      79  (ntrunc = 42 for T42). ntrunc must be <= nlat-1. Coefficients are
      80  ordered so that first (nm=0) is m=0,n=0, second is m=0,n=1,
      81  nm=ntrunc is m=0,n=ntrunc, nm=ntrunc+1 is m=1,n=1, etc.
      82  The values of m (degree) and n (order) as a function of the index
      83  nm are given by the arrays indxm, indxn returned by getspecindx.
      84
      85  The associated legendre polynomials are normalized so that the
      86  integral (pbar(n,m,theta)**2)*sin(theta) on the interval theta=0 to pi
      87  is 1, where pbar(m,n,theta)=sqrt((2*n+1)*factorial(n-m)/(2*factorial(n+m)))*
      88  sin(theta)**m/(2**n*factorial(n)) times the (n+m)th derivative of
      89  (x**2-1)**n with respect to x=cos(theta).
      90  theta = pi/2 - phi, where phi is latitude and theta is colatitude.
      91  Therefore, cos(theta) = sin(phi) and sin(theta) = cos(phi).
      92  Note that pbar(0,0,theta)=sqrt(2)/2, and pbar(1,0,theta)=.5*sqrt(6)*sin(lat).
      93
      94  The default grid type is regular (equally spaced latitude points).
      95  Set gridtype='gaussian' when creating a class instance
      96  for gaussian latitude points.
      97
      98  Quantities needed to compute spherical harmonics are precomputed and stored
      99  when the class instance is created with legfunc='stored' (the default).
     100  If legfunc='computed', they are recomputed on the fly on each method call.
     101  The storage requirements for legfunc="stored" increase like nlat**2, while
     102  those for legfunc='stored' increase like nlat**3.  However, for
     103  repeated method invocations on a single class instance, legfunc="stored"
     104  will always be faster.
     105
     106  @contact: U{Jeff Whitaker<mailto:jeffrey.s.whitaker@noaa.gov>}
     107
     108  @version: 1.0.7
     109
     110  @license: Permission to use, copy, modify, and distribute this software and its
     111  documentation for any purpose and without fee is hereby granted,
     112  provided that the above copyright notice appear in all copies and that
     113  both that copyright notice and this permission notice appear in
     114  supporting documentation.
     115  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
     116  INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
     117  EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
     118  CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
     119  USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
     120  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
     121  PERFORMANCE OF THIS SOFTWARE.
     122  """
     123  import _spherepack, numpy, math, sys
     124
     125  # define a list of instance variables that cannot be rebound
     126  # or unbound.
     127  _private_vars = ['nlon','nlat','gridtype','legfunc','rsphere']
     128  __version__ = '1.0.9'
     129
     130 -class Spharmt:
     131      """
     132   spherical harmonic transform class.
     133
     134   @ivar nlat: number of latitudes (set when class instance is created,
     135   cannot be changed).
     136
     137   @ivar nlon: number of longitudes (set when class instance is created,
     138   cannot be changed).
     139
     140   @ivar rsphere: The radius of the sphere in meters (set when class
     141   instance is created, cannot be changed).
     142
     143   @ivar legfunc: 'stored' or 'computed'.  If 'stored',
     144   associated legendre functions are precomputed and stored when the
     145   class instance is created.  If 'computed', associated
     146   legendre functions are computed on the fly when transforms are
     147   requested. Set when class instance is created, cannot be changed.
     148
     149   @ivar gridtype: 'regular' (equally spaced in longitude and latitude)
     150   or 'gaussian' (equally spaced in longitude, latitudes located at
     151   roots of ordinary Legendre polynomial of degree nlat). Set when class
     152   instance is created, cannot be changed.
     153      """
     154
     155 -    def __setattr__(self, key, val):
     156          """
     157  prevent modification of read-only instance variables.
     158          """
     159          if key in self.__dict__ and key in _private_vars:
     160              raise AttributeError('Attempt to rebind read-only instance variable '+key)
     161          else:
     162              self.__dict__[key] = val
     163
     164 -    def __delattr__(self, key):
     165          """
     166  prevent deletion of read-only instance variables.
     167          """
     168          if key in self.__dict__ and key in _private_vars:
     169              raise AttributeError('Attempt to unbind read-only instance variable '+key)
     170          else:
     171              del self.__dict__[key]
     172
     173 -    def __init__(self, nlon, nlat, rsphere=6.3712e6, gridtype='regular', legfunc='stored'):
     174          """
     175   create a Spharmt class instance.
     176
     177   @param nlon: Number of longitudes. The grid must be oriented from
     178   east to west, with the first point at the Greenwich meridian
     179   and the last point at 360-delta degrees east
     180   (where delta = 360/nlon degrees). Must be >= 4. Transforms will
     181   be faster when nlon is the product of small primes.
     182
     183   @param nlat: Number of latitudes.  The grid must be oriented from north
     184   to south. If nlat is odd the equator is included.
     185   If nlat is even the equator will lie half way between points
     186   points nlat/2 and (nlat/2)+1. Must be >=3.
     187
     188   @keyword rsphere: The radius of the sphere in meters.
     189   Default 6371200 (the value for Earth).
     190
     191   @keyword legfunc: 'stored' (default) or 'computed'.  If 'stored',
     192   associated legendre functions are precomputed and stored when the
     193   class instance is created.  This uses O(nlat**3) memory, but
     194   speeds up the spectral transforms.  If 'computed', associated
     195   legendre functions are computed on the fly when transforms are
     196   requested.  This uses O(nlat**2) memory, but slows down the spectral
     197   transforms a bit.
     198
     199   @keyword gridtype: 'regular' (default) or 'gaussian'. Regular grids
     200   will include the poles and equator if nlat is odd.  Gaussian
     201   grids never include the poles, but will include the equator if
     202   nlat is odd.
     203
     204          """
     205  # sanity checks.
     206          if rsphere > 0.0:
     207              self.rsphere= rsphere
     208          else:
     209              msg = 'Spharmt.__init__ illegal value of rsphere (%s) - must be postitive' % (rsphere)
     210              raise ValueError(msg)
     211          if nlon > 3:
     212              self.nlon = nlon
     213          else:
     214              msg = 'Spharmt.__init__ illegal value of nlon (%s) - must be at least 4' % (nlon,)
     215              raise ValueError(msg)
     216          if nlat > 2:
     217              self.nlat = nlat
     218          else:
     219              msg = 'Spharmt.__init__ illegal value of nlat (%s) - must be at least 3' % (nlat,)
     220              raise ValueError(msg)
     221          if gridtype != 'regular' and gridtype != 'gaussian':
     222              msg = 'Spharmt.__init__ illegal value of gridtype (%s) - must be either "gaussian" or "regular"' % gridtype
     223              raise ValueError(msg)
     224          else:
     225              self.gridtype = gridtype
     226
     227          if legfunc != 'computed' and legfunc != 'stored':
     228              msg = 'Spharmt.__init__ illegal value of legfunc (%s) - must be either "computed" or "stored"' % legfunc
     229              raise ValueError(msg)
     230          else:
     231              self.legfunc = legfunc
     232
     233          if nlon%2:                              # nlon is odd
     234              n1 = min(nlat, (nlon + 1)/2)
     235          else:
     236              n1 = min(nlat, (nlon + 2)/2)
     237          if nlat%2:                              # nlat is odd
     238              n2 = (nlat + 1)/2
     239          else:
     240              n2 = nlat/2
     241
     242          if gridtype == 'regular':
     243              if legfunc == 'stored':
     244                  lshaes = (n1*n2*(nlat + nlat - n1 + 1))/2 + nlon + 15
     245                  lwork = 5*nlat*n2 + 3*((n1 - 2)*(nlat + nlat - n1 -1))/2
     246                  wshaes, ierror = _spherepack.shaesi(nlat, nlon, lshaes, lwork, nlat+1)
     247                  if ierror != 0:
     248                      msg = 'In return from call to shaesi in Spharmt.__init__ ierror =  %d' % ierror
     249                      raise ValueError(msg)
     250                  self.wshaes = wshaes
     251                  lshses = lshaes
     252                  wshses, ierror = _spherepack.shsesi(nlat, nlon, lshses, lwork, nlat+1)
     253                  if ierror != 0:
     254                      msg = 'In return from call to shsesi in Spharmt.__init__ ierror =  %d' % ierror
     255                      raise ValueError(msg)
     256                  self.wshses = wshses
     257                  lvhaes = n1*n2*(nlat + nlat - n1 + 1) + nlon + 15
     258                  lwork = 3*(max(n1 -2,0)*(nlat + nlat - n1 - 1))/2 + 5*n2*nlat
     259                  wvhaes, ierror = _spherepack.vhaesi(nlat, nlon, lvhaes, lwork, 2*(nlat+1))
     260                  if ierror != 0:
     261                      msg = 'In return from call to vhaesi in Spharmt.__init__ ierror =  %d' % ierror
     262                      raise ValueError(msg)
     263                  self.wvhaes = wvhaes
     264                  lwork = 3*(max(n1 - 2,0)*(nlat + nlat - n1 -1))/2 + 5*n2*nlat
     265                  lvhses = n1*n2*(nlat + nlat - n1 + 1) + nlon + 15
     266                  wvhses, ierror = _spherepack.vhsesi(nlat,nlon,lvhses,lwork,2*(nlat+1))
     267                  if ierror != 0:
     268                      msg = 'In return from call to vhsesi in Spharmt.__init__ ierror =  %d' % ierror
     269                      raise ValueError(msg)
     270                  self.wvhses = wvhses
     271              else:
     272                  lshaec = 2*nlat*n2+3*((n1-2)*(nlat+nlat-n1-1))/2+nlon+15
     273                  wshaec, ierror = _spherepack.shaeci(nlat, nlon, lshaec, 2*(nlat+1))
     274                  if ierror != 0:
     275                      msg = 'In return from call to shaeci in Spharmt.__init__ ierror =  %d' % ierror
     276                      raise ValueError(msg)
     277                  self.wshaec = wshaec
     278                  lshsec = lshaec
     279                  wshsec, ierror = _spherepack.shseci(nlat, nlon, lshsec, 2*(nlat+1))
     280                  if ierror != 0:
     281                      msg = 'In return from call to shseci in Spharmt.__init__ ierror =  %d' % ierror
     282                      raise ValueError(msg)
     283                  self.wshsec = wshsec
     284                  lvhaec = 4*nlat*n2+3*max(n1-2,0)*(2*nlat-n1-1)+nlon+15
     285                  wvhaec, ierror = _spherepack.vhaeci(nlat, nlon, lvhaec,  2*(nlat+1))
     286                  if ierror != 0:
     287                      msg = 'In return from call to vhaeci in Spharmt.__init__ ierror =  %d' % ierror
     288                      raise ValueError(msg)
     289                  self.wvhaec = wvhaec
     290                  lvhsec = lvhaec
     291                  wvhsec, ierror = _spherepack.vhseci(nlat, nlon, lvhsec,  2*(nlat+1))
     292                  if ierror != 0:
     293                      msg = 'In return from call to vhseci in Spharmt.__init__ ierror =  %d' % ierror
     294                      raise ValueError(msg)
     295                  self.wvhsec = wvhsec
     296
     297
     298          elif gridtype == 'gaussian':
     299              if legfunc == 'stored':
     300                  lshags = nlat*(3*(n1 + n2) - 2) + (n1 - 1)*(n2*(2*nlat - n1) - 3*n1)/2 + nlon + 15
     301                  lwork = 4*nlat*(nlat + 2) + 2
     302                  ldwork = nlat*(nlat + 4)
     303                  wshags, ierror = _spherepack.shagsi(nlat, nlon, lshags, lwork, ldwork)
     304                  if ierror != 0:
     305                      msg = 'In return from call to shagsi in Spharmt.__init__ ierror =  %d' % ierror
     306                      raise ValueError(msg)
     307                  self.wshags = wshags
     308                  lshsgs = lshags
     309                  wshsgs, ierror = _spherepack.shsgsi(nlat, nlon, lshsgs, lwork, ldwork)
     310                  if ierror != 0:
     311                      msg = 'In return from call to shsgsi in Spharmt.__init__ ierror =  %d' % ierror
     312                      raise ValueError(msg)
     313                  self.wshsgs = wshsgs
     314                  lvhags = (nlat +1)*(nlat + 1)*nlat/2 +nlon + 15
     315                  ldwork = (3*nlat*(nlat + 3) + 2)/2
     316                  wvhags, ierror = _spherepack.vhagsi(nlat, nlon, lvhags, ldwork)
     317                  if ierror != 0:
     318                      msg = 'In return from call to vhagsi in Spharmt.__init__ ierror =  %d' % ierror
     319                      raise ValueError(msg)
     320                  self.wvhags = wvhags
     321                  lvhsgs = n1*n2*(nlat + nlat - n1 +1) + nlon + 15 + 2*nlat
     322                  ldwork = (3*nlat*(nlat + 3) + 2)/2
     323                  wvhsgs, ierror = _spherepack.vhsgsi(nlat, nlon, lvhsgs, ldwork)
     324                  if ierror != 0:
     325                      msg = 'In return from call to vhsgsi in Spharmt.__init__ ierror =  %d' % ierror
     326                      raise ValueError(msg)
     327                  self.wvhsgs = wvhsgs
     328              else:
     329                  lshagc = nlat*(2*n2+3*n1-2)+3*n1*(1-n1)/2+nlon+15
     330                  wshagc, ierror = _spherepack.shagci(nlat, nlon, lshagc, nlat*(nlat+4))
     331                  if ierror != 0:
     332                      msg = 'In return from call to shagci in Spharmt.__init__ ierror =  %d' % ierror
     333                      raise ValueError(msg)
     334                  self.wshagc = wshagc
     335                  lshsgc = lshagc
     336                  wshsgc, ierror = _spherepack.shsgci(nlat, nlon, lshsgc, nlat*(nlat+4))
     337                  if ierror != 0:
     338                      msg = 'In return from call to shsgci in Spharmt.__init__ ierror =  %d' % ierror
     339                      raise ValueError(msg)
     340                  self.wshsgc = wshsgc
     341                  lvhagc = 4*nlat*n2+3*max(n1-2,0)*(2*nlat-n1-1)+nlon+n2+15
     342                  ldwork = 2*nlat*(nlat+1)+1
     343                  wvhagc, ierror = _spherepack.vhagci(nlat, nlon, lvhagc, ldwork)
     344                  if ierror != 0:
     345                      msg = 'In return from call to vhagci in Spharmt.__init__ ierror =  %d' % ierror
     346                      raise ValueError(msg)
     347                  self.wvhagc = wvhagc
     348                  lvhsgc = 4*nlat*n2+3*max(n1-2,0)*(2*nlat-n1-1)+nlon+15
     349                  wvhsgc, ierror = _spherepack.vhsgci(nlat, nlon, lvhsgc, ldwork)
     350                  if ierror != 0:
     351                      msg = 'In return from call to vhsgci in Spharmt.__init__ ierror =  %d' % ierror
     352                      raise ValueError(msg)
     353                  self.wvhsgc = wvhsgc
     354
     355 -    def grdtospec(self, datagrid, ntrunc=None):
     356          """
     357   grid to spectral transform (spherical harmonic analysis).
     358
     359   @param datagrid: rank 2 or 3 numpy float32 array with shape (nlat,nlon) or
     360   (nlat,nlon,nt), where nt is the number of grids to be transformed.  If
     361   datagrid is rank 2, nt is assumed to be 1.
     362
     363   @keyword ntrunc:  optional spectral truncation limit.
     364   (default self.nlat-1)
     365
     366   @return: C{B{dataspec}} - rank 1 or 2 numpy complex array with shape
     367   (ntrunc+1)*(ntrunc+2)/2 or ((ntrunc+1)*(ntrunc+2)/2,nt) containing
     368   complex spherical harmonic coefficients resulting from the spherical
     369   harmonic analysis of datagrid.
     370          """
     371
     372  # check that datagrid is rank 2 or 3 with size (self.nlat, self.nlon) or
     373  # (self.nlat, self.nlon, nt) where nt is number of grids to transform.
     374
     375          idim = datagrid.ndim
     376
     377          if len(datagrid.shape) > 3:
     378              msg = 'grdtospec needs a rank two or three array, got %d' % (len(datagrid.shape),)
     379              raise ValueError(msg)
     380
     381          if datagrid.shape[0] != self.nlat or datagrid.shape[1] != self.nlon:
     382              msg = 'grdtospec needs an array of size %d by %d, got %d by %d' % (self.nlat, self.nlon, datagrid.shape[0], datagrid.shape[1],)
     383              raise ValueError(msg)
     384
     385  # check ntrunc.
     386
     387          if ntrunc is None:
     388              ntrunc = self.nlat-1
     389
     390          if ntrunc < 0 or ntrunc+1 > datagrid.shape[0]:
     391              msg = 'ntrunc must be between 0 and %d' % (datagrid.shape[0]-1,)
     392              raise ValueError(msg)
     393
     394
     395          nlat = self.nlat
     396          nlon = self.nlon
     397          if nlat%2:                              # nlat is odd
     398              n2 = (nlat + 1)/2
     399          else:
     400              n2 = nlat/2
     401
     402          if len(datagrid.shape) == 2:
     403              nt = 1
     404              datagrid = numpy.reshape(datagrid, (nlat,nlon,1))
     405          else:
     406              nt = datagrid.shape[2]
     407
     408          if self.gridtype == 'regular':
     409
     410  # do grid to spectral transform.
     411
     412              if self.legfunc == 'stored':
     413                  lwork = (nt+1)*nlat*nlon
     414                  a,b,ierror = _spherepack.shaes(datagrid,self.wshaes,lwork)
     415                  if ierror != 0:
     416                      msg = 'In return from call to shaes in Spharmt.grdtospec ierror =  %d' % ierror
     417                      raise ValueError(msg)
     418              else:
     419                  lwork = nlat*(nt*nlon+max(3*n2,nlon))
     420                  a,b,ierror = _spherepack.shaec(datagrid,self.wshaec,lwork)
     421                  if ierror != 0:
     422                      msg = 'In return from call to shaec in Spharmt.grdtospec ierror =  %d' % ierror
     423
     424                      raise ValueError(msg)
     425
     426  # gaussian grid.
     427
     428          elif self.gridtype == 'gaussian':
     429
     430  # do grid to spectral transform.
     431
     432              if self.legfunc == 'stored':
     433                  lwork = nlat*nlon*(nt+1)
     434                  a,b,ierror = _spherepack.shags(datagrid,self.wshags,lwork)
     435                  if ierror != 0:
     436                      msg = 'In return from call to shags in Spharmt.grdtospec ierror =  %d' % ierror
     437                      raise ValueError(msg)
     438              else:
     439                  lwork = nlat*(nlon*nt+max(3*n2,nlon))
     440                  a,b,ierror = _spherepack.shagc(datagrid,self.wshagc,lwork)
     441                  if ierror != 0:
     442                      msg = 'In return from call to shagc in Spharmt.grdtospec ierror =  %d' % ierror
     443                      raise ValueError(msg)
     444
     445  # convert 2d real and imag spectral arrays into 1d complex array.
     446
     447          dataspec = _spherepack.twodtooned(a,b,ntrunc)
     448
     449          if idim == 2:
     450              return numpy.squeeze(dataspec)
     451          else:
     452              return dataspec
     453
     454 -    def spectogrd(self, dataspec):
     455
     456          """
     457   spectral to grid transform (spherical harmonic synthesis).
     458
     459   @param dataspec: rank 1 or 2 numpy complex array with shape
     460   (ntrunc+1)*(ntrunc+2)/2 or ((ntrunc+1)*(ntrunc+2)/2,nt) containing
     461   complex spherical harmonic coefficients (where ntrunc is the
     462   triangular truncation limit and nt is the number of spectral arrays
     463   to be transformed). If dataspec is rank 1, nt is assumed to be 1.
     464
     465   @return: C{B{datagrid}} - rank 2 or 3 numpy float32 array with shape
     466   (nlat,nlon) or (nlat,nlon,nt) containing the gridded data resulting from
     467   the spherical harmonic synthesis of dataspec.
     468          """
     469
     470  # make sure dataspec is rank 1 or 2.
     471
     472          idim = dataspec.ndim
     473
     474          if len(dataspec.shape) > 2:
     475              msg = 'spectogrd needs a rank one or two array, got %d' % (len(dataspec.shape),)
     476              raise ValueError(msg)
     477
     478          nlat = self.nlat
     479          nlon = self.nlon
     480          if nlat%2:                              # nlat is odd
     481              n2 = (nlat + 1)/2
     482          else:
     483              n2 = nlat/2
     484
     485          if len(dataspec.shape) == 1:
     486              nt = 1
     487              dataspec = numpy.reshape(dataspec, (dataspec.shape[0],1))
     488          else:
     489              nt = dataspec.shape[1]
     490
     491          ntrunc = int(-1.5 + 0.5*math.sqrt(9.-8.*(1.-dataspec.shape[0])))
     492          if ntrunc > nlat-1:
     493              msg = 'ntrunc too large - can be max of %d, got %d' % (nlat-1,ntrunc)
     494              raise ValueError(msg)
     495
     496          a, b = _spherepack.onedtotwod(dataspec,nlat)
     497
     498  # regular grid.
     499
     500          if self.gridtype == 'regular':
     501
     502  # do spectral to grid transform.
     503
     504              if self.legfunc == 'stored':
     505                  lwork = (nt+1)*nlat*nlon
     506                  datagrid, ierror = _spherepack.shses(nlon,a,b,self.wshses,lwork)
     507                  if ierror != 0:
     508                      msg = 'In return from call to shses in Spharmt.spectogrd ierror =  %d' % ierror
     509                      raise ValueError(msg)
     510              else:
     511                  lwork = nlat*(nt*nlon+max(3*n2,nlon))
     512                  datagrid, ierror = _spherepack.shsec(nlon,a,b,self.wshsec,lwork)
     513                  if ierror != 0:
     514                      msg = 'In return from call to shsec in Spharmt.spectogrd ierror =  %d' % ierror
     515                      raise ValueError(msg)
     516
     517  # gaussian grid.
     518
     519          elif self.gridtype == 'gaussian':
     520
     521  # do spectral to grid transform.
     522
     523              if self.legfunc == 'stored':
     524                  lwork = nlat*nlon*(nt+1)
     525                  datagrid, ierror = _spherepack.shsgs(nlon,a,b,self.wshsgs,lwork)
     526                  if ierror != 0:
     527                      msg = 'In return from call to shsgs in Spharmt.spectogrd ierror =  %d' % ierror
     528                      raise ValueError(msg)
     529              else:
     530                  lwork = nlat*(nlon*nt+max(3*n2,nlon))
     531                  datagrid, ierror = _spherepack.shsgc(nlon,a,b,self.wshsgc,lwork)
     532                  if ierror != 0:
     533                      msg = 'In return from call to shsgc in Spharmt.spectogrd ierror =  %d' % ierror
     534                      raise ValueError(msg)
     535
     536          if idim == 1:
     537              return numpy.squeeze(datagrid)
     538          else:
     539              return datagrid
     540
     541 -    def getvrtdivspec(self, ugrid, vgrid, ntrunc=None):
     542
     543          """
     544   compute spectral coefficients of vorticity and divergence given vector wind.
     545
     546   @param ugrid: rank 2 or 3 numpy float32 array containing grid of zonal
     547   winds.  Must have shape (nlat,nlon) or (nlat,nlon,nt), where nt is the number
     548   of grids to be transformed.  If ugrid is rank 2, nt is assumed to be 1.
     549
     550   @param vgrid: rank 2 or 3 numpy float32 array containing grid of meridional
     551   winds.  Must have shape (nlat,nlon) or (nlat,nlon,nt), where nt is the number
     552   of grids to be transformed.  Both ugrid and vgrid must have the same shape.
     553
     554   @keyword ntrunc:  optional spectral truncation limit.
     555   (default self.nlat-1)
     556
     557   @return: C{B{vrtspec, divspec}} - rank 1 or 2 numpy complex arrays
     558   of vorticity and divergence spherical harmonic coefficients with shape
     559   shape (ntrunc+1)*(ntrunc+2)/2 or ((ntrunc+1)*(ntrunc+2)/2,nt).
     560          """
     561
     562  # make sure ugrid,vgrid are rank 2 or 3 and same shape.
     563
     564          idim = ugrid.ndim
     565
     566          shapeu = ugrid.shape
     567          shapev = vgrid.shape
     568
     569          if ntrunc is None:
     570              ntrunc = self.nlat-1
     571
     572          if shapeu != shapev:
     573              msg = 'getvrtdivspec input arrays must be same shape!'
     574              raise ValueError(msg)
     575
     576
     577          if len(shapeu) !=2 and len(shapeu) !=3:
     578              msg = 'getvrtdivspec needs rank two or three arrays!'
     579              raise ValueError(msg)
     580
     581          if shapeu[0] != self.nlat or shapeu[1] != self.nlon:
     582              msg = 'getvrtdivspec needs input arrays whose first two dimensions are si%d and %d, got %d and %d' % (self.nlat, self.nlon, ugrid.shape[0], ugrid.shape[1],)
     583              raise ValueError(msg)
     584
     585
     586  # check ntrunc.
     587
     588          if ntrunc < 0 or ntrunc+1 > shapeu[0]:
     589              msg = 'ntrunc must be between 0 and %d' % (ugrid.shape[0]-1,)
     590              raise ValueError(msg)
     591
     592          nlat = self.nlat
     593          nlon = self.nlon
     594          if nlat%2:                              # nlat is odd
     595              n2 = (nlat + 1)/2
     596          else:
     597              n2 = nlat/2
     598          rsphere= self.rsphere
     599
     600  # convert from geographical to math coordinates, add extra dimension
     601  # if necessary.
     602
     603          if len(shapeu) == 2:
     604              nt = 1
     605              w = numpy.reshape(ugrid, (nlat,nlon,1))
     606              v = -numpy.reshape(vgrid, (nlat,nlon,1))
     607          else:
     608              nt = shapeu[2]
     609              w = ugrid
     610              v = -vgrid
     611
     612  # regular grid.
     613
     614          if self.gridtype == 'regular':
     615
     616  # vector harmonic analysis.
     617
     618              if self.legfunc == 'stored':
     619                  lwork = (2*nt+1)*nlat*nlon
     620                  br,bi,cr,ci,ierror = _spherepack.vhaes(v,w,self.wvhaes,lwork)
     621                  if ierror != 0:
     622                      msg = 'In return from call to vhaes in Spharmt.getvrtdivspec ierror =  %d' % ierror
     623                      raise ValueError(msg)
     624              else:
     625                  lwork = nlat*(2*nt*nlon+max(6*n2,nlon))
     626                  br,bi,cr,ci,ierror = _spherepack.vhaec(v,w,self.wvhaec,lwork)
     627                  if ierror != 0:
     628                      msg = 'In return from call to vhaec in Spharmt.getvrtdivspec ierror =  %d' % ierror
     629                      raise ValueError(msg)
     630
     631  # gaussian grid.
     632
     633          elif self.gridtype == 'gaussian':
     634
     635  # vector harmonic analysis.
     636
     637              if self.legfunc == 'stored':
     638                  lwork = (2*nt+1)*nlat*nlon
     639                  br,bi,cr,ci,ierror = _spherepack.vhags(v,w,self.wvhags,lwork)
     640                  if ierror != 0:
     641                      msg = 'In return from call to vhags in Spharmt.getvrtdivspec ierror =  %d' % ierror
     642                      raise ValueError(msg)
     643              else:
     644                  lwork = 2*nlat*(2*nlon*nt+3*n2)
     645                  br,bi,cr,ci,ierror = _spherepack.vhagc(v,w,self.wvhagc,lwork)
     646                  if ierror != 0:
     647                      msg = 'In return from call to vhagc in Spharmt.getvrtdivspec ierror =  %d' % ierror
     648                      raise ValueError(msg)
     649
     650  # convert vector harmonic coeffs to 1d complex coefficients
     651  # of vorticity and divergence.
     652
     653          vrtspec, divspec = _spherepack.twodtooned_vrtdiv(br,bi,cr,ci,ntrunc,rsphere)
     654
     655          if idim == 2:
     656              return numpy.squeeze(vrtspec), numpy.squeeze(divspec)
     657          else:
     658              return vrtspec, divspec
     659
     660 -    def getuv(self, vrtspec, divspec):
     661
     662          """
     663   compute vector wind on grid given complex spectral coefficients
     664   of vorticity and divergence.
     665
     666   @param vrtspec: rank 1 or 2 numpy complex array of vorticity spectral
     667   coefficients, with shape (ntrunc+1)*(ntrunc+2)/2 or
     668   ((ntrunc+1)*(ntrunc+2)/2,nt) (where ntrunc is the triangular truncation
     669   and nt is the number of spectral arrays to be transformed).
     670   If vrtspec is rank 1, nt is assumed to be 1.
     671
     672   @param divspec: rank 1 or 2 numpy complex array of divergence spectral
     673   coefficients, with shape (ntrunc+1)*(ntrunc+2)/2 or
     674   ((ntrunc+1)*(ntrunc+2)/2,nt) (where ntrunc is the triangular truncation
     675   and nt is the number of spectral arrays to be transformed).
     676   Both vrtspec and divspec must have the same shape.
     677
     678   @return: C{B{ugrid, vgrid}} - rank 2 or 3 numpy float32 arrays containing
     679   gridded zonal and meridional winds. Shapes are either (nlat,nlon) or
     680   (nlat,nlon,nt).
     681          """
     682
     683          idim = vrtspec.ndim
     684
     685          shapevrt = vrtspec.shape
     686          shapediv = divspec.shape
     687
     688  # make sure vrtspec, divspec are rank 1 or 2, and have the same shape.
     689
     690          if shapevrt != shapediv:
     691              msg = 'vrtspec, divspec must be same size in getuv!'
     692              raise ValueError(msg)
     693
     694          if len(shapevrt) !=1 and len(shapevrt) !=2:
     695              msg = 'getuv needs rank one or two input arrays!'
     696              raise ValueError(msg)
     697
     698  # infer ntrunc from size of dataspec (dataspec must be rank 1!)
     699  # dataspec is assumed to have size (ntrunc+1)*(ntrunc+2)/2
     700
     701          ntrunc = int(-1.5 + 0.5*math.sqrt(9.-8.*(1.-vrtspec.shape[0])))
     702
     703          nlat = self.nlat
     704          nlon = self.nlon
     705          if nlat%2:                              # nlat is odd
     706              n2 = (nlat + 1)/2
     707          else:
     708              n2 = nlat/2
     709          rsphere= self.rsphere
     710
     711          if len(vrtspec.shape) == 1:
     712              nt = 1
     713              vrtspec = numpy.reshape(vrtspec, (vrtspec.shape[0],1))
     714              divspec = numpy.reshape(divspec, (divspec.shape[0],1))
     715          else:
     716              nt = vrtspec.shape[1]
     717
     718  # convert 1d complex arrays of vort, div to 2d vector harmonic arrays.
     719
     720          br,bi,cr,ci = _spherepack.onedtotwod_vrtdiv(vrtspec,divspec,nlat,rsphere)
     721
     722  # regular grid.
     723
     724          if self.gridtype == 'regular':
     725
     726  # vector harmonic synthesis.
     727
     728              if self.legfunc == 'stored':
     729                  lwork = (2*nt+1)*nlat*nlon
     730                  v, w, ierror = _spherepack.vhses(nlon,br,bi,cr,ci,self.wvhses,lwork)
     731                  if ierror != 0:
     732                      msg = 'In return from call to vhses in Spharmt.getuv ierror =  %d' % ierror
     733                      raise ValueError(msg)
     734              else:
     735                  lwork = nlat*(2*nt*nlon+max(6*n2,nlon))
     736                  v, w, ierror = _spherepack.vhsec(nlon,br,bi,cr,ci,self.wvhsec,lwork)
     737                  if ierror != 0:
     738                      msg = 'In return from call to vhsec in Spharmt.getuv ierror =  %d' % ierror
     739                      raise ValueError(msg)
     740
     741  # gaussian grid.
     742
     743          elif self.gridtype == 'gaussian':
     744
     745  # vector harmonic synthesis.
     746
     747              if self.legfunc == 'stored':
     748                  lwork = (2*nt+1)*nlat*nlon
     749                  v, w, ierror = _spherepack.vhsgs(nlon,br,bi,cr,ci,self.wvhsgs,lwork)
     750                  if ierror != 0:
     751                      msg = 'In return from call to vhsgs in Spharmt.getuv ierror =  %d' % ierror
     752                      raise ValueError(msg)
     753              else:
     754                  lwork = nlat*(2*nt*nlon+max(6*n2,nlon))
     755                  v, w, ierror = _spherepack.vhsgc(nlon,br,bi,cr,ci,self.wvhsgc,lwork)
     756                  if ierror != 0:
     757                      msg = 'In return from call to vhsgc in Spharmt.getuv ierror =  %d' % ierror
     758                      raise ValueError(msg)
     759
     760  # convert to u and v in geographical coordinates.
     761
     762          if idim == 1:
     763              return numpy.reshape(w, (nlat,nlon)), -numpy.reshape(v, (nlat,nlon))
     764          else:
     765              return w,-v
     766
     767 -    def getpsichi(self, ugrid, vgrid, ntrunc=None):
     768
     769          """
     770   compute streamfunction and velocity potential on grid given vector wind.
     771
     772   @param ugrid: rank 2 or 3 numpy float32 array containing grid of zonal
     773   winds.  Must have shape (nlat,nlon) or (nlat,nlon,nt), where nt is the number
     774   of grids to be transformed.  If ugrid is rank 2, nt is assumed to be 1.
     775
     776   @param vgrid: rank 2 or 3 numpy float32 array containing grid of meridional
     777   winds.  Must have shape (nlat,nlon) or (nlat,nlon,nt), where nt is the number
     778   of grids to be transformed.  Both ugrid and vgrid must have the same shape.
     779
     780   @keyword ntrunc:  optional spectral truncation limit.
     781   (default self.nlat-1)
     782
     783   @return: C{B{psigrid, chigrid}} - rank 2 or 3 numpy float32 arrays
     784   of gridded streamfunction and velocity potential. Shapes are either
     785   (nlat,nlon) or (nlat,nlon,nt).
     786          """
     787
     788  # make sure ugrid,vgrid are rank 2 or 3 and same shape.
     789
     790          idim = ugrid.ndim
     791
     792          shapeu = ugrid.shape
     793          shapev = vgrid.shape
     794
     795          if ntrunc is None:
     796              ntrunc = self.nlat-1
     797
     798          if shapeu != shapev:
     799              msg = 'getvrtdivspec input arrays must be same shape!'
     800              raise ValueError(msg)
     801
     802
     803          if len(shapeu) !=2 and len(shapeu) !=3:
     804              msg = 'getvrtdivspec needs rank two or three arrays!'
     805              raise ValueError(msg)
     806
     807          if shapeu[0] != self.nlat or shapeu[1] != self.nlon:
     808              msg = 'getpsichi needs input arrays whose first two dimensions are si%d and %d, got %d and %d' % (self.nlat, self.nlon, ugrid.shape[0], ugrid.shape[1],)
     809              raise ValueError(msg)
     810
     811  # check ntrunc.
     812
     813          if ntrunc < 0 or ntrunc+1 > ugrid.shape[0]:
     814              msg = 'ntrunc must be between 0 and %d' % (ugrid.shape[0]-1,)
     815              raise ValueError(msg)
     816
     817  # compute spectral coeffs of vort, div.
     818
     819          vrtspec, divspec = self.getvrtdivspec(ugrid, vgrid, ntrunc)
     820
     821  # number of grids to compute.
     822
     823          if len(vrtspec.shape) == 1:
     824              nt = 1
     825              vrtspec = numpy.reshape(vrtspec, ((ntrunc+1)*(ntrunc+2)/2,1))
     826              divspec = numpy.reshape(divspec, ((ntrunc+1)*(ntrunc+2)/2,1))
     827          else:
     828              nt = vrtspec.shape[1]
     829
     830  # convert to spectral coeffs of psi, chi.
     831
     832          psispec = _spherepack.invlap(vrtspec, self.rsphere)
     833          chispec = _spherepack.invlap(divspec, self.rsphere)
     834
     835  # inverse transform to grid.
     836
     837          psigrid =  self.spectogrd(psispec)
     838          chigrid =  self.spectogrd(chispec)
     839
     840          if idim == 2:
     841              return numpy.squeeze(psigrid), numpy.squeeze(chigrid)
     842          else:
     843              return psigrid, chigrid
     844
     845 -    def getgrad(self, chispec):
     846
     847          """
     848   compute vector gradient on grid given complex spectral coefficients.
     849
     850   @param chispec: rank 1 or 2 numpy complex array with shape
     851   (ntrunc+1)*(ntrunc+2)/2 or ((ntrunc+1)*(ntrunc+2)/2,nt) containing
     852   complex spherical harmonic coefficients (where ntrunc is the
     853   triangular truncation limit and nt is the number of spectral arrays
     854   to be transformed). If chispec is rank 1, nt is assumed to be 1.
     855
     856   @return: C{B{uchi, vchi}} - rank 2 or 3 numpy float32 arrays containing
     857   gridded zonal and meridional components of the vector gradient.
     858   Shapes are either (nlat,nlon) or (nlat,nlon,nt).
     859          """
     860
     861  # make sure chispec is rank 1 or 2.
     862
     863          idim = chispec.ndim
     864
     865          if len(chispec.shape) !=1 and len(chispec.shape) !=2:
     866              msg = 'getgrad needs rank one or two arrays!'
     867              raise ValueError(msg)
     868
     869  # infer ntrunc from size of chispec (chispec must be rank 1!)
     870  # chispec is assumed to have size (ntrunc+1)*(ntrunc+2)/2
     871
     872          ntrunc = int(-1.5 + 0.5*math.sqrt(9.-8.*(1.-chispec.shape[0])))
     873
     874  # number of grids to compute.
     875
     876          if len(chispec.shape) == 1:
     877              nt = 1
     878              chispec = numpy.reshape(chispec, ((ntrunc+1)*(ntrunc+2)/2,1))
     879          else:
     880              nt = chispec.shape[1]
     881
     882  # convert chispec to divspec.
     883
     884          divspec = _spherepack.lap(chispec,self.rsphere)
     885
     886  # call getuv, with vrtspec=0, to get uchi,vchi.
     887
     888          uchi, vchi = self.getuv(numpy.zeros(chispec.shape, chispec.dtype), divspec)
     889
     890          if idim == 1:
     891              return numpy.squeeze(uchi), numpy.squeeze(vchi)
     892          else:
     893              return uchi, vchi
     894
     895 -    def specsmooth(self, datagrid, smooth):
     896
     897          """
     898   isotropic spectral smoothing on a sphere.
     899
     900   @param datagrid: rank 2 or 3 numpy float32 array with shape (nlat,nlon) or
     901   (nlat,nlon,nt), where nt is the number of grids to be smoothed.  If
     902   datagrid is rank 2, nt is assumed to be 1.
     903
     904   @param smooth: rank 1 array of length nlat containing smoothing factors
     905   as a function of total wavenumber.
     906
     907   @return: C{B{datagrid}} - rank 2 or 3 numpy float32 array with shape
     908   (nlat,nlon) or (nlat,nlon,nt) containing the smoothed grids.
     909          """
     910
     911  # check that datagrid is rank 2 or 3 with size (self.nlat, self.nlon) or
     912  # (self.nlat, self.nlon, nt) where nt is number of grids to transform.
     913
     914          if len(datagrid.shape) > 3:
     915              msg = 'specsmooth needs a rank two or three array, got %d' % (len(datagrid.shape),)
     916              raise ValueError(msg)
     917
     918          if datagrid.shape[0] != self.nlat or datagrid.shape[1] != self.nlon:
     919              msg = 'specsmooth needs an array of size %d by %d, got %d by %d' % (self.nlat, self.nlon, datagrid.shape[0], datagrid.shape[1],)
     920              raise ValueError(msg)
     921
     922
     923  # make sure smooth is rank 1, same size as datagrid.shape[0]
     924
     925          if len(smooth.shape) !=1 or smooth.shape[0] != datagrid.shape[0]:
     926              msg = 'smooth must be rank 1 and same size as datagrid.shape[0] in specsmooth!'
     927              raise ValueError(msg)
     928
     929  # grid to spectral transform.
     930
     931          nlat = self.nlat
     932          dataspec = self.grdtospec(datagrid, nlat-1)
     933
     934  # multiply spectral coeffs. by smoothing factor.
     935
     936          dataspec = _spherepack.multsmoothfact(dataspec, smooth)
     937
     938  # spectral to grid transform.
     939
     940          datagrid = self.spectogrd(dataspec)
     941
     942          return datagrid
     943
     944 -def regrid(grdin, grdout, datagrid, ntrunc=None, smooth=None):
     945      """
     946   regrid data using spectral interpolation, while performing
     947   optional spectral smoothing and/or truncation.
     948
     949   @param grdin: Spharmt class instance describing input grid.
     950
     951   @param grdout: Spharmt class instance describing output grid.
     952
     953   @param datagrid: data on input grid (grdin.nlat x grdin.nlon). If
     954   datagrid is rank 3, last dimension is the number of grids to interpolate.
     955
     956   @keyword ntrunc:  optional spectral truncation limit for datagrid
     957   (default min(grdin.nlat-1,grdout.nlat-1)).
     958
     959   @keyword smooth: rank 1 array of length grdout.nlat containing smoothing
     960   factors as a function of total wavenumber (default is no smoothing).
     961
     962   @return: C{B{datagrid}} - interpolated (and optionally smoothed) array(s)
     963   on grdout.nlon x grdout.nlat grid.
     964      """
     965
     966  # check that datagrid is rank 2 or 3 with size (grdin.nlat, grdin.nlon) or
     967  # (grdin.nlat, grdin.nlon, nt) where nt is number of grids to transform.
     968
     969      if len(datagrid.shape) > 3:
     970          msg = 'regrid needs a rank two or three array, got %d' % (len(datagrid.shape),)
     971          raise ValueError(msg)
     972
     973      if datagrid.shape[0] != grdin.nlat or datagrid.shape[1] != grdin.nlon:
     974          msg = 'grdtospec needs an array of size %d by %d, got %d by %d' % (grdin.nlat, grdin.nlon, datagrid.shape[0], datagrid.shape[1],)
     975          raise ValueError(msg)
     976
     977      if smooth is not None and (len(smooth.shape) !=1 or smooth.shape[0] != grdout.nlat):
     978          msg = 'smooth must be rank 1 size grdout.nlat in regrid!'
     979          raise ValueError(msg)
     980
     981      if ntrunc is None:
     982          ntrunc = min(grdout.nlat-1,grdin.nlat-1)
     983
     984      dataspec = grdin.grdtospec(datagrid,ntrunc)
     985
     986      if smooth is not None:
     987          dataspec = _spherepack.multsmoothfact(dataspec, smooth)
     988
     989      return grdout.spectogrd(dataspec)
     990
     991 -def gaussian_lats_wts(nlat):
     992
     993      """
     994   compute the gaussian latitudes (in degrees) and quadrature weights.
     995
     996   @param nlat: number of gaussian latitudes desired.
     997
     998   @return: C{B{lats, wts}} - rank 1 numpy float64 arrays containing
     999   gaussian latitudes (in degrees north) and gaussian quadrature weights.
    1000      """
    1001
    1002  # get the gaussian colatitudes and weights using gaqd.
    1003
    1004      colats, wts, ierror = _spherepack.gaqd(nlat)
    1005
    1006      if ierror != 0:
    1007          msg = 'In return from call to gaqd ierror =  %d' % ierror
    1008          raise ValueError(msg)
    1009
    1010  # convert to degrees north latitude.
    1011
    1012      lats = 90.0 - colats*180.0/math.pi
    1013
    1014      return lats, wts
    1015
    1016 -def getspecindx(ntrunc):
    1017
    1018      """
    1019   compute indices of zonal wavenumber (indxm) and degree (indxn)
    1020   for complex spherical harmonic coefficients.
    1021
    1022   @param ntrunc: spherical harmonic triangular truncation limit.
    1023
    1024   @return: C{B{indxm, indxn}} - rank 1 numpy Int32 arrays
    1025   containing zonal wavenumber (indxm) and degree (indxn) of
    1026   spherical harmonic coefficients.
    1027      """
    1028
    1029      indexn = numpy.indices((ntrunc+1,ntrunc+1))[1,:,:]
    1030      indexm = numpy.indices((ntrunc+1,ntrunc+1))[0,:,:]
    1031      indices = numpy.nonzero(numpy.greater(indexn, indexm-1).flatten())
    1032      indxn = numpy.take(indexn.flatten(),indices)
    1033      indxm = numpy.take(indexm.flatten(),indices)
    1034
    1035      return numpy.squeeze(indxm), numpy.squeeze(indxn)
    1036
    1037 -def getgeodesicpts(m):
    1038      """
    1039   computes the lat/lon values of the points on the surface of the sphere
    1040   corresponding to a twenty-sided (icosahedral) geodesic.
    1041
    1042   @param m: the number of points on the edge of a single geodesic triangle.
    1043   There are 10*(m-1)**2+2 total geodesic points, including the poles.
    1044
    1045   @return: C{B{lats, lons}} - rank 1 numpy float32 arrays containing
    1046   the latitudes and longitudes of the geodesic points (in degrees). These
    1047   points are nearly evenly distributed on the surface of the sphere.
    1048      """
    1049      x,y,z = _spherepack.ihgeod(m)
    1050  # convert cartesian coords to lat/lon.
    1051      rad2dg = 180./math.pi
    1052      r1 = x*x+y*y
    1053      r = numpy.sqrt(r1+z*z)
    1054      r1 = numpy.sqrt(r1)
    1055      xtmp = numpy.where(numpy.logical_or(x,y),x,numpy.ones(x.shape,numpy.float32))
    1056      ztmp = numpy.where(numpy.logical_or(r1,z),z,numpy.ones(z.shape,numpy.float32))
    1057      lons = rad2dg*numpy.arctan2(y,xtmp)+180.
    1058      lats = rad2dg*numpy.arctan2(r1,ztmp)-90.
    1059      lat = numpy.zeros(10*(m-1)**2+2,numpy.float32)
    1060      lon = numpy.zeros(10*(m-1)**2+2,numpy.float32)
    1061  # first two points are poles.
    1062      lat[0] = 90; lat[1] = -90.
    1063      lon[0] = 0.; lon[1] = 0.
    1064      lat[2:] = lats[0:2*(m-1),0:m-1,:].flatten()
    1065      lon[2:] = lons[0:2*(m-1),0:m-1,:].flatten()
    1066      return lat,lon
    1067
    1068 -def legendre(lat,ntrunc):
    1069      """
    1070   calculate associated legendre functions for triangular truncation T(ntrunc),
    1071   at a given latitude.
    1072
    1073   @param lat:  the latitude (in degrees) to compute the associate legendre
    1074   functions.
    1075
    1076   @param ntrunc:  the triangular truncation limit.
    1077
    1078   @return: C{B{pnm}} - rank 1 numpy float32 array containing the
    1079   (C{B{ntrunc}}+1)*(C{B{ntrunc}}+2)/2 associated legendre functions at
    1080   latitude C{B{lat}}.
    1081      """
    1082      return _spherepack.getlegfunc(lat,ntrunc)
    1083
    1084 -def specintrp(lon,dataspec,legfuncs):
    1085      """
    1086      spectral interpolation given spherical harmonic coefficients.
    1087
    1088      @param lon: longitude (in degrees) of point on a sphere to interpolate to.
    1089
    1090      @param dataspec:  spectral coefficients of function to interpolate.
    1091
    1092      @param legfuncs: associated legendre functions with same triangular
    1093      truncation as C{B{dataspec}} (computed using L{legendre}), computed
    1094      at latitude of interpolation point.
    1095
    1096      @return: C{B{ob}} - interpolated value.
    1097      """
    1098      ntrunc1 = int(-1.5 + 0.5*math.sqrt(9.-8.*(1.-dataspec.shape[0])))
    1099      ntrunc2 = int(-1.5 + 0.5*math.sqrt(9.-8.*(1.-legfuncs.shape[0])))
    1100      if ntrunc1 != ntrunc2:
    1101          raise ValueError('first dimensions of dataspec and legfuncs in Spharmt.specintrp imply inconsistent spectral truncations - they must be the same!')
    1102      return _spherepack.specintrp((math.pi/180.)*lon,ntrunc1,dataspec,legfuncs)
    1103