Esempio n. 1
0
def getMLvec(ntrunc, m="pn"):
    if ntrunc == 0:
        M = [0]
        L = [0]
    elif m == "pn":
        m, l = getspecindx(ntrunc)
        M = list(m) + list(-m[ntrunc + 1 :])
        L = list(l) + list(l[ntrunc + 1 :])
    elif m == "p":
        M, L = getspecindx(ntrunc)
    return zip(M, L)
Esempio n. 2
0
def getMLvec(ntrunc,m='pn'):
    if ntrunc==0:
        M = [0]
        L = [0]
    elif m=='pn':    
        m,l = getspecindx(ntrunc)
        M = list(m) + list(-m[ntrunc+1:])
        L = list(l) + list(l[ntrunc+1:])
    elif m=='p':
        M,L = getspecindx(ntrunc)
    return zip(M,L)
Esempio n. 3
0
def sph_filter(field, l_cut, gridtype='gaussian'):
    """Remove all waves above a specific spherical wavenumber l_cut"""
    nlat, nlon = len(field.lat), len(field.lon)
    grid = spharm.Spharmt(nlon, nlat, gridtype=gridtype)
    other_dims = [d for d in field.dims if d not in ('lat', 'lon')]

    # need the field in N-S, E-W form, (lat, lon, other coords)
    vfield = (field.sel(lat=sorted(cbbt.lat, reverse=True),
                        lon=sorted(cbbt.lon)).transpose(
                            'lat', 'lon', *other_dims))

    ntrunc = nlat - 1
    m, n = spharm.getspecindx(ntrunc)
    l = m + n

    # transform to spherical modes, eliminate high wavenumbers and transform back
    coeffs = grid.grdtospec(vfield.values, ntrunc)
    mask = l > l_cut
    coeffs[mask] = 0

    values = grid.spectogrd(coeffs)

    # copy the original field and comply to the same coordinate ordering
    nfield = vfield.copy()
    nfield.values = values
    return nfield.transpose(*field.dims)
Esempio n. 4
0
def spht(field, ntrunc=None, gridtype='gaussian'):
    """Transform a field on lat-lon grid to spherical harmonics.

    Currently only works for:
        - fields defined on the lat-lon points, not latb-lonb.
        - triangular truncation.

    Returns an xarray.DataArray
    """
    nlat, nlon = len(field.lat), len(field.lon)
    grid = spharm.Spharmt(nlon, nlat, gridtype=gridtype)
    if ntrunc is None:
        ntrunc = nlat - 1

    other_dims = [d for d in field.dims if d not in ('lat', 'lon')]

    # need the field in N-S, E-W form, (lat, lon, other coords)
    vfield = (field.sel(lat=sorted(cbbt.lat, reverse=True),
                        lon=sorted(cbbt.lon)).transpose(
                            'lat', 'lon', *other_dims))

    # calculate the spectral coefficients, given the truncation level
    coeffs = grid.grdtospec(vfield.values, ntrunc)

    m, n = spharm.getspecindx(ntrunc)

    # put the coefficients into a grid, half of which will be empty
    # due to the triangular trunctation
    cc = np.zeros((ntrunc + 1, ntrunc + 1) + tuple(coeffs.shape[1:]),
                  dtype=np.complex128)
    cc[m, n] = coeffs
    coords = [('m', np.arange(0, ntrunc + 1)), ('n', np.arange(0, ntrunc + 1))]
    for d in other_dims:
        coords.append((d, field.coords[d]))
    return xr.DataArray(data=cc, coords=coords, name=field.name)
Esempio n. 5
0
    def __init__(self,
                 rsphere=EARTH_RADIUS,
                 omega=EARTH_OMEGA,
                 resolution=2.5,
                 ntrunc=None,
                 legfunc="stored"):
        """Grid constructor.

        - The radius and angular velocity of the planet are given by `rsphere` in
          m and `omega` in 1/s, respectively (the default values correspond to
          those of the Earth).
        - The grid resolution is uniform and specified with the `resolution`
          parameter in degrees.
        - Uses spherical harmonics for some operations utilizing the
          `spharm`/`pyspharm` package. By default (`ntrunc=None`,
          `legfunc="stored"`), the spherical harmonics are truncated after (number
          of latitudes - 1) functions and precomputed. Consult the documentation of
          `spharm.Spharmt` for more information on these parameters.
        """
        # Planet parameters (radius, angular velocity)
        self.rsphere = rsphere
        self.omega = omega
        # Setup longitude and latitude grid. Make sure longitude 0° is not
        # repeated and that both latitudes 90° and -90° exist. Latitudes start
        # at the North pole, which is the convention used by pyspharm.
        self.nlon = int(360. / resolution)
        self.nlat = int(180. / resolution) + 1
        if self.nlat % 2 == 0:
            raise ValueError(
                "Number of latitudes must be odd but is {}".format(self.nlat))
        self.lons = np.linspace(0., 360., self.nlon, endpoint=False)
        self.lats = np.linspace(90., -90., self.nlat, endpoint=True)
        self.lon, self.lat = np.meshgrid(self.lons, self.lats)
        # Grid spacing in degrees
        self.dlon = resolution
        self.dlat = -resolution
        # Spherical coordinate grid (for use with trigonometric functions)
        self.lams = np.deg2rad(self.lons)
        self.phis = np.deg2rad(self.lats)
        self.lam, self.phi = np.meshgrid(self.lams, self.phis)
        # Grid spacing in radians
        self.dlam = np.deg2rad(self.dlon)
        self.dphi = np.deg2rad(self.dlat)
        # Precompute Coriolis field
        self.fcor = self.coriolis(self.lat)
        # Spherical harmonic transform object
        self._spharm = spharm.Spharmt(self.nlon,
                                      self.nlat,
                                      rsphere=rsphere,
                                      gridtype="regular",
                                      legfunc=legfunc)
        self._ntrunc = (self.nlat - 1) if ntrunc is None else ntrunc
        _, self.specindxn = spharm.getspecindx(self._ntrunc)
        # Eigenvalues of the horizontal Laplacian for each spherical harmonic.
        # Force use of complex64 datatype (= 2 * float32) because spharm will
        # cast to float32 components anyway but the multiplication with the
        # python scalars results in float64 values.
        self.laplacian_eigenvalues = (self.specindxn * (1. + self.specindxn) /
                                      rsphere / rsphere).astype(
                                          np.complex64, casting="same_kind")
Esempio n. 6
0
 def __init__(self,sp,dt,ntrunc,ptop=0.,p0=1.e5,grav=9.80616,omega=7.292e-5,cp=1004,\
         rgas=287.,efold=3600.,ndiss=8,tdrag=1.e30,tdiab=1.e30,\
         umax=40,jetexp=2,delth=20,moistfact=1.0):
     # set model parameters
     self.p0 = p0 # mean surface pressure
     self.ptop = ptop # model top pressure
     self.rgas = rgas # gas constant for dry air
     self.grav = grav # gravity
     self.omega = omega # rotation rate
     self.cp = cp # specific heat of dry air at constant pressure
     self.delth = delth # static stability
     # factor to reduce static stability in rising air
     # (crude moist physics assuming air is saturated)
     # moistfact = 1 is dry model
     self.moistfact = moistfact
     dp = 0.5*(ptop-p0)
     exnf1 = cp*((p0+0.5*dp)/p0)**(rgas/cp)
     exnf2 = cp*((p0+1.5*dp)/p0)**(rgas/cp)
     self.delta_exnf = exnf2-exnf1 # diff in exner function between 2 levs.
     # efolding time scale for hyperdiffusion at shortest wavenumber
     self.efold = efold
     self.ndiss = ndiss # order of hyperdiffusion (2 for laplacian)
     self.sp = sp # Spharmt instance
     self.ntrunc = ntrunc # triangular truncation wavenumber
     self.dt = dt # time step (secs)
     self.tdiab = tdiab # lower layer drag timescale
     self.tdrag = tdrag # interface relaxation timescale
     # create lat/lon arrays
     delta = 2.*np.pi/sp.nlon
     if sp.gridtype == 'regular':
        lats1d = 0.5*np.pi-delta*np.arange(sp.nlat)
        wts = np.cos(lats1d)
     else:
        lats1d,wts = gaussian_lats_wts(sp.nlat)
        lats1d = lats1d*np.pi/180. # convert to radians.
     lons1d = np.arange(-np.pi,np.pi,delta)
     lons,lats = np.meshgrid(lons1d,lats1d)
     self.lons = lons; self.lats = lats
     # weights for computing global means.
     self.globalmeanwts = np.ones((sp.nlat,sp.nlon))*wts[:,np.newaxis]
     self.globalmeanwts = self.globalmeanwts/self.globalmeanwts.sum()
     self.f = 2.*omega*np.sin(lats)[:,:,np.newaxis] # coriolis
     # create laplacian operator and its inverse.
     indxm, indxn = getspecindx(ntrunc)
     indxn = indxn.astype(np.float32)
     totwavenum = indxn*(indxn+1.0)
     self.lap = -totwavenum/sp.rsphere**2
     self.ilap = np.zeros(self.lap.shape, np.float32)
     self.ilap[1:] = 1./self.lap[1:]
     # hyperdiffusion operator
     self.hyperdiff = -(1./efold)*(totwavenum/totwavenum[-1])**(ndiss/2)
     # set equilibrium layer thicknes profile.
     self._interface_profile(umax,jetexp)
Esempio n. 7
0
    def __init__(self,shape,lengths):
        self.shape=shape
        self.lengths=lengths
        self.spharm=spharm.Spharmt(self.shape[1],self.shape[0],legfunc='computed')

        #Define the inverse laplacian in spectral space:
        setattr(self,'_s_Inv_Laplacian',self.spharm.grdtospec(np.zeros(self.shape)))
        self.legendre_order=spharm.getspecindx(self.shape[0]-1)[1]
        self._s_Inv_Laplacian[1:]=(self.legendre_order[1:]*(self.legendre_order[1:]+1))
        self._s_Inv_Laplacian[0]=1.0
        self._s_Inv_Laplacian/=self.lengths.rsphere**2

        #To shift input to have first index at Greenwich meridian
        self.shift_index=np.argmin(np.abs(self.lengths.lon))

        #For putting on the C-grid:
        #self.spharm_regrid=spharm.Spharmt(self.shape[1]*2,(self.shape[0]-1)*2+1,legfunc='computed')
        return
Esempio n. 8
0
 def __init__(self,sp,dt,ntrunc,theta1=300,theta2=330,grav=9.80616,omega=7.292e-5,cp=1004,\
         zmid=5.e3,ztop=15.e3,efold=3600.,ndiss=8,tdrag=1.e30,tdiab=1.e30,umax=30,jetexp=4):
     # setup model parameters
     self.theta1 = theta1 # lower layer pot. temp.
     self.theta2 = theta2 # upper layer pot. temp.
     self.delth = theta2-theta1 # difference in potential temp between layers
     self.grav = grav # gravity
     self.omega = omega # rotation rate
     self.cp = cp # Specific Heat of Dry Air at Constant Pressure,
     self.zmid = zmid # resting depth of lower layer (m)
     self.ztop = ztop # resting depth of both layers (m)
     # efolding time scale for hyperdiffusion at shortest wavenumber
     self.efold = efold
     self.ndiss = ndiss # order of hyperdiffusion (2 for laplacian)
     self.sp = sp # Spharmt instance
     self.ntrunc = ntrunc # triangular truncation wavenumber
     self.dt = dt # time step (secs)
     self.tdiab = tdiab # lower layer drag timescale
     self.tdrag = tdrag # interface relaxation timescale
     # create lat/lon arrays
     delta = 2.*np.pi/sp.nlon
     if sp.gridtype == 'regular':
        lats1d = 0.5*np.pi-delta*np.arange(sp.nlat)
     else:
        lats1d,wts = gaussian_lats_wts(sp.nlat)
        lats1d = lats1d*np.pi/180.
     lons1d = np.arange(-np.pi,np.pi,delta)
     lons,lats = np.meshgrid(lons1d,lats1d)
     self.lons = lons
     self.lats = lats
     self.f = 2.*omega*np.sin(lats)[:,:,np.newaxis] # coriolis
     # create laplacian operator and its inverse.
     indxm, indxn = getspecindx(ntrunc)
     indxn = indxn.astype(np.float32)[:,np.newaxis]
     totwavenum = indxn*(indxn+1.0)
     self.lap = -totwavenum/sp.rsphere**2
     self.ilap = np.zeros(self.lap.shape, np.float32)
     self.ilap[1:,:] = 1./self.lap[1:,:]
     # hyperdiffusion operator
     self.hyperdiff = -(1./efold)*(totwavenum/totwavenum[-1])**(ndiss/2)
     # initialize orography to zero.
     self.orog = np.zeros((sp.nlat,sp.nlon),np.float32)
     # set equilibrium layer thicknes profile.
     self._interface_profile(umax,jetexp)
Esempio n. 9
0
     times = nc['time'][:].tolist()
     levels = nc['plev'][:].tolist()
     ntime = times.index(fhour)
     nlev = levels.index(level)
     lons = nc['longitude'][:]
     lats = nc['latitude'][:]
     nlons = len(lons)
     nlats = len(lats)
     re = 6.3712e6
     ntrunc = nlats - 1
     spec = Spharmt(nlons,
                    nlats,
                    rsphere=re,
                    gridtype='regular',
                    legfunc='computed')
     indxm, indxn = getspecindx(ntrunc)
     degree = indxn.astype(np.float)
 if int(nc['time'][ntime]) != fhour:
     raise ValueError('incorrect forecast time')
 fcst_data1 = nc[varnc][ntime, nlev, ...]
 nc.close()
 if fhour > 9:
     fcstfile = '%s/%s/fv3longcontrol2_historyp_%s_latlon.nc' % (datapath2,
                                                                 date, date)
 else:
     fcstfile = '%s/%s/fv3control2_historyp_%s_latlon.nc' % (datapath2,
                                                             date, date)
 nc = Dataset(fcstfile)
 if int(nc['time'][ntime]) != fhour:
     raise ValueError('incorrect forecast time')
 fcst_data2 = nc[varnc][ntime, nlev, ...]
Esempio n. 10
0
import numpy as np
# set up orthographic map projection.
map = Basemap(projection='ortho',lat_0=30,lon_0=-60,resolution='l')
# draw coastlines, country boundaries, fill continents.
map.drawcoastlines()
# draw the edge of the map projection region (the projection limb)
map.drawmapboundary()
# draw lat/lon grid lines every 30 degrees.
map.drawmeridians(np.arange(0,360,30))
map.drawparallels(np.arange(-90,90,30))
min = int(raw_input('input degree (m) of legendre function to plot:'))
nin = int(raw_input('input order  (n) of legendre function to plot:'))
nlons = 720; nlats = 361
x = Spharmt(nlons,nlats,legfunc='computed')
ntrunc = nlats-1
indxm, indxn = getspecindx(ntrunc)
nm = -1
i = 0
for m,n in zip(indxm,indxn):
    if m  == min and n == nin:
        nm = i
        exit
    else:
        i = i + 1
if nm < 0:
    raise ValueError('invalid m,n - must fit within triangular truncation at wavenumber '+repr(ntrunc))
coeffs = np.zeros((ntrunc+1)*(ntrunc+2)/2,np.complex)
coeffs[nm] = 1.
spharmonic = x.spectogrd(coeffs)
delta = 360./nlons
lats = 90.-delta*np.arange(nlats)
Esempio n. 11
0
 def wavenumbers(self):
     """
     Wavenumbers corresponding to the spectral fields.
     """
     return getspecindx(self.truncation)
