SHTns  2.6.5
shallow_water.py

Solves the non-linear barotropically unstable shallow water test case in Python.

1 #!/usr/bin/python
2 
3 ## "non-linear barotropically unstable shallow water test case"
4 ## example provided by Jeffrey Whitaker
5 ## https://gist.github.com/jswhit/3845307
6 ##
7 ## Running the script should pop up a window with this image:
8 ## http://i.imgur.com/ZlxR1.png
9 
10 
11 import numpy as np
12 import shtns
13 
14 class Spharmt(object):
15  """
16  wrapper class for commonly used spectral transform operations in
17  atmospheric models. Provides an interface to shtns compatible
18  with pyspharm (pyspharm.googlecode.com).
19  """
20  def __init__(self,nlons,nlats,ntrunc,rsphere,gridtype='gaussian'):
21  """initialize
22  nlons: number of longitudes
23  nlats: number of latitudes"""
24  self._shtns = shtns.sht(ntrunc, ntrunc, 1, \
25  shtns.sht_orthonormal+shtns.SHT_NO_CS_PHASE)
26  if gridtype == 'gaussian':
27  #self._shtns.set_grid(nlats,nlons,shtns.sht_gauss_fly|shtns.SHT_PHI_CONTIGUOUS,1.e-10)
28  self._shtns.set_grid(nlats,nlons,shtns.sht_quick_init|shtns.SHT_PHI_CONTIGUOUS,1.e-10)
29  elif gridtype == 'regular':
30  self._shtns.set_grid(nlats,nlons,shtns.sht_reg_dct|shtns.SHT_PHI_CONTIGUOUS,1.e-10)
31  self.lats = np.arcsin(self._shtns.cos_theta)
32  self.lons = (2.*np.pi/nlons)*np.arange(nlons)
33  self.nlons = nlons
34  self.nlats = nlats
35  self.ntrunc = ntrunc
36  self.nlm = self._shtns.nlm
37  self.degree = self._shtns.l
38  self.lap = -self.degree*(self.degree+1.0).astype(np.complex)
39  self.invlap = np.zeros(self.lap.shape, self.lap.dtype)
40  self.invlap[1:] = 1./self.lap[1:]
41  self.rsphere = rsphere
42  self.lap = self.lap/rsphere**2
43  self.invlap = self.invlap*rsphere**2
44  def grdtospec(self,data):
45  """compute spectral coefficients from gridded data"""
46  return self._shtns.analys(data)
47  def spectogrd(self,dataspec):
48  """compute gridded data from spectral coefficients"""
49  return self._shtns.synth(dataspec)
50  def getuv(self,vrtspec,divspec):
51  """compute wind vector from spectral coeffs of vorticity and divergence"""
52  return self._shtns.synth((self.invlap/self.rsphere)*vrtspec, (self.invlap/self.rsphere)*divspec)
53  def getvrtdivspec(self,u,v):
54  """compute spectral coeffs of vorticity and divergence from wind vector"""
55  vrtspec, divspec = self._shtns.analys(u, v)
56  return self.lap*self.rsphere*vrtspec, self.lap*rsphere*divspec
57  def getgrad(self,divspec):
58  """compute gradient vector from spectral coeffs"""
59  vrtspec = np.zeros(divspec.shape, dtype=np.complex)
60  u,v = self._shtns.synth(vrtspec,divspec)
61  return u/rsphere, v/rsphere
62 
63 if __name__ == "__main__":
64  import matplotlib.pyplot as plt
65  import time
66 
67  # non-linear barotropically unstable shallow water test case
68  # of Galewsky et al (2004, Tellus, 56A, 429-440).
69  # "An initial-value problem for testing numerical models of the global
70  # shallow-water equations" DOI: 10.1111/j.1600-0870.2004.00071.x
71  # http://www-vortex.mcs.st-and.ac.uk/~rks/reprints/galewsky_etal_tellus_2004.pdf
72 
73  # requires matplotlib for plotting.
74 
75  # grid, time step info
76  nlons = 256 # number of longitudes
77  ntrunc = int(nlons/3) # spectral truncation (for alias-free computations)
78  nlats = int(nlons/2) # for gaussian grid.
79  dt = 150 # time step in seconds
80  itmax = 6*int(86400/dt) # integration length in days
81 
82  # parameters for test
83  rsphere = 6.37122e6 # earth radius
84  omega = 7.292e-5 # rotation rate
85  grav = 9.80616 # gravity
86  hbar = 10.e3 # resting depth
87  umax = 80. # jet speed
88  phi0 = np.pi/7.; phi1 = 0.5*np.pi - phi0; phi2 = 0.25*np.pi
89  en = np.exp(-4.0/(phi1-phi0)**2)
90  alpha = 1./3.; beta = 1./15.
91  hamp = 120. # amplitude of height perturbation to zonal jet
92  efold = 3.*3600. # efolding timescale at ntrunc for hyperdiffusion
93  ndiss = 8 # order for hyperdiffusion
94 
95  # setup up spherical harmonic instance, set lats/lons of grid
96  x = Spharmt(nlons,nlats,ntrunc,rsphere,gridtype='gaussian')
97  lons,lats = np.meshgrid(x.lons, x.lats)
98  f = 2.*omega*np.sin(lats) # coriolis
99 
100  # zonal jet.
101  vg = np.zeros((nlats,nlons),np.float)
102  u1 = (umax/en)*np.exp(1./((x.lats-phi0)*(x.lats-phi1)))
103  ug = np.zeros((nlats),np.float)
104  ug = np.where(np.logical_and(x.lats < phi1, x.lats > phi0), u1, ug)
105  ug.shape = (nlats,1)
106  ug = ug*np.ones((nlats,nlons),dtype=np.float) # broadcast to shape (nlats,nlonss)
107  # height perturbation.
108  hbump = hamp*np.cos(lats)*np.exp(-(lons/alpha)**2)*np.exp(-(phi2-lats)**2/beta)
109 
110  # initial vorticity, divergence in spectral space
111  vrtspec, divspec = x.getvrtdivspec(ug,vg)
112  vrtg = x.spectogrd(vrtspec)
113  divg = x.spectogrd(divspec)
114 
115  # create hyperdiffusion factor
116  hyperdiff_fact = np.exp((-dt/efold)*(x.lap/x.lap[-1])**(ndiss/2))
117 
118  # solve nonlinear balance eqn to get initial zonal geopotential,
119  # add localized bump (not balanced).
120  vrtg = x.spectogrd(vrtspec)
121  tmpg1 = ug*(vrtg+f); tmpg2 = vg*(vrtg+f)
122  tmpspec1, tmpspec2 = x.getvrtdivspec(tmpg1,tmpg2)
123  tmpspec2 = x.grdtospec(0.5*(ug**2+vg**2))
124  phispec = x.invlap*tmpspec1 - tmpspec2
125  phig = grav*(hbar + hbump) + x.spectogrd(phispec)
126  phispec = x.grdtospec(phig)
127 
128  # initialize spectral tendency arrays
129  ddivdtspec = np.zeros(vrtspec.shape+(3,), np.complex)
130  dvrtdtspec = np.zeros(vrtspec.shape+(3,), np.complex)
131  dphidtspec = np.zeros(vrtspec.shape+(3,), np.complex)
132  nnew = 0; nnow = 1; nold = 2
133 
134  # time loop.
135  time1 = time.clock()
136  for ncycle in range(itmax+1):
137  t = ncycle*dt
138  # get vort,u,v,phi on grid
139  vrtg = x.spectogrd(vrtspec)
140  ug,vg = x.getuv(vrtspec,divspec)
141  phig = x.spectogrd(phispec)
142  print('t=%6.2f hours: min/max %6.2f, %6.2f' % (t/3600.,vg.min(), vg.max()))
143  # compute tendencies.
144  tmpg1 = ug*(vrtg+f); tmpg2 = vg*(vrtg+f)
145  ddivdtspec[:,nnew], dvrtdtspec[:,nnew] = x.getvrtdivspec(tmpg1,tmpg2)
146  dvrtdtspec[:,nnew] *= -1
147  tmpg = x.spectogrd(ddivdtspec[:,nnew])
148  tmpg1 = ug*phig; tmpg2 = vg*phig
149  tmpspec, dphidtspec[:,nnew] = x.getvrtdivspec(tmpg1,tmpg2)
150  dphidtspec[:,nnew] *= -1
151  tmpspec = x.grdtospec(phig+0.5*(ug**2+vg**2))
152  ddivdtspec[:,nnew] += -x.lap*tmpspec
153  # update vort,div,phiv with third-order adams-bashforth.
154  # forward euler, then 2nd-order adams-bashforth time steps to start.
155  if ncycle == 0:
156  dvrtdtspec[:,nnow] = dvrtdtspec[:,nnew]
157  dvrtdtspec[:,nold] = dvrtdtspec[:,nnew]
158  ddivdtspec[:,nnow] = ddivdtspec[:,nnew]
159  ddivdtspec[:,nold] = ddivdtspec[:,nnew]
160  dphidtspec[:,nnow] = dphidtspec[:,nnew]
161  dphidtspec[:,nold] = dphidtspec[:,nnew]
162  elif ncycle == 1:
163  dvrtdtspec[:,nold] = dvrtdtspec[:,nnew]
164  ddivdtspec[:,nold] = ddivdtspec[:,nnew]
165  dphidtspec[:,nold] = dphidtspec[:,nnew]
166  vrtspec += dt*( \
167  (23./12.)*dvrtdtspec[:,nnew] - (16./12.)*dvrtdtspec[:,nnow]+ \
168  (5./12.)*dvrtdtspec[:,nold] )
169  divspec += dt*( \
170  (23./12.)*ddivdtspec[:,nnew] - (16./12.)*ddivdtspec[:,nnow]+ \
171  (5./12.)*ddivdtspec[:,nold] )
172  phispec += dt*( \
173  (23./12.)*dphidtspec[:,nnew] - (16./12.)*dphidtspec[:,nnow]+ \
174  (5./12.)*dphidtspec[:,nold] )
175  # implicit hyperdiffusion for vort and div.
176  vrtspec *= hyperdiff_fact
177  divspec *= hyperdiff_fact
178  # switch indices, do next time step.
179  nsav1 = nnew; nsav2 = nnow
180  nnew = nold; nnow = nsav1; nold = nsav2
181 
182  time2 = time.clock()
183  print('CPU time = ',time2-time1)
184 
185  # make a contour plot of potential vorticity in the Northern Hem.
186  fig = plt.figure(figsize=(12,4))
187  # dimensionless PV
188  pvg = (0.5*hbar*grav/omega)*(vrtg+f)/phig
189  print('max/min PV',pvg.min(), pvg.max())
190  lons1d = (180./np.pi)*x.lons-180.; lats1d = (180./np.pi)*x.lats
191  levs = np.arange(-0.2,1.801,0.1)
192  cs=plt.contourf(lons1d,lats1d,pvg,levs,cmap=plt.cm.spectral,extend='both')
193  cb=plt.colorbar(cs,orientation='horizontal') # add colorbar
194  cb.set_label('potential vorticity')
195  plt.grid()
196  plt.xlabel('degrees longitude')
197  plt.ylabel('degrees latitude')
198  plt.xticks(np.arange(-180,181,60))
199  plt.yticks(np.arange(-5,81,20))
200  plt.axis('equal')
201  plt.axis('tight')
202  plt.ylim(0,lats1d[0])
203  plt.title('PV (T%s with hyperdiffusion, hour %6.2f)' % (ntrunc,t/3600.))
204  plt.show()