{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "2a9badc3", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import scipy.fftpack\n", "import netCDF4 as netcdf\n", "import matplotlib.pyplot as plt\n", "from IPython.display import display,clear_output\n", "import xarray as xr\n", "import os\n", "\n", "g = 9.80665 # gravitation constant" ] }, { "cell_type": "markdown", "id": "1e3a6cd9", "metadata": {}, "source": [ "### The model equations\n", "$\\newcommand{\\V}[1]{\\vec{\\boldsymbol{#1}}}$\n", "$\\newcommand{\\I}[1]{\\widehat{\\boldsymbol{\\mathrm{#1}}}}$\n", "$\\newcommand{\\pd}[2]{\\frac{\\partial#1}{\\partial#2}}$\n", "$\\newcommand{\\ppd}[2]{\\frac{\\partial^2#1}{\\partial#2}}$\n", "$\\newcommand{\\pdt}[1]{\\frac{\\partial#1}{\\partial t}}$\n", "$\\newcommand{\\ddt}[1]{\\frac{\\D#1}{\\D t}}$\n", "$\\newcommand{\\D}{\\mathrm{d}}$\n", "$\\newcommand{\\Ii}{\\I{\\imath}}$\n", "$\\newcommand{\\Ij}{\\I{\\jmath}}$\n", "$\\newcommand{\\Ik}{\\I{k}}$\n", "$\\newcommand{\\VU}{\\V{U}}$\n", "$\\newcommand{\\del}{\\boldsymbol{\\nabla}}$\n", "$\\newcommand{\\dt}{\\cdot}$\n", "$\\newcommand{\\x}{\\times}$\n", "$\\newcommand{\\dv}{\\del\\cdot}$\n", "$\\newcommand{\\curl}{\\del\\times}$\n", "$\\newcommand{\\lapl}{\\nabla^2}$\n", "\n", "The equations for 2D (in x,z) atmospheric flow (momentum conservation, heat energy conservation and mass\n", "continuity) with incompressible Boussinesq approximation:\n", "\n", "\\begin{align}\n", "\\pdt{u} + u \\pd{u}{x} + w \\pd{u}{z}= - \\frac{1}{\\rho_0} \\pd{p'}{x}\n", "\\end{align}\n", "\n", "$$\n", "\\pdt{w} + u \\pd{w}{x} + w \\pd{w}{z}= - \\frac{1}{\\rho_0} \\pd{p'}{z} + g \\frac{\\theta'}{\\theta_0}\n", "$$\n", "\n", "$$\n", "\\pdt{\\theta'} + u \\pd{\\theta'}{x} + w \\pd{\\theta'}{z} = 0\n", "$$\n", "\n", "$$\n", "\\delta \\equiv \\pd{u}{x} + \\pd{w}{z} = 0\n", "$$\n", "\n", "where $p'$ and $\\theta'$ are perturbation pressure and potential temperature, respective, from the horizontally homogeneous hydrostatic mean state pressure $p_0$ and $\\theta_0$. \n", "\n", "The 2D equations essentially assume that there is no change along the y direction.\n", "\n", "### Equations for vorticity and stream function\n", "\n", "We can obtain an equation for the y component of vorticity, $\\eta$, by taking $\\pd{u Equation}{z} - \\pd{w Equation}{x}$. We will end up with the following equation\n", "\n", "$$\n", "\\pdt{\\eta} + u \\pd{\\eta}{x} + w \\pd{\\eta}{z} = - \\frac{g}{\\theta_0} \\pd{\\theta'}{x} \n", "$$\n", "\n", "where the right hand side is vorticity generation term due to horizontal gradient of potential temperature or buoyancy. Vorticity $\\eta$ is defined by\n", "\n", "$$\n", "\\eta \\equiv \\pd{u}{z} - \\pd{w}{x}.\n", "$$\n", "\n", "For 2D incompressible flow, the velocity component can be expressed in terms of stream function $\\psi$:\n", "\n", "$$\n", "u = \\pd{\\psi}{z},\n", "$$\n", "\n", "$$\n", "w = - \\pd{\\psi}{x}.\n", "$$\n", "\n", "\n", "Plug the above into $\\eta$ definition above, we get \n", "\n", "$$\n", "\\eta = \\ppd{\\psi}{x^2} + \\ppd{\\psi}{z^2}.\n", "$$\n", "\n", "The above equation is a Possion equation which belongs to the class of elliptic equations. When vorticity $\\eta$ and boundary values of $\\psi$ are known, stream function $\\psi$ in the interior domain can be obtain by solving the elliptic equation. \n", "\n", "We will formulate a x-z 2D cloud model by integrating $\\eta$ and $\\theta'$ equations in time to obtain their values at future time level, then solve the vorticity Possion to obtain stream function $\\psi$. $u$ and $w$ at the future time level can then be calculated from $\\psi$. \n", "\n", "## Finite difference equations for the cloud model\n", "\n", "We will us upstream-forward scheme for the time integration. For the case of $u_{i,k} \\ge 0$ and $w_{i,k} \\ge 0$, the time integration equations for $\\eta$ and $\\theta'$ are\n", "\n", "$$\n", "\\eta_{i,k}^{n+1} = \\eta_{i,k}^n - {\\Delta}t u_{i,k}^n\\frac{\\eta_{i,k}^n -\\eta_{i-1,k}^n}{{\\Delta}x} - {\\Delta}t w_{i,k}^n\\frac{\\eta_{i,k}^n -\\eta_{i,k-1}^n}{{\\Delta}z}\n", "- {\\Delta}t \\frac{g}{\\theta_0} \\frac{{\\theta'}_{i+1,k}^n - {\\theta'}_{i-1,k}^n}{2{\\Delta}x}\n", "$$\n", "\n", "$$\n", "{\\theta'}_{i,k}^{n+1} = {\\theta'}_{i,k}^n - {\\Delta}t u_{i,k}^n\\frac{{\\theta'}_{i,k}^n -{\\theta'}_{i-1,k}^n}{{\\Delta}x} - {\\Delta}t w_{i,k}^n\\frac{{\\theta'}_{i,k}^n -{\\theta'}_{i,k-1}^n}{{\\Delta}z}\n", " $$\n", " \n", "For $u_{i,k}$ and $w_{i,k}$ or different signs, the advection terms need to use different grid points to ensure upstream advection all the time. " ] }, { "cell_type": "markdown", "id": "22f09a7c", "metadata": {}, "source": [ "### Set model configuration parameters" ] }, { "cell_type": "code", "execution_count": null, "id": "483bb3fc", "metadata": {}, "outputs": [], "source": [ "# Remember for python, array index starts from 0, so array index goes from 0 to nx-1 in x-direction" ] }, { "cell_type": "markdown", "id": "60644533", "metadata": {}, "source": [ "### Specify the grid:\n", "Select a grid size that allows the Poisson solver to be fast if using the FFT solver.\n", "\n", "2n +1 for `nx` and `nz` is ideal." ] }, { "cell_type": "markdown", "id": "6eb8e20a", "metadata": {}, "source": [ "## Using successive over-relaxation (SOR) method to iteratively solve streamfunction elliptic equation\n", "\n", "$$\n", "\\ppd{\\psi}{x^2} + \\ppd{\\psi}{z^2} = \\eta\n", "$$\n", "\n", "Using center differencing, the finite difference form of the equation is\n", "\n", "$$\n", "\\frac{\\psi_{i+1,k}-2\\psi_{i,k}+\\psi_{i-1,k} }{{\\Delta}x^2} + \\frac{\\psi_{i,k+1}-2\\psi_{i,k}+\\psi_{i,k-1} }{{\\Delta}x^2} = \\eta_{i,k}.\n", "$$\n", "\n", "After some manipulations, the equation can be rewritten as\n", "\n", "$$\n", "\\psi_{i,k} = - C \\eta_{i,k} + c_x(\\psi_{i+1,k}+\\psi_{i-1,k}) + c_z(\\psi_{i,k+1}+\\psi_{i,k-1}), \n", "$$\n", "where\n", "\n", "$$\n", "c_x = \\frac{{\\Delta}z^2}{2({\\Delta}x^2+{\\Delta}z^2)}, c_z = \\frac{{\\Delta}x^2}{2({\\Delta}x^2+{\\Delta}z^2)}, C = \\frac{{\\Delta}x^2{\\Delta}z^2}{2({\\Delta}x^2+{\\Delta}z^2)}.\n", "$$\n", "\n", "The SOR method use the following formula to iteratively update $\\psi$:\n", " \n", "$$\n", "R_{i,k}^\\nu = c_x(\\psi_{i+1,k}^{\\nu}+\\psi_{i-1,k}^{\\nu+1}) + c_z(\\psi_{i,k+1}^{\\nu}+\\psi_{i,k-1}^{\\nu+1}) - C \\eta_{i,k} - \\psi_{i,k}^{\\nu},\n", "$$\n", "\n", "$$\n", "\\psi_{i,k}^{\\nu+1} = \\psi_{i,k}^{\\nu} + \\alpha R_{i,k}^\\nu.\n", "$$\n", "\n", "Where $\\nu$ is the iteration number. When you always use the latest updated $\\psi$ in the calculation, the superscript $\\nu$ can be dropped from the formula. $\\alpha$ is an over-relaxation coefficient whose optimal value is between 1 and 2. " ] }, { "cell_type": "code", "execution_count": null, "id": "d6a6f26a", "metadata": {}, "outputs": [], "source": [ "def poisson2d(soriter_max,nx,nz,dx,dz, vort, streamf):\n", "# INPUT:\n", "# soriter_max: The number of iterations to perform\n", "# nx, nz The number of grid points in x and z directions.\n", "# dx, dz: grid spacings in x and z directions.\n", "# vort: Vorticity the defines the right hand side to the vorticity Possion equation.\n", "# streamf: The first guess of stream function \n", "#\n", "# OUTPUT:\n", "# streamf: The updated stream function that is the solution to the Possion equation.\n", "# \n", "# Calculate coefficients for Poisson solver\n", " \n", " # Calculate coefficients for Poisson solver\n", " cx = ? \n", " cz = ?\n", " C = ?\n", " \n", " # Calculate optimal alpha\n", " # M,N in t_m equation are # of grid intervals, not # of grid points\n", " t_m = np.cos(np.pi/(nx-1)) + np.cos(np.pi/(nz-1))\n", " alpha_opt = (8.-4.*np.sqrt(4.-t_m**2.))/t_m**2. # Optimal over-relaxation coefficient. The formula in presentation/handout is an approximate version of this.\n", " \n", " for iter in np.arange(soriter_max):\n", "\n", " for i in np.arange(1, nx-1, 1):\n", " for k in np.arange(1, nz-1, 1):\n", " R = ?\n", " streamf[k,i] = streamf[k,i] + alpha_opt*R\n", " " ] }, { "cell_type": "markdown", "id": "188a7623", "metadata": {}, "source": [ "## Also explicitly calculate x, z, and t arrays" ] }, { "cell_type": "code", "execution_count": null, "id": "ddb2acde-217b-4e7c-8576-6f01ea5c2c48", "metadata": {}, "outputs": [], "source": [ "x_grid = np.arange(0,nx,1)*dx\n", "z_grid = np.arange(0,nz,1)*dz\n", "t_grid = np.arange(0,numt,1)*dt\n", "\n", "# Initalize state variable arrays to zero\n", "u = np.zeros((numt,nz,nx))\n", "w = np.zeros((numt,nz,nx))\n", "vort = np.zeros((numt,nz,nx)) # Greek letter nu in equations\n", "theta = np.zeros((numt,nz,nx)) # Note theta here is perturbations per governing equations\n", "streamf = np.zeros((numt,nz,nx))\n", "\n", "# Initial thermal perturbation\n", "\n", "thermradx = 250. # Initial thermal bubble horizontal radius in m\n", "thermradz = 200. # Initial thermal bubble vertical radius in m\n", "\n", "# Remember: python indicies start at 0\n", "thermx = lenx*0.5 # Initial horizontal position of thermal bubble " ] }, { "cell_type": "markdown", "id": "5137e2be-fcbf-417e-896f-3978a4992d14", "metadata": {}, "source": [ "## Specify a hot thermal bubble of 5K - you will get a rising mushroom cloud. \n", "## Google search for 'mushroom cloud'." ] }, { "cell_type": "code", "execution_count": null, "id": "49598cfc-2266-4f65-9988-c8d8316a9b0e", "metadata": {}, "outputs": [], "source": [ "thermz = 0.2*lenz # Initial vertical position of thermal bubble\n", "delpt = 10. # Thermal bubble perturbation" ] }, { "cell_type": "markdown", "id": "9c6b9ac6-dfd8-4186-9f09-b712fc20c020", "metadata": {}, "source": [ "# Specify a -5 K cold bubble dropping to the ground, you will get a solution like downburst and thunderstorm outflow/gust front. The gust front propagates along the ground in the form of a density current.\n", "# Google search for \"photograph of gust front outflow\" " ] }, { "cell_type": "code", "execution_count": null, "id": "6628cc5a-afe9-441a-b6ba-33a7144dc6f1", "metadata": {}, "outputs": [], "source": [ "#thermz = 0.3*lenz # Initial vertical position of thermal bubble\n", "#delpt = -10. # Thermal bubble perturbation\n", "\n", "if delpt >= 0.0:\n", " runname = 'HotBubble'+str(nx)\n", "else:\n", " runname = 'ColdBubble'+str(nx)\n", "\n", "for i in np.arange(1,nx-1):\n", " for k in np.arange(1,nz-1):\n", " radnd = np.sqrt(((i*dx-thermx)/thermradx)**2.+((k*dx-thermz)/thermradz)**2.)\n", " if(radnd < 1.):\n", " theta[0,k,i] = theta[0,k,i] + delpt*(np.cos(radnd*np.pi/2.)**2.)" ] }, { "cell_type": "markdown", "id": "088e7d29", "metadata": {}, "source": [ "## Perform time integration of vortiticity and potential temperature equations using the following finite different equations. Only the case of positive u and w is given as an example.\n", "\n", "For the case of $u_{i,k} \\ge 0$ and $w_{i,k} \\ge 0$, the time integration equations for $\\eta$ and $\\theta'$ are\n", "\n", "$$\n", "\\eta_{i,k}^{n+1} = \\eta_{i,k}^n - {\\Delta}t u_{i,k}^n\\frac{\\eta_{i,k}^n -\\eta_{i-1,k}^n}{{\\Delta}x} - {\\Delta}t w_{i,k}^n\\frac{\\eta_{i,k}^n -\\eta_{i,k-1}^n}{{\\Delta}z}\n", "- {\\Delta}t \\frac{g}{\\theta_0} \\frac{{\\theta'}_{i+1,k}^n - {\\theta'}_{i-1,k}^n}{2{\\Delta}x}\n", "$$\n", "\n", "$$\n", "{\\theta'}_{i,k}^{n+1} = {\\theta'}_{i,k}^n - {\\Delta}t u_{i,k}^n\\frac{{\\theta'}_{i,k}^n -{\\theta'}_{i-1,k}^n}{{\\Delta}x} - {\\Delta}t w_{i,k}^n\\frac{{\\theta'}_{i,k}^n -{\\theta'}_{i,k-1}^n}{{\\Delta}z}\n", "$$\n", " \n", "For $u_{i,k}$ and $w_{i,k}$ or different signs, the advection terms need to use different grid points to ensure upstream advection all the time. \n", "\n", "After $\\eta$ and $\\theta'$ at time level $n+1$ are obtained, Possion equation for $\\psi$ is solved by calling function poisson2d or poisson_fft.\n", "\n", "$u$ and $w$ are obtained from $\\psi$ according to \n", "\n", "$$\n", "u_{i,k}^{n+1} = \\frac{{\\psi}_{i,k+1}^{n+1} - {\\psi}_{i,k-1}^n}{2{\\Delta}z},\n", "$$\n", "\n", "$$\n", "w_{i,k}^{n+1} = -\\frac{{\\psi}_{i+1,k}^{n+1} - {\\psi}_{i-1,k}^n}{2{\\Delta}x}.\n", "$$" ] }, { "cell_type": "code", "execution_count": null, "id": "72f7db1e", "metadata": {}, "outputs": [], "source": [ "# main time loop, horizontal loop, vertical loop\n", "# note that boundary points remain zero to satisfy boundary conditions\n", "for t in np.arange(0, numt-1, 1):\n", "\n", " for i in np.arange(1, nx-1, 1):\n", " for k in np.arange(1, nz-1, 1):\n", "\n", " adv_vort_x = ? # add code to calculate x advection of vorticity\n", " adv_vort_z = ? # add code to calculate z advection of vorticity\n", " \n", " adv_vort = adv_vort_x + adv_vort_z\n", " \n", " adv_theta_x = ? # add code to calculate x advection of theta\n", " adv_theta_z = ? # add code to calculate x advection of theta\n", " \n", " adv_theta = adv_theta_x + adv_theta_z\n", " \n", " vort_gen = ? # add code to calculate buoyancy vorticity generation term\n", "\n", " # Advance vorticity and theta one time step\n", "\n", " vort[t+1,k,i] = vort[t,k,i]+dt*adv_vort + vort_gen\n", " theta[t+1,k,i] = theta[t,k,i]+dt*adv_theta\n", " \n", " # Call poisson2d function to solve for streamfunction from vorticity\n", " # Note that this loop updates from t to t+1, so the following equations are valid after this update (i.e, t = t+1)\n", " \n", "# Solve stream function Poisson equation using SOR iteration method - you will code up this function\n", "\n", " streamf[t+1,:,:]=streamf[t,:,:] # set first guess to previous time level value\n", " soriter_max = 30 # Max number of iterations\n", " poisson2d(soriter_max,nx,nz,dx,dz,vort[t+1,:,:],streamf[t+1,:,:])\n", " \n", "# Calculate u,w from streamfunction (centered in space) \n", " for i in np.arange(1, nx-1, 1):\n", " for k in np.arange(1, nz-1, 1):\n", " u[t+1,k,i] = ? # calculate u from stream function\n", " w[t+1,k,i] = ? # calculate w from stream function\n", " \n", " # Enforce boundary conditions (zero vertical gradient for u at top and bottom boundaries). \n", " # u kept as 0 at left and right boundaries (no change)\n", " # k=0 and k=nz-1 represent the top and bottom boundary conditions\n", " u[t+1, 0,:] = u[t+1, 1,:]\n", " u[t+1,nz-1,:] = u[t+1,nz-2,:]\n", " \n", " # Enforce boundary conditions (zero horizontal gradient for w at left and right booundaries)\n", " # w kept as 0 at top and bottom boundaries (no change)\n", " # i=0 and i=nx-1 represent the left and right boundary conditions\n", " w[t+1,:, 0] = w[t+1,:, 1]\n", " w[t+1,:,nx-1] = w[t+1,:,nx-2]\n", " \n", " time = (t+1) * dt\n", "\n", " umax = np.max( u[t+1,:,:] )\n", " wmax = np.max( w[t+1,:,:] )\n", " ptmax = np.max( theta[t+1,:,:] )\n", " vtmax = np.max( vort[t+1,:,:] )\n", " sfmax = np.max( streamf[t+1,:,:] )\n", "\n", " umin = np.min( u[t+1,:,:] )\n", " wmin = np.min( w[t+1,:,:] )\n", " ptmin = np.min( theta[t+1,:,:] )\n", " vtmin = np.min( vort[t+1,:,:] )\n", " sfmin = np.min( streamf[t+1,:,:] )\n", " \n", " if np.mod(t+1,5) == 0: \n", " print(\"umin,umax,wmin,wmax,ptmin,ptmax,vtmin,vtmax,sfmin,sfmax, at time {0:6.0f} (s) are: \\\n", " {1:6.2f} {2:6.2f} {3:6.2f} {4:6.2f} {5:6.2f} {6:6.2f} {7:6.4f} {8:6.4f} {9:6.2f} {10:6.2f}\".format\\\n", " (time,umin,umax,wmin,wmax,ptmin,ptmax,vtmin,vtmax,sfmin,sfmax) )\n", " " ] }, { "cell_type": "code", "execution_count": null, "id": "d5be8df7", "metadata": {}, "outputs": [], "source": [ "# Write variables to netcdf file\n", "\n", "ncfile = netcdf.Dataset(runname+'_test.nc','w')\n", "\n", "# Create x,z,t dimensions\n", "ncfile.createDimension('time',numt)\n", "ncfile.createDimension('z',nz)\n", "ncfile.createDimension('x',nx)\n", "\n", "# Create x, z, t, u, w, vort, theta, streamf variables\n", "# Note 'f' refers to float type\n", "x_var = ncfile.createVariable('x',np.dtype('float32').char,('x',))\n", "z_var = ncfile.createVariable('z',np.dtype('float32').char,('z',))\n", "t_var = ncfile.createVariable('time',np.dtype('float32').char,('time',))\n", "\n", "u_var = ncfile.createVariable('u',np.dtype('float32').char,('time','z','x',))\n", "w_var = ncfile.createVariable('w',np.dtype('float32').char,('time','z','x',))\n", "vort_var = ncfile.createVariable('vort',np.dtype('float32').char,('time','z','x',))\n", "theta_var = ncfile.createVariable('theta',np.dtype('float32').char,('time','z','x',))\n", "streamf_var = ncfile.createVariable('streamf',np.dtype('float32').char,('time','z','x',))\n", "\n", "# Assign units to variables\n", "x_var.units = 'm'\n", "z_var.units = 'm'\n", "t_var.units = 's'\n", "\n", "u_var.units = 'm s^-1'\n", "w_var.units = 'm s^-1'\n", "vort_var.units = 's^-1'\n", "theta_var.units = 'K'\n", "streamf_var.units = 'm^2 s^-1'\n", "\n", "# Write data\n", "x_var[:] = x_grid\n", "z_var[:] = z_grid\n", "t_var[:] = t_grid\n", "\n", "u_var[:,:,:] = u\n", "w_var[:,:,:] = w\n", "theta_var[:,:,:] = theta\n", "streamf[:,:,:] = streamf\n", "vort_var[:,:,:] = vort\n", "\n", "# Close file\n", "ncfile.close()" ] }, { "cell_type": "code", "execution_count": null, "id": "a9d338b7-423a-4e47-8e72-0feb4aa1e732", "metadata": {}, "outputs": [], "source": [ "# Make plots\n", "# First construct figure and axes of plot\n", "fig_ex = plt.figure( figsize=(15, 17) )\n", "ax_ex = fig_ex.add_subplot(111)\n", "\n", "outdir=runname+'_pngs'\n", "\n", "if outdir and not os.path.exists(outdir): os.mkdir(outdir)\n", "\n", "# Time loop to display plots sequentially\n", "for t in np.arange(0,numt,1):\n", " \n", " # Clear prior axis data so plots don't stack on top of each other\n", " ax_ex.clear()\n", " \n", " # Note arrays are (row,column...) in python indices. Since i is column and k is\n", " # row in our model, we need to simply transverse 2-D arrays to align with the \n", " # expected plots\n", " \n", " # Plot theta\n", " \n", " sfmax = np.max( streamf[t,:,:] )\n", " sfmin = np.min( streamf[t,:,:] )\n", " \n", " #print(\"sfmin= {0:6.2f}, sfmax= {1:6.2f} at time {2:6.0f} (s)\".format(sfmin, sfmax,t*dt) )\n", " \n", " levels = [0.0,0.125,0.25,0.5,0.75,1.0,1.25,1.5,1.75,2.0,2.5,3.0,3.5,4.0,4.5,5.0]\n", " levels = np.arange(0,3.5,0.5)\n", " \n", "# theta_plot = ax_ex.contourf(x_grid,z_grid,theta[t,:,:], levels=levels, extend='both')\n", " theta_plot = ax_ex.contourf(x_grid,z_grid,theta[t,:,:], extend='both')\n", "\n", " levels=np.arange(-1300,1400,50)\n", " streamf_plot = ax_ex.contour(x_grid,z_grid,streamf[t,:,:],\\\n", " levels=levels,colors='red')\n", " \n", " levels=np.arange(-0.07,0.08,0.01)\n", " vort_plot = ax_ex.contour(x_grid,z_grid,vort[t,:,:],levels=levels,colors='blue')\n", " \n", " # Plot wind vectors at windintv intervals (python slices arrays as [start:stop:interval])\n", " windintv = 1 # Plot interval for wind vectors\n", " vector_plot = ax_ex.quiver(x_grid[::windintv],z_grid[::windintv],\\\n", " u[t,::windintv,::windintv],w[t,::windintv,::windintv], color='white')\n", "\n", " time = t*dt\n", " plt.title('Simulated theta at time {0:6.1f} (s)'.format(time),fontsize=20 )\n", " \n", " if t == 0:\n", " cb1 = fig_ex.colorbar(theta_plot, ax=ax_ex, location='bottom', shrink=1.0, fraction=0.05, pad=0.05)\n", " cb1.set_label('(K)', size='x-large')\n", "\n", " # Display plots\n", " clear_output(wait=True)\n", " display(fig_ex)\n", " \n", " if outdir: \n", " timestamp = round(time,2)\n", " pngname = outdir+'/'+runname+'%06d.png' % round(timestamp) \n", "# print( pngname )\n", " plt.savefig(pngname, dpi=72, facecolor='w', edgecolor='w', orientation='portrait',bbox_inches = 'tight')\n", " \n", "clear_output()" ] }, { "cell_type": "code", "execution_count": null, "id": "c031dd2d", "metadata": {}, "outputs": [], "source": [ "from janim import makeanim\n", "import glob\n", "\n", "pngs = glob.glob(outdir+'/*.png') # the * matches anything\n", "pngs.sort()\n", "makeanim(pngs,outfile=runname+'.html',sortOrder=True,\n", " ctlOnSide=True,titlestring=\"Animation of 2D Thermal Bubble Convection\")" ] }, { "cell_type": "code", "execution_count": null, "id": "ad58b8ca-d479-44c0-90de-a0c34b504b04", "metadata": {}, "outputs": [], "source": [ "#ds = xr.open_dataset(runname+'.nc')\n", "\n", "#ds" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.11" } }, "nbformat": 4, "nbformat_minor": 5 }