{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ENV / ATM 415: Climate Laboratory\n", "\n", "# Exploring the rate of climate change\n", "\n", "Tuesday April 11, 2016" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib notebook\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import climlab\n", "import netCDF4 as nc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So far in this class we have talked a lot about **Equilibrium Climate Sensitivity**: the surface warming that is necessary to bring the planetary energy budget back into balance (energy in = energy out) after a doubling of atmospheric CO2.\n", "\n", "Although this concept is very important, it is not the only important measure of climate change, and not the only question for which we need to apply climate models.\n", "\n", "Consider two basic facts about climate change in the real world:\n", "\n", "- There is no sudden, abrupt doubling of CO2. Instead, CO2 and other radiative forcing agents change gradually over time.\n", "- The timescale for adjustment to equilibrium is **very long** because of the heat capacity of the deep oceans.\n", "\n", "We will now extend our climate model to deal with both of these issues simultaneously." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Two versions of Radiative-Convective Equilibrium with different climate sensitivities\n", "\n", "We are going to use the `BandRCModel` but set it up with two slightly different sets of parameters." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Need the ozone data again for our Radiative-Convective model\n", "ozone_filename = 'ozone_1.9x2.5_L26_2000clim_c091112.nc'\n", "ozone = nc.Dataset(ozone_filename)\n", "lat = ozone.variables['lat'][:]\n", "lon = ozone.variables['lon'][:]\n", "lev = ozone.variables['lev'][:]\n", "O3_zon = np.mean( ozone.variables['O3'],axis=(0,3) )\n", "O3_global = np.sum( O3_zon * np.cos(np.deg2rad(lat)), axis=1 ) / np.sum( np.cos(np.deg2rad(lat) ) )" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "#steps_per_year = 20\n", "steps_per_year = 180\n", "\n", "# parameter set 1 -- store in a dictionary for easy re-use\n", "p1 = {'albedo_sfc': 0.22,\n", " 'adj_lapse_rate': 6.,\n", " 'timestep': climlab.constants.seconds_per_year/steps_per_year,\n", " 'qStrat': 5E-6,\n", " 'relative_humidity': 0.77}\n", "# parameter set 2\n", "p2 = {'albedo_sfc': 0.2025,\n", " 'adj_lapse_rate': 7.,\n", " 'timestep': climlab.constants.seconds_per_year/steps_per_year,\n", " 'qStrat': 2E-7,\n", " 'relative_humidity': 0.6}\n", "# Make a list of the two parameter sets\n", "param = [p1, p2]\n", "# And a list of two corresponding Radiative-Convective models!\n", "slab = []\n", "\n", "for p in param:\n", " model = climlab.BandRCModel(lev=lev, **p)\n", " model.absorber_vmr['O3'] = O3_global\n", " slab.append(model)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Run both models out to equilibrium and check their surface temperatures:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Total elapsed time is 4.0 years.\n", "Total elapsed time is 5.0 years.\n", "The equilibrium surface temperatures are:\n", "Model 0: 287.94 K\n", "Model 1: 287.93 K\n" ] } ], "source": [ "for model in slab:\n", " model.integrate_converge()\n", " \n", "print 'The equilibrium surface temperatures are:'\n", "print 'Model 0: %0.2f K' %slab[0].Ts\n", "print 'Model 1: %0.2f K' %slab[1].Ts" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ok so our two models (by construction) start out with nearly identical temperatures.\n", "\n", "Now we double CO2 and calculate the Equilibrium Climate Sensitivity:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Total elapsed time is 9.0 years.\n", "Total elapsed time is 9.0 years.\n", "The equilibrium climate sensitivities are:\n", "Model 0: 3.74 K\n", "Model 1: 2.98 K\n" ] } ], "source": [ "# We will make copies of each model and double CO2 in the copy\n", "slab_2x = []\n", "for model in slab:\n", " model_2x = climlab.process_like(model)\n", " model_2x.absorber_vmr['CO2'] *= 2.\n", " model_2x.integrate_converge()\n", " slab_2x.append(model_2x)\n", "\n", "# Climate sensitivity\n", "DeltaT = []\n", "for n in range(len(slab)):\n", " DeltaT.append(slab_2x[n].Ts - slab[n].Ts)\n", "print 'The equilibrium climate sensitivities are:'\n", "print 'Model 0: %0.2f K' %DeltaT[0]\n", "print 'Model 1: %0.2f K' %DeltaT[1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So Model 0 is **more sensitive** than Model 1. It has a larger system gain, or a more positive overall climate feedback. \n", "\n", "This is actually due to differences in how we have parameterized the water vapor feedback in the two models. We could look at this more carefully if we wished." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Time to reach equilibrium\n", "\n", "These models reached their new equilibria in just a few years. Why is that? Because they have very little heat capacity:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "array([ 0., 1.])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "slab[0].depth_bounds" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The \"ocean\" in these models is just a \"slab\" of water 1 meter deep.\n", "\n", "That's all we need to calculate the equilibrium temperatures, but it tells us nothing about the timescales for climate change in the real world.\n", "\n", "For this, we need a deep ocean that can **exchange heat with the surface**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Transient warming scenarios in column models with ocean heat uptake" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are now going to build two new models. The atmosphere (radiative-convective model) will be identical to the two \"slab\" models we just used. But these will be coupled to a **column of ocean water** 2000 m deep!\n", "\n", "We will **parameterize the ocean heat uptake** as a diffusive mixing process. Much like when we discussed the diffusive parameterization for atmospheric heat transport -- we are assuming that ocean dynamics result in a vertical mixing of heat from warm to cold temperatures.\n", "\n", "The following code will set this up for us.\n", "\n", "We will make one more assumption, just for the sake of illustration:\n", "\n", "*The more sensitive model (Model 0) is also more efficent at taking up heat into the deep ocean*" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "climlab Process of type . \n", "State variables and domain shapes: \n", " Tatm: (26,) \n", " Tocean: (20,) \n", " Ts: (1,) \n", "The subprocess tree: \n", "top: \n", " LW: \n", " H2O: \n", " convective adjustment: \n", " OHU: \n", " SW: \n", " insolation: \n", "\n" ] } ], "source": [ "# Create the domains\n", "ocean_bounds = np.arange(0., 2010., 100.)\n", "depthax = climlab.Axis(axis_type='depth', bounds=ocean_bounds)\n", "levax = climlab.Axis(axis_type='lev', points=lev)\n", "atm = climlab.domain.Atmosphere(axes=levax)\n", "ocean = climlab.domain.Ocean(axes=depthax)\n", "\n", "# Model 0 has a higher ocean heat diffusion coefficient -- \n", "# a more efficent deep ocean heat sink\n", "ocean_diff = [5.E-4, 3.5E-4]\n", "\n", "# List of deep ocean models\n", "deep = []\n", "for n in range(len(param)):\n", " # Create the state variables\n", " Tinitial_ocean = slab[n].Ts * np.ones(ocean.shape)\n", " Tocean = climlab.Field(Tinitial_ocean.copy(), domain=ocean)\n", " Tatm = climlab.Field(slab[n].Tatm.copy(), domain=atm)\n", "\n", " # By declaring Ts to be a numpy view of the first element of the array Tocean\n", " # Ts becomes effectively a dynamic reference to that element and is properly updated\n", " Ts = Tocean[0:1]\n", " atm_state = {'Tatm': Tatm, 'Ts': Ts}\n", " \n", " model = climlab.BandRCModel(state=atm_state, **param[n])\n", " model.set_state('Tocean', Tocean)\n", " diff = climlab.dynamics.diffusion.Diffusion(state={'Tocean': model.Tocean}, \n", " K=ocean_diff[n], diffusion_axis='depth', **param[n])\n", " model.add_subprocess('OHU', diff)\n", " model.absorber_vmr['O3'] = O3_global\n", " deep.append(model)\n", "\n", "print deep[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now consider the CO2 increase. In the real world, CO2 has been increasing every year since the beginning of industrialization. Future CO2 concentrations depend on collective choices made by human societies about how much fossil fuel to extract and burn.\n", "\n", "We will set up a simple scenario. Suppose that CO2 increases by 1% of its existing concentration every year **until it reaches 2x its initial concentration**. This takes about 70 years.\n", "\n", "After 70 years, we assume that all anthropogenic emissions, and CO2 concentration is **stabilized** at the 2x level.\n", "\n", "What happens to the surface temperature?\n", "\n", "How do the histories of surface and deep ocean temperature compare in our two models?\n", "\n", "We are going to simulation **400 years of transient global warming** in the two models." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support.' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " this.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width);\n", " canvas.attr('height', height);\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
')\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('