# Python qbo.py # By G. K. Vallis # Should work in both python 2 and python 3. """ 1-d QBO, following the model of Plumb (1977) Uses 3rd order Adam-Bashforth time differencing (a useful default method). Momentum forcing from the lower boundary by two gravity waves of opposite phase speed. Grid goes from j=0 at z = 0 to j = nz-1 at z = ztop. Boundary conditions are ub[0] = 0 and ub[nz-1] = ub[nz-2] The pythonic way has the top at ub[nz-1] A useful exercise would be to make a movie of the output. Run this model with an external plot window (e.g., qt5) to see the pseudo-movie. """ # Default parameters that work are amplitude = 1 and time = 50, or 0.1 and 500 from __future__ import (division, print_function) import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np from numba import jit import time as tm mpl.use('Qt5Agg') from matplotlib.animation import FFMpegWriter metadata = dict(title='MJOs', artist='Geoff', comment='Movie support!') writer1 = FFMpegWriter(fps=10, metadata=metadata) # # This is just a function that brings the plot-figure to the top # if you are using a qt backend. def show_plot(figure_id=None): import matplotlib.pyplot as plt if figure_id is not None: fig = plt.figure(num=figure_id) else: fig = plt.gcf() pass fig.canvas.draw() if 'qt' in plt.get_backend().lower(): plt.show(block=False) plt.pause(1e-7) fig.canvas.manager.window.activateWindow() fig.canvas.manager.window.raise_() else: plt.show() plt.pause(1e-7) pass # # Here are a few cosmetic details fn = 18 fontb = {'size': fn} mpl.rc('font', **fontb) mpl.rc('text', usetex = False) # Now the real stuff: # Define constants and parameters # A few physical (but nondimensional) values first ztop = 4 famp = 1 # forcing amplitude (arbitrary-ish # If you change famp you will need to change the plotting times. ztop = 4 # top boundary level gwdamp = 1. # GW damping rate, usually unity diffusv = 0.02 # mean flow diffusion, e.g., 0.02 diffusv = 0.21*famp # diffusion scales with forcing amplit k1 = 1 ; k2 = 1 # zonal wavenumbers for the two waves c1 = 1 ; c2 = -1 # phase speeds for the two waves # rossby = 0 # 0 for Plumb, 1 for Lindzen-Holton # Now some numerical values # note that z=0 and z=ztop correspond to j = 0 and j = nz-1 nz = 100 # number of vertical gridpoints nzm1 = nz - 1 # 100 is good number to play with dz = 1.*ztop/nz # grid size dz2 = dz**2 rdz2 = 1./dz2 dt = 0.001/famp # Scale timestep with amplitude # time_stop = 40 # stopping time time_stop = 41/famp # Maybe make it amplitude dependent # as a rule of thumb, 20000 steps takes a few seconds on a laptop, with jit # showing the movie of ub in realtime slows it down a lot nsteps = int(time_stop/dt) # nsteps = 20000 # nplot1 is used to make a simple movie. # Set nplot1 = 200 for a movie (slow), set nplot1 = 200000 for no movie (fast) nplot1 = 500 # Plot u-movie every nplot1 (e.g 100)steps nplot2 = 50 # plot contour every so many time steps nplot2len = np.int(nsteps/nplot2) # size of plot array time = 0 # initial time # Now initialize some of the basic arrays for later use z = np.linspace(0, ztop, nz) # vertical prediction levels zplot = np.linspace(0, ztop, nz) # plotting levels in vertical ubplot = np.zeros((nz, nplot2len)) # time series for mean wind timeplot = np.zeros(nplot2len) G1 = np.zeros((nz, nplot2)) # time series for wave 1 flux G2 = np.zeros((nz, nplot2)) # time series for wave 2 flux F_gw1z = np.zeros(nz) # vector for wave 1 forcing at time n F_gw2z = np.zeros(nz) # vector for wave 2 forcing at time n f0 = np.zeros(nz) # vector for total forcing at time n fm1 = np.zeros(nz) # forcing at n-1 fm2 = np.zeros(nz) # at time n-1 ub0 = 0 # mean wind at z = 0 ub = 0.1*np.sin(np.pi*z/4) # initial mean wind profile # ub = z*0.1 # or try a shear ############################################################################ # the jit decorator below is optional # numba/jit is a just-in-time LLVM compiler. @jit # It provides a big speedup (> x20) def rhs_mean(Am, gwdamp, k1, k2, c1, c2, ub, f0, F_gw1z, F_gw2z, diffusv, dz, dz2, nz): """ rhs of mean wind equation """ # first compute vertical dependence of momentum flux divergence # compute the eastward and westward wave forcing if rossby == 1: gwdamp = 1. gw1z = gwdamp/(k1*(ub-c1)**2) gw2z = gwdamp/(k2*(ub-c2)**2) gw10 = gwdamp/(k1*(ub0-c1)**2) gw20 = gwdamp/(k2*(ub0-c2)**2) # # Now, if rossby = 1, replace G2 with the expression for Rossby waves if rossby == 1: beta = 32; amplit = 1; k2 = 3 ; gw20 = amplit*gwdamp/(k2*(ub0-c2)**2)*(1/(k2*(ub0 - c2)) - 1); gpart = amplit*gwdamp/(k2*(ub-c2)**2) ; rpart = beta/(k2**2*(ub - c2)) - 1 ; gw2z = gpart*rpart ; # now evaluate effect of gwave forcing for j in range(0, nz): F_gw1z[j] = famp*gw1z[j]*np.exp(-(0.5*(gw10+gw1z[j]) + np.sum(gw1z[1:j-1]))*dz) F_gw2z[j] = -famp*gw2z[j]*np.exp(-(0.5*(gw20+gw2z[j]) + np.sum(gw2z[1:j-1]))*dz) pass # now add wave plus mean flow diffusion terms to get total forcing f0[1:nzm1] = F_gw1z[1:nzm1] + F_gw2z[1:nzm1] \ + diffusv*rdz2*(ub[2:nz] - 2.*ub[1:nz-1]+ub[0:nz-2]) return (F_gw1z, F_gw2z, f0) pass def ustep(ic, ub, time, f0, fm1, fm2): """time stepping for mean wind equation advance ubar by one timestep, 3rd order AB The right-hand side is input as array f0 """ if ic == 0: # forward euler dt1 = dt dt2 = 0.0 dt3 = 0.0 elif ic == 1: # AB2 at step 2 dt1 = 1.5*dt dt2 = -0.5*dt dt3 = 0.0 else: # AB3 from step 3 on dt1 = 23./12.*dt dt2 = -16./12.*dt dt3 = 5./12.*dt ub[1:nz-1] = ub[1:nz-1] + \ (dt1*f0[1:nz-1] + dt2*fm1[1:nz-1] + dt3*fm2[1:nz-1]) ub[nz-1] = ub[nz-2] # top boundary condition of no gradient # ub[0] = 0 # not needed as it is not evolved. fm2[:] = fm1[:] fm1[:] = f0[:] time = time + dt return ub, fm1, fm2, time pass t_start = tm.clock() ifig = 1 count = 0 # Begin time stepping iplot1 = 0 iplot2 = 0 print ('nsteps, nplot1, nplot2', nsteps, nplot1, nplot2) fig1 = plt.figure(ifig) # make a movie of wind writer1.setup(fig1, "qbo.mp4", dpi=100) for ic in range(0,nsteps): count = count + 1 F_gw1z, F_gw2z, f0 = rhs_mean(famp, gwdamp, k1, k2, c1, c2, ub, f0, F_gw1z, F_gw2z, diffusv, dz, dz2, nz) ub, fm1, fm2, time = ustep(ic, ub, time, f0, fm1, fm2) # now a movie if ic % nplot1 == 0: iplot1 += 1 # fig1 = plt.figure(ifig) # make a movie of wind if iplot1 == 1: show_plot() # this brings the window to the front plt.clf() ax1 = fig1.add_subplot(1, 1, 1) ax1.plot(ub, zplot) plt.xlim((-1.0, +1.0)) ax1.set_xlabel('zonal wind') ax1.set_ylabel('Height') # plotlabel = "Time = %1.2f" % time plotlabel = "Time = {:1.2f}".format(time) ax1.set_title(plotlabel) # plt.draw() plt.pause(1e-7) writer1.grab_frame() if (ic % nplot2 == 0): ubplot[:,iplot2] = ub[:] timeplot[iplot2] = time iplot2 += 1 pass t_end = tm.clock() writer1.finish() print ('CPU time = ', t_end - t_start) # Now do some plotting. Some of the parameters here are hardwired for a # forcing amplitude of one. ifig += 1 fig2 = plt.figure(ifig) plt.clf() iplstart = 0 plt.set_cmap('Spectral_r') ax1 = fig2.add_axes([0.1, 0.1, 0.8, 0.5]) ax1.set_xlabel ('Time',size='18') ax1.set_ylabel ('Height', size='18') ax1.set_title('Zonal mean zonal wind',size=18) pcc = ax1.contourf(timeplot[iplstart:], z, ubplot[:,iplstart:]) ax1.contour(timeplot[iplstart:], z, ubplot[:,iplstart:], colors='k', linestyles='solid') ax1.contour(timeplot[iplstart:], z, ubplot[:,iplstart:], levels=[0], linewidths=2) ax2 = fig2.add_axes([0.92, 0.1, 0.02, 0.5]) ax1.set_xticks([20, 25, 30, 35, 40]) ax1.set_xlim((19.5,40.5)) ax1.set_yticks([0, 1, 2, 3., 4]) ax1.set_ylim((0, 3.2)) fig2.colorbar(pcc, cax=ax2) plt.savefig("qbo-contour.pdf") show_plot() ifig += 1 fig3 = plt.figure(ifig) plt.clf() plint = 35 for ipl in range(1,7): itpl = 400+(ipl-1)*plint timepl = timeplot[itpl] ax = fig3.add_subplot(2, 3, ipl) ax.plot(ubplot[:,itpl], zplot[:],linewidth=2) ax.set_xticks([-1, 0, 1.]) ax.set_xlim((-1.0, +1.0)) # ax.set_xlim((-0.8, +0.8)) ax.set_yticks([0, 1, 2, 3.]) ax.set_ylim((0, +3.1)) plotlabel = "t = %1.2f" % timepl ax.set_title(plotlabel,size=12) if ipl == 1: ax.set_ylabel('Height', size=14) elif ipl ==4: ax.set_ylabel('Height', size=14) if ipl > 3: ax.set_xlabel('u', style='italic', size=14) pass pass plt.savefig("qbo-period.pdf") # saves a PDF plot show_plot() ifig += 1 fig = plt.figure(ifig) plt.clf() ax = fig.add_subplot(1, 1, 1) ax.plot(timeplot, ubplot[50,:], 'k--', label = 'z = 2', linewidth=2) ax.plot(timeplot, ubplot[13,:], 'b-', label = 'z = 0.5', linewidth=2) ax.set_yticks([-0.8, -0.4, 0, 0.4, 0.8 ]) ax.set_ylim((-0.8, 0.8)) ax.set_xticks(np.arange(0, 41, 10)) ax.set_xlim((0, 40)) ax.tick_params(labelsize=18) ax.set_xlabel ('Time',size='20') ax.set_ylabel ('Zonal wind', size='20') ax.legend(frameon = True, prop={'size':20,'style':'italic'},loc=3) show_plot() # plt.show() plt.savefig("qbowind-time.pdf") # saves a PDF plot