Esempio n. 12
0
    def performConvolution(self, out_times=None, ntrunc=None, topo=None,
                            verbose=False, eliter=5, nrem=1, massconerr=1e-2):  
        """Convolve an ice load and an earth response model in fft space.
        Calculate the uplift associated with stored earth and ice model.
        
        Parameters
        ----------
        out_times : an array of times at which to caluclate the convolution.
                    (default is to use previously stored values).
        ntrunc : int
           The truncation number of the spherical harmonic expansion. Default
           is from the earth model, must be <= ice model's and < grid.nlat
        topo   : array
            Topography on which to compute. If None (default), assumes a flat
            topography. Must be the same shapt as ice.
        verbose : boolean
            Display progress on computation. Depends on progressbar module.
            Default is False.
        eliter  : int
            The maximum number of iterations allowed to compute initial elastic
            response to redistributed load at each stage. If 0, instantaneous
            elastic response is not computed. Default 5.
        nrem   : int
            Number of removal stages between the provided ice stages
            (intermediate steps are interpolated linearly). Default 1.
       
        Results
        -------
        observerDict : GiaSimOutput
            A dictionary whose keys are fields of interest, such as uplift
            ('upl'), geoid ('geo'), and solid surface topography ('sstopo'),
            computed on the input grid at out_times.
        """
 
        DENICE      = 931.   #934.           # kg/m^3
        DENWAT      = 1000.  #999.           # kg/m^3
        DENSEA      = 1000.  #1029.          # kg/m^3
        GSURF       = 9.815          # m/s^2
        #PAperM      = DENSEA*GSURF
        NREM        = nrem           # number of intermediate steps


        earth = self.earth
        ice = self.ice
        grid = self.grid
        if topo is None and self.topo is not None:
            topo = self.topo
        if topo is not None:
            assert topo.shape == ice.shape, 'Topo and Ice must have the same shape'

        # Resolution
        ntrunc = ntrunc or min(earth.nmax, ice.nlat-1)
        assert ntrunc <= ice.nlat-1, 'ntrunc > ice.nlat-1'
        ms, ns = spharm.getspecindx(ice.nlat-1)
        # npad is the indices in the larger (padded) array of spherical
        # harmonics that correspond to the smaller (response) array.
        npad = (ns <= ntrunc)
        
        # Store out_times
        if out_times is None:
            out_times = self.out_times
        else:
            out_times = out_times
        self.out_times = out_times
        assert out_times is not None, 'out_times is not set'
        
        # Calculate times of intermediate removal stages.
        diffs = np.diff(ice.times)
        addRemovalTimes = []
        for i in range(1, NREM+1):
            addRemovalTimes.append(ice.times[:-1]+i*diffs/NREM)
        addRemovalTimes = np.array(addRemovalTimes).flatten()
        remTimes = np.union1d(ice.times, addRemovalTimes)[::-1]
        calcTimes = np.union1d(remTimes, out_times)[::-1]

        # Initialize output observer         
        observerDict = initialize_output(self, out_times, calcTimes, ice.nlat-1, 
                                            ntrunc, ns, ice.shape) 

        for o in observerDict:
            o.loadStageUpdate(ice.times[0], sstopo=topo)

        esl = 0                 # Equivalent sea level assumed to start at 0.

        elRespArray = earth.getResp(0.)
        ssResp = np.zeros_like(ns) 
        ssResp[npad] = observerDict['SS'].isolateRespArray(elRespArray)

        # Convolve each ice stage to the each output time.
        # Primary loop: over ice load changes.
        for icea, ta, iceb, tb in ice.pairIter():
            ################### LOAD STAGE CALCULATION ###################
            # Determine the water load redistribution for ice, uplift, and
            # geoid changes between ta and tb,
            if topo is not None:
                # Get index for starting time.
                nta = observerDict['SS'].locateByTime(ta)
                # Collect the solid-surface topography at beginning of step.
                Ta = observerDict['sstopo'].array[nta]

                # Redistribute the ocean by change in ocean floor / surface.
                ssa, ssb = observerDict['SS'].array[[nta, nta+1]] 
                dSS = self.harmTrans.spectogrd(ssb-ssa)
                dhwBarU = sealevelChangeByUplift(dSS, Ta+DENICE/DENSEA*icea, 
                                                        grid)
                dhwU = oceanUpliftLoad(dhwBarU, Ta+DENICE/DENSEA*icea, dSS)

                # Update the solid-surface topography with uplift / geoid.
                Tb = Ta + dSS - dhwBarU
                esl += dhwBarU
                dLoad = dhwU.copy()
                dwLoad = dhwU.copy()                # Save the water load

                # Redistribute ice, consistent with current floating ice. 
                dILoad, dhwBarI = floatingIceRedistribute(icea, iceb, Tb, grid,
                                                            DENICE/DENSEA)

                # Combine loads from ocean changes and ice volume changes.
                dLoad += dILoad
                esl += dhwBarI
                dwLoad += volumeChangeLoad(dhwBarI, Tb+DENICE/DENSEA*iceb)
                Tb -= dhwBarI
                

                # Calculate instantaneous (elastic and gravity) responses to
                # the load shift and redistribute ocean accordingly.
                # Note: WE DO NOT CURRENTLY RECHECK FOR FLOATING ICE LOADS.
                if eliter:
                    # Get elastic and geoid response to the water load.
                    # Find the elastic uplift in response to stage's load
                    # redistribution.
                    dSSel = self.harmTrans.spectogrd((ssResp)*\
                                self.harmTrans.grdtospec(dLoad)) 

                    dhwBarUel = sealevelChangeByUplift(dSSel, 
                                                        Tb+DENICE/DENSEA*iceb, grid)
                    dhwUel = oceanUpliftLoad(dhwBarUel, 
                                                Tb+DENICE/DENSEA*iceb, dSSel)

                    Tb = Tb + dSSel - dhwBarUel
                    esl += dhwBarUel
                    dLoad = dLoad + dhwUel
                    dwLoad += dhwUel

                    # Iterate elastic responses until they are sufficiently small.
                    for i in range(eliter):
                        # Need to save elastic uplift and geoid at each iteration
                        # to compare to previous steps for convergence.
                        dSSelp = self.harmTrans.spectogrd((ssResp)*\
                                    self.harmTrans.grdtospec(dhwUel))
                      
                     

                        dhwBarUel = sealevelChangeByUplift(dSSelp, 
                                                            Tb+DENICE/DENSEA*iceb, grid)
                        dhwUel = oceanUpliftLoad(dhwBarUel, 
                                                    Tb+DENICE/DENSEA*iceb, dSSelp)

                        # Correct topography
                        Tb = Tb + dSSelp - dhwBarUel
                        esl += dhwBarUel
                        dLoad = dLoad + dhwUel
                        dwLoad += dhwUel

                        # Truncation error from further iteration
                        err = np.mean(np.abs(dSSelp))/np.mean(np.abs(dSSel))
                        if err <= massconerr:
                            break
                        else:
                            dSSel = dSSel + dSSelp
                       
                            continue

                observerDict['SS'].array[nta+1] += self.harmTrans.grdtospec(dSSel) 

                for o in observerDict:
                    # Topography and load for time tb are updated and saved.
                    o.loadStageUpdate(tb, dLoad=dLoad, 
                                      topo=Tb+iceb*(Tb + DENICE/DENSEA*iceb>=0), 
                                      esl=esl, dwLoad=dwLoad, sstopo=Tb)

            else:
                dLoad = (iceb-icea)*DENICE/DENSEA
                Tb = None

                for o in observerDict:
                    # Topography and load for time tb are updated and saved.
                    o.loadStageUpdate(tb, dLoad=dLoad)

            # Transform load change into spherical harmonics.
            loadChangeSpec = self.harmTrans.grdtospec(dLoad)/NREM
            
            # Check for mass conservation.
            massConCheck = np.abs(loadChangeSpec[0])/np.abs(loadChangeSpec.max())
            if  verbose and massConCheck >= massconerr:
                print("Load at {0} doesn't conserve mass: {1}.".format(ta,
                                                                massConCheck))
            # N.B. the n=0 load should be zero in cases of glacial isostasy, as 
            # mass is conserved during redistribution.

            ################# RESPONSE STAGE CALCULATION #################
            # Secondary loop: over output times.
            for inter_time in np.linspace(tb, ta, NREM, endpoint=False)[::-1]:
                # Perform the time convolution for each output time
                for t_out in calcTimes[calcTimes < inter_time]:
                    respArray = earth.getResp(inter_time-t_out)
                    for o in observerDict:
                        o.respStageUpdate(t_out, respArray, 
                                            DENSEA*loadChangeSpec) 

        # Don't keep the intermediate uplift stages for water redistribution
        #observerDict.removeObserver('eslUpl', 'eslGeo') 

        return observerDict