{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Lorenz's (1963) model: forward modelling\n",
"\n",
"$$\n",
"\\newcommand{\\myd}{\\mathrm{d}}\n",
"\\newcommand{\\statev}{\\mathbf{X}}\n",
"\\newcommand{\\lrayleigh}{{\\mathrm{r}}}\n",
"\\newcommand{\\rayleigh}{{\\mathrm{Ra}}}\n",
"\\newcommand{\\pr}{\\mathrm{Pr}}\n",
"\\newcommand{\\adj}{T}\n",
"\\newcommand{\\tstep}{\\Delta t}\n",
"$$\n",
"\n",
"## Introduction\n",
"\n",
"A good understanding of the forward model is mandatory before any practice of data assimilation. The goal of this first notebook is to get familiar with the numerical model we are dealing with today. The model we are interested in is [the famous model proposed by Edward Lorenz in 1963](https://doi.org/10.1175/1520-0469(1963)020%3C0130:DNF%3E2.0.CO;2). This model is the canonical example of a set of coupled deterministic, nonlinear, ordinary differential equations (ode) able to exhibit chaotic behaviour. It is a simplified, 3-variable representation of atmospheric cellular convection, based on the earlier work of Saltzman (1962). \n",
"\n",
"Its time evolution is governed by the following set of nondimensional equations\n",
" \n",
"$$\n",
"\\begin{eqnarray}\n",
"\\frac{\\myd X}{\\myd t} &=& -\\pr (X -Y), \\label{eq:lorx}\\\\ \n",
"\\frac{\\myd Y}{\\myd t} &=& -XZ +\\lrayleigh X -Y, \\label{eq:lory}\\\\\n",
"\\frac{\\myd Z}{\\myd t} &=& XY - bZ,\\label{eq:lorz} \n",
"\\end{eqnarray}\n",
"$$\n",
"\n",
"which has to be supplemented with a (column) vector of initial conditions\n",
"\n",
"$$\n",
"\\statev_0=\\left[X(t=0),Y(t=0),Z(t=0) \\right]^{\\adj}.\n",
"$$\n",
"\n",
"The variable $X$ is connected with the streamfunction describing atmospheric flow, while both variables $Y$ and $Z$ are connected with the temperature deviation responsible for convection. So the state of atmospheric quiescence (no convection) is described by $\\statev=[0,0,0]^\\adj$.\n",
" \n",
"Three nondimensional numbers define the parameter space:\n",
" + $\\pr$, the Prandtl number, which is the ratio of kinematic viscosity to thermal diffusivity\n",
" + $\\lrayleigh$, which is the ratio of the Rayleigh number $\\rayleigh$ to the critical value of the Rayleigh number $\\rayleigh_c$ (in convection parlance, $\\lrayleigh$ tells you how many times supercritical the system is)\n",
" + $b$, which is a geometrical factor\n",
" \n",
"(Note that time has been non-dimensionalized using the thermal diffusion timescale as the timescale of reference.)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Original integration by Lorenz\n",
" \n",
"We first stick to Lorenz's original choice of parameters and initial condition, and pick\n",
"\n",
"$$\n",
" \\begin{eqnarray}\n",
" \\pr&=&10, \\label{eq:lorpar1}\\\\\n",
" \\lrayleigh&=&28, \\label{eq:lorpar2}\\\\\n",
" b&=&8/3. \\label{eq:lorpar3}\n",
" \\end{eqnarray}\n",
"$$ \n",
"\n",
"The equations are integrated numerically using a standard explicit integration scheme, known as the explicit Runge-Kutta scheme of order 4 (aka RK4), using the ode solver that comes with scipy (see the `forwardModel.py` python script) . This means in practice that the time axis is discretized, being divided into segments of width $\\tstep$; 4th order accuracy means that the error characterizing this numerical approximation is proportional to $\\tstep ^4$. \n",
"\n",
"The piece of code below allows you to run the model and visualize the time evolution of $X$, $Y$ and $Z$. \n",
"\n",
"You can increase the value of the total integration time $T$ to see the long-term evolution of the solution. \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"## Uncomment this line to make it interactive in JupyterLab!\n",
"# %matplotlib widget"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"import numpy as np\n",
"import forwardModel as fw\n",
"import matplotlib.pyplot as plt\n",
"\n",
"\n",
"# Getting familiar with Lorenz' 1963 model\n",
"# Control parameters \n",
"rayleigh = 28 # Value of the Rayleigh number ratio \n",
"prandtl = 10.\n",
"b = 8./3.\n",
"\n",
"#integration time parameters\n",
"dt = 1.e-3 # This is the time step size\n",
"T = 30. # Total integration time (in nondimensional time)\n",
"n_steps = int( np.ceil( T / dt) )\n",
"time = np.linspace(0., T, n_steps + 1, endpoint=True) # array of discrete times\n",
"\n",
"#initial condition\n",
"x0 = np.array( [0., 1., 0.], dtype=float )\n",
"\n",
"#numerical integration given initial conditions and control parameters\n",
"x = fw.forwardModel_r( x0, time, rayleigh, prandtl, b) \n",
"\n",
"#plot result\n",
"fig, ax = plt.subplots(nrows=3, ncols=1, sharex=True)\n",
"for k, comp in enumerate ([\"X\",\"Y\",\"Z\"]):\n",
" ax[k].plot(time, x[k,:], label='L63 - '+comp)\n",
" ax[k].set_ylabel(comp)\n",
" ax[k].legend()\n",
"ax[-1].set_xlabel('Time')\n",
"ax[-1].set_xlim(time[0],time[-1])\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The Butterfly effect: sensitivity to a slight change in parameters or initial condition\n",
"\n",
"Any change in the initial condition or control parameter will result in a solution that will diverge from the reference solution - this is the Butterfly effect, a hallmark of chaotic dynamical systems that makes long-term prediction of the dynamics of such systems impossible. \n",
"\n",
"Below you can see how a change in the initial condition, by an amount $\\epsilon_{ic}$ affects the solution. \n",
"In the code, the change affects the $Y$ variable; feel free to apply it to the other variables, and also to vary its magnitude. \n",
"\n",
"Likewise, let $\\epsilon_{par}$ be the amount by which the parameter $\\lrayleigh$ is changed. You can also visualize how this impacts the solution and play with its magnitude as well. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#Sensitivity to initial condition\n",
"#Change initial condition and compare solutions\n",
"epsilon_ic = 2.e-1 # feel free to change this value\n",
"x0 = np.array( [0., 1.+epsilon_ic, 0.], dtype=float )\n",
"x2 = fw.forwardModel_r( x0, time, rayleigh, prandtl, b) \n",
"\n",
"#Sensitivity to control parameters\n",
"epsilon_par = .1\n",
"x0 = np.array( [0., 1, 0.], dtype=float )\n",
"x3 = fw.forwardModel_r( x0, time, rayleigh+epsilon_par, prandtl, b) \n",
"\n",
"#Plot all three trajectories\n",
"fig, ax = plt.subplots(nrows=3, ncols=1, sharex=True)\n",
"for k, comp in enumerate ([\"X\",\"Y\",\"Z\"]):\n",
" ax[k].plot(time, x[k,:], label='L63')\n",
" ax[k].plot(time, x2[k,:], label=\"pert. IC,$\\epsilon_{IC}$=\"+str(epsilon_ic))\n",
" ax[k].plot(time, x3[k,:], label=\"pert. $r$, $\\epsilon_{Ra}$=\"+str(epsilon_par)) # effect of changing the value of one control parameter, Ra\n",
" ax[k].set_ylabel( comp )\n",
" ax[k].legend( loc='best')\n",
"ax[-1].set_xlabel('Time')\n",
"ax[-1].set_xlim(time[0],time[-1])\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## For later for those interested\n",
"\n",
"Plot the time evolution of the difference between the original solution by Lorenz and the one obtained with a perturbed initial condition using a logarithmic scale on the $y$-axis. Describe this evolution. "
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python 3",
"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.8.8"
}
},
"nbformat": 4,
"nbformat_minor": 4
}