#!/usr/bin/env python # coding: utf-8 # # ENV/ATM 415: Climate Laboratory # # [Brian E. J. Rose](http://www.atmos.albany.edu/facstaff/brose/index.html), University at Albany # # # Lecture 16: The one-dimensional Energy Balance Model # ## Contents # # 1. [Heat transport and the energy budget for each latitude band](#section1) # 2. [Parameterizing the radiation terms](#section2) # 3. [Tuning the longwave parameters with reanalysis data](#section3) # 4. [The one-dimensional diffusive energy balance model](#section4) # 5. [The annual-mean EBM](#section5) # 6. [Tuning the diffusivity](#section6) # 7. [Summary: parameter values in the diffusive EBM](#section7) # ____________ # # # ## 1. Heat transport and the energy budget for each latitude band # ____________ # Way back at the beginning of the semester we wrote down a **global average energy budget** that looked like # # $$ C \frac{dT}{dt} = \text{ASR} - \text{OLR} $$ # Last time we talked about the additional **local heating** of each latitude band associated with atmospheric and oceanic motions: # # $$ h = - \frac{1}{2 \pi a^2 \cos⁡\phi } \frac{\partial \mathcal{H}}{\partial \phi} $$ # Using this, we can write down a **local energy budget** expressing how the temperature of each latitude band should change: # # $$ C \frac{\partial T}{\partial t} = \text{ASR} - \text{OLR} - \frac{1}{2 \pi a^2 \cos⁡\phi } \frac{\partial \mathcal{H}}{\partial \phi}$$ # Now the temperature $T$ is a function of latitude $\phi$ as well as time $t$ (so we write it as a partial derivative) # We also introduced the **diffusive heat transport parameterization** # # $$ \mathcal{H}(\phi) = -2 \pi ~a^2 \cos\phi D \frac{\partial T}{\partial \phi} $$ # Putting this parameterization into the budget above gives # # $$ C \frac{\partial T}{\partial t} = \text{ASR} - \text{OLR} + \frac{D}{\cos⁡\phi } \frac{\partial}{\partial \phi} \left( \cos\phi \frac{\partial T}{\partial \phi} \right)$$ # # (where we pulled the parameter $D$ out of the derivative because we are assuming it is a constant) # ____________ # # # ## 2. Parameterizing the radiation terms # ____________ # To turn our **budget** into a **model**, we need specific parameterizations that link the radiation ASR and OLR to surface temperature $T$ (the state variable for our model). # ### Fixed albedo assumption # First, as usual, we can write the solar term as # # $$ \text{ASR} = (1-\alpha) ~ Q $$ # For now, we will **assume that the planetary albedo is fixed (does not depend on temperature)**. Therefore the entire shortwave term $(1-\alpha) Q$ is a fixed source term in our budget. It varies in space and time but does not depend on $T$. # Note that the solar term is (at least in annual average) larger at equator than poles… and transport term acts to flatten out the temperatures. # ### Parameterizing the longwave radiation # Now, we almost have a model we can solve for $T$! Just need a function OLR$(T)$ expressing the temperature dependence of the emission to space. # # So… what’s the link between OLR and temperature???? # # [ discuss ] # We spent a good chunk of the course looking at this question, and developed a model of a vertical column of air. # # We are trying now to build a model of the equator-to-pole (or pole-to-pole) temperature structure. # We COULD use an array of column models, representing temperature as a function of height and latitude (and time). # # But instead, we will keep things simple, one spatial dimension at a time. # Introduce the following **simple parameterization**: # # $$ \text{OLR} = A + B T $$ # # With: # # - $T$ the zonal average surface temperature in ºC # - $A$ is a constant in W m$^{-2}$ # - $B$ is a constant in W m$^{-2}$ ºC$^{-1}$. # # Think of $A$ as an inverse measure of the greenhouse gas amount (Why?). # # The parameter $B$ is closely related to the **climate feedback parameter** $\lambda$ that we defined a while back. The only difference is that in the EBM we are going to explicitly separate the **albedo feedback** from all other radiative feedbacks. # ____________ # # # ## 3. Tuning the longwave parameters with reanalysis data # ____________ # ### OLR versus surface temperature in NCEP Reanalysis data # # Let's look at the data to find reasonable values for $A$ and $B$. # In[1]: get_ipython().run_line_magic('matplotlib', 'inline') import numpy as np import matplotlib.pyplot as plt import xarray as xr import climlab # In[2]: ncep_url = "http://www.esrl.noaa.gov/psd/thredds/dodsC/Datasets/ncep.reanalysis.derived/" ncep_air = xr.open_dataset( ncep_url + "pressure/air.mon.1981-2010.ltm.nc", decode_times=False) ncep_Ts = xr.open_dataset( ncep_url + "surface_gauss/skt.sfc.mon.1981-2010.ltm.nc", decode_times=False) lat_ncep = ncep_Ts.lat; lon_ncep = ncep_Ts.lon print( ncep_Ts) # In[3]: Ts_ncep_annual = ncep_Ts.skt.mean(dim=('lon','time')) # In[4]: ncep_ulwrf = xr.open_dataset( ncep_url + "other_gauss/ulwrf.ntat.mon.1981-2010.ltm.nc", decode_times=False) ncep_dswrf = xr.open_dataset( ncep_url + "other_gauss/dswrf.ntat.mon.1981-2010.ltm.nc", decode_times=False) ncep_uswrf = xr.open_dataset( ncep_url + "other_gauss/uswrf.ntat.mon.1981-2010.ltm.nc", decode_times=False) OLR_ncep_annual = ncep_ulwrf.ulwrf.mean(dim=('lon','time')) ASR_ncep_annual = (ncep_dswrf.dswrf - ncep_uswrf.uswrf).mean(dim=('lon','time')) # In[5]: from scipy.stats import linregress slope, intercept, r_value, p_value, std_err = linregress(Ts_ncep_annual, OLR_ncep_annual) print( 'Best fit is A = %0.0f W/m2 and B = %0.1f W/m2/degC' %(intercept, slope)) # We're going to plot the data and the best fit line, but also another line using these values: # In[6]: # More standard values A = 210. B = 2. # In[7]: fig, ax1 = plt.subplots(figsize=(8,6)) ax1.plot( Ts_ncep_annual, OLR_ncep_annual, 'o' , label='data') ax1.plot( Ts_ncep_annual, intercept + slope * Ts_ncep_annual, 'k--', label='best fit') ax1.plot( Ts_ncep_annual, A + B * Ts_ncep_annual, 'r--', label='B=2') ax1.set_xlabel('Surface temperature (C)', fontsize=16) ax1.set_ylabel('OLR (W m$^{-2}$)', fontsize=16) ax1.set_title('OLR versus surface temperature from NCEP reanalysis', fontsize=18) ax1.legend(loc='upper left') ax1.grid() # Discuss these curves... # # Suggestion of at least 3 different regimes with different slopes (cold, medium, warm). # # Unbiased "best fit" is actually a poor fit over all the intermediate temperatures. # # The astute reader will note that... by taking the zonal average of the data before the regression, we are biasing this estimate toward cold temperatures. [WHY?] # # # Let's take these reference values: # # $$ A = 210 ~ \text{W m}^{-2}, ~~~ B = 2 ~ \text{W m}^{-2}~^\circ\text{C}^{-1} $$ # Note that in the **global average**, recall $\overline{T_s} = 288 \text{ K} = 15^\circ\text{C}$ # # And so this parameterization gives # # $$ \overline{\text{OLR}} = 210 + 15 \times 2 = 240 ~\text{W m}^{-2} $$ # # And the observed global mean is $\overline{\text{OLR}} = 239 ~\text{W m}^{-2} $ # So this is consistent. # # # # ### Relationship between $B$ and feedback parameters # Recall that when we looked at climate forcing and feedback, we said that overall response to a forcing $\Delta R$ in W m$^{-2}$ is # # $$ \Delta T = \frac{\Delta R}{\lambda} $$ # # and where $\lambda$ is the overall **climate feedback parameter**: # # $$\lambda = \lambda_0 - \sum_{i=1}^{N} \lambda_i $$ # # and # # - $\lambda_0 = 3.3$ W m$^{-2}$ K$^{-1}$ is the no-feedback climate response # - $\sum_{i=1}^{N} \lambda_i$ is the sum of all radiative feedbacks, defined to be **positive** for **amplifying processes**. # More positive feedbacks thus mean that $\lambda$ is a smaller number, which means the response to a given forcing is larger! # Here in the EBM the parameter $B$ plays the same role as $\lambda$ -- a smaller number means a more sensitive model. # Our estimate $B = 2 ~ \text{W m}^{-2}~^\circ\text{C}^{-1}$ thus implies that the sum of all LW feedback processes (including water vapor, lapse rates and clouds) is # # $$ \sum_{i=1}^{N} \lambda_i = 3.3 ~\text{W m}^{-2}~^\circ\text{C}^{-1} - 2 ~\text{W m}^{-2}~^\circ\text{C}^{-1} = 1.3 ~\text{W m}^{-2}~^\circ\text{C}^{-1} $$ # Looking back at the chart of feedback parameter values from GCMs, does this seem plausible? # ____________ # # # ## 4. The one-dimensional diffusive energy balance model # ____________ # # # Putting the above OLR parameterization into our budget equation gives # # $$ C \frac{\partial T}{\partial t} = (1-\alpha) ~ Q - \left( A + B~T \right) + \frac{D}{\cos⁡\phi } \frac{\partial }{\partial \phi} \left( \cos⁡\phi ~ \frac{\partial T}{\partial \phi} \right) $$ # This is the equation for a very important and useful simple model of the climate system. It is typically referred to as the (one-dimensional) Energy Balance Model. # # (although as we have seen over and over, EVERY climate model is actually an “energy balance model” of some kind) # # Also for historical reasons this is often called the **Budyko-Sellers model**, after Budyko and Sellers who both (independently of each other) published influential papers on this subject in 1969. # Recap: parameters in this model are # # - C: heat capacity in J m$^{-2}$ ºC$^{-1}$ # - A: longwave emission at 0ºC in W m$^{-2}$ # - B: increase in emission per degree, in W m$^{-2}$ ºC$^{-1}$ # - D: horizontal (north-south) diffusivity of the climate system in W m$^{-2}$ ºC$^{-1}$ # # We also need to specify the albedo. # ### Tune albedo formula to match observations # # Let's go back to the NCEP Reanalysis data to see how planetary albedo actually varies as a function of latitude. # In[8]: days = np.linspace(1.,50.)/50 * climlab.constants.days_per_year Qann_ncep = np.mean( climlab.solar.insolation.daily_insolation(lat_ncep, days ),axis=1) albedo_ncep = 1 - ASR_ncep_annual / Qann_ncep albedo_ncep_global = np.average(albedo_ncep, weights=np.cos(np.deg2rad(lat_ncep))) # In[9]: print( 'The annual, global mean planetary albedo is %0.3f' %albedo_ncep_global) fig,ax = plt.subplots() ax.plot(lat_ncep, albedo_ncep) ax.grid(); ax.set_xlabel('Latitude') ax.set_ylabel('Albedo') # **The albedo increases markedly toward the poles.** # # There are several reasons for this: # # - surface snow and ice increase toward the poles # - Cloudiness is an important (but complicated) factor. # - Albedo increases with solar zenith angle (the angle at which the direct solar beam strikes a surface) # #### Approximating the observed albedo # # The albedo curve can be approximated by a smooth function that increases with latitude: # # $$ \alpha(\phi) \approx \alpha_0 + \alpha_2 P_2(\sin\phi) $$ # # where $P_2$ is a function called the 2nd Legendre polynomial. Don't worry about exactly what this means. This is what it looks like: # In[10]: # Add a new curve to the previous figure a0 = albedo_ncep_global a2 = 0.25 ax.plot(lat_ncep, a0 + a2 * climlab.utils.legendre.P2(np.sin(np.deg2rad(lat_ncep)))) fig # Of course we are not fitting all the details of the observed albedo curve. But we do get the correct global mean a reasonable representation of the equator-to-pole gradient in albedo. # ____________ # # # ## 5. The annual-mean EBM # ____________ # For now, we will be focusing on the **annual mean** model. # # For the insolation, we set $Q(\phi,t) = \bar{Q}(\phi)$, the annual mean value (large at equator, small at pole). # ### Animating the adjustment of annual mean EBM to equilibrium # Before looking at the details of how to set up an EBM in `climlab`, let's look at an animation of the adjustment of the model (its temperature and energy budget) from an isothermal initial condition. # # For reference, all the code necessary to generate the animation is here in the notebook. # In[11]: # Some imports needed to make and display animations from IPython.display import HTML from matplotlib import animation def setup_figure(): templimits = -20,32 radlimits = -340, 340 htlimits = -6,6 latlimits = -90,90 lat_ticks = np.arange(-90,90,30) fig, axes = plt.subplots(3,1,figsize=(8,10)) axes[0].set_ylabel('Temperature (deg C)') axes[0].set_ylim(templimits) axes[1].set_ylabel('Energy budget (W m$^{-2}$)') axes[1].set_ylim(radlimits) axes[2].set_ylabel('Heat transport (PW)') axes[2].set_ylim(htlimits) axes[2].set_xlabel('Latitude') for ax in axes: ax.set_xlim(latlimits); ax.set_xticks(lat_ticks); ax.grid() fig.suptitle('Diffusive energy balance model with annual-mean insolation', fontsize=14) return fig, axes def initial_figure(model): # Make figure and axes fig, axes = setup_figure() # plot initial data lines = [] lines.append(axes[0].plot(model.lat, model.Ts)[0]) lines.append(axes[1].plot(model.lat, model.ASR, 'k--', label='SW')[0]) lines.append(axes[1].plot(model.lat, -model.OLR, 'r--', label='LW')[0]) lines.append(axes[1].plot(model.lat, model.net_radiation, 'c-', label='net rad')[0]) lines.append(axes[1].plot(model.lat, model.heat_transport_convergence(), 'g--', label='dyn')[0]) lines.append(axes[1].plot(model.lat, np.squeeze(model.net_radiation)+model.heat_transport_convergence(), 'b-', label='total')[0]) axes[1].legend(loc='upper right') lines.append(axes[2].plot(model.lat_bounds, model.diffusive_heat_transport())[0]) lines.append(axes[0].text(60, 25, 'Day 0')) return fig, axes, lines def animate(day, model, lines): model.step_forward() # The rest of this is just updating the plot lines[0].set_ydata(model.Ts) lines[1].set_ydata(model.ASR) lines[2].set_ydata(-model.OLR) lines[3].set_ydata(model.net_radiation) lines[4].set_ydata(model.heat_transport_convergence()) lines[5].set_ydata(np.squeeze(model.net_radiation)+model.heat_transport_convergence()) lines[6].set_ydata(model.diffusive_heat_transport()) lines[-1].set_text('Day {}'.format(int(model.time['days_elapsed']))) return lines # In[12]: # A model starting from isothermal initial conditions e = climlab.EBM_annual() e.Ts[:] = 15. # in degrees Celsius e.compute_diagnostics() # In[13]: # Plot initial data fig, axes, lines = initial_figure(e) # In[14]: ani = animation.FuncAnimation(fig, animate, frames=np.arange(1, 100), fargs=(e, lines)) # In[15]: HTML(ani.to_html5_video()) # ____________ # # # ## 6. Tuning the diffusivity # ____________ # We want to choose a value of $D$ that gives a reasonable approximation to observations: # # - $\Delta T \approx 45$ ºC between equator and pole # - $\mathcal{H}_{max} \approx 5.5$ PW (peak heat transport) # In[16]: ebm = climlab.EBM_annual(num_lat=40, A=210, B=2, a0=0.354, a2=0.25, D=1.) print(ebm) # In[17]: ebm.integrate_years(20.) # In[18]: ebm.Ts # In[19]: deltaT = np.max(ebm.Ts) - np.min(ebm.Ts) print(deltaT) # In[20]: #. The heat transport in PW ebm.heat_transport() # In[21]: # what's the peak value? np.max(ebm.heat_transport()) # ### Class exercise # # Repeat these calculations with some different values of $D$. We'll crowd-source the optimal value that best matches our observational targets. # In[ ]: # ### Results # # 12 students calculated $\Delta T$ and $\mathcal{H}_{max}$ for different values of the diffusivity parameter $D$. # # We collected the data on the whiteboard during class: # ![column sketch](http://www.atmos.albany.edu/facstaff/brose/classes/ENV415_Spring2018/images/classdata.jpg) # Here are the same data entered into a [Pandas data frame](http://pandas.pydata.org/pandas-docs/stable/10min.html). # In[22]: import pandas as pd classdata = pd.DataFrame({'$D$': [1., 0.5, 0.638, 0.55, 0.58, 0.6, 0.57, 0.63, 12.0, 0.423, 0.45, 0., 0.63], '$\Delta T$': [33., 52., 45.007, 49.46, 47.85, 46.8, 48.37, 45.85, 3.56, 57.686, 55.716, 126.124, 48.37], '$\mathcal{H}_{max}$': [6.6, 5.3, 5.8, 5.5, 5.6, 5.7, 5.59, 5.76, 8.60, 4.96, 5.095, 0., 5.76], }) classdata # Now we can do fun things like make a scatterplot of the data: # In[23]: fig, axes = plt.subplots(2,1) classdata.plot.scatter(x='$D$', y='$\Delta T$', ax=axes[0]) classdata.plot.scatter(x='$D$', y='$\mathcal{H}_{max}$', ax=axes[1]) # Evidently the temperature gradient $\Delta T$ decreases with $D$, while the heat transport increases with $D$. # ## A more systematic search # In[24]: Darray = np.arange(0., 2.05, 0.05) model_list = [] Tmean_list = [] deltaT_list = [] Hmax_list = [] for D in Darray: ebm = climlab.EBM_annual(A=210, B=2, a0=0.354, a2=0.25, D=D) ebm.integrate_years(20., verbose=False) Tmean = ebm.global_mean_temperature() deltaT = np.max(ebm.Ts) - np.min(ebm.Ts) energy_in = np.squeeze(ebm.ASR - ebm.OLR) Htrans = ebm.heat_transport() Hmax = np.max(Htrans) model_list.append(ebm) Tmean_list.append(Tmean) deltaT_list.append(deltaT) Hmax_list.append(Hmax) # In[34]: color1 = 'b' color2 = 'r' fig, ax1 = plt.subplots(figsize=(8,6)) ax1.plot(Darray, deltaT_list, color=color1) ax1.plot(Darray, Tmean_list, 'b--') ax1.set_xlabel('D (W m$^{-2}$ K$^{-1}$)', fontsize=14) ax1.set_xticks(np.arange(Darray[0], Darray[-1], 0.2)) ax1.set_ylabel('$\Delta T$ (equator to pole)', fontsize=14, color=color1) for tl in ax1.get_yticklabels(): tl.set_color(color1) ax2 = ax1.twinx() ax2.plot(Darray, Hmax_list, color=color2) ax2.set_ylabel('Maximum poleward heat transport (PW)', fontsize=14, color=color2) for tl in ax2.get_yticklabels(): tl.set_color(color2) ax1.set_title('Effect of diffusivity on temperature gradient and heat transport in the EBM', fontsize=16) # Add our crowd-sourced data to the figure classdata.plot.scatter(x='$D$', y='$\Delta T$', ax=ax1) classdata.plot.scatter(x='$D$', y='$\mathcal{H}_{max}$', ax=ax2) for ax in [ax1, ax2]: ax.set_xlim(0,2) ax1.plot([0.6, 0.6], [0, 140], 'k-') ax1.grid() # When $D=0$, every latitude is in radiative equilibrium and the heat transport is zero. As we have already seen, this gives an equator-to-pole temperature gradient much too high. # # When $D$ is **large**, the model is very efficient at moving heat poleward. The heat transport is large and the temperture gradient is weak. # # The real climate seems to lie in a sweet spot in between these limits. # # It looks like our fitting criteria are met reasonably well with $D=0.6$ W m$^{-2}$ K$^{-1}$ # Also, note that the **global mean temperature** (plotted in dashed blue) is completely insensitive to $D$. Why do you think this is so? # ____________ # # # ## 7. Summary: parameter values in the diffusive EBM # ____________ # Our model is defined by the following equation # # $$ C \frac{\partial T}{\partial t} = (1-\alpha) ~ Q - \left( A + B~T \right) + \frac{D}{\cos⁡\phi } \frac{\partial }{\partial \phi} \left( \cos⁡\phi ~ \frac{\partial T}{\partial \phi} \right) $$ # # with the albedo given by # # $$ \alpha(\phi) = \alpha_0 + \alpha_2 P_2(\sin\phi) $$ # We have chosen the following parameter values, which seems to give a reasonable fit to the observed **annual mean temperature and energy budget**: # # - $ A = 210 ~ \text{W m}^{-2}$ # - $ B = 2 ~ \text{W m}^{-2}~^\circ\text{C}^{-1} $ # - $ a_0 = 0.354$ # - $ a_2 = 0.25$ # - $ D = 0.6 ~ \text{W m}^{-2}~^\circ\text{C}^{-1} $ # In[ ]: