Skip to content

Instantly share code, notes, and snippets.

@dionhaefner
Last active June 17, 2021 14:20
Show Gist options
  • Save dionhaefner/c6486a0d84133c28222eeb49d6e728b6 to your computer and use it in GitHub Desktop.
Save dionhaefner/c6486a0d84133c28222eeb49d6e728b6 to your computer and use it in GitHub Desktop.
Ocean modelling the Veros: The Sverdrup relation
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Ocean Modelling with Veros\n",
"\n",
"### 🌊 The Sverdrup Relation 🌊 \n",
"\n",
"In this exercise we investigate the [Sverdrup relation](https://en.wikipedia.org/wiki/Sverdrup_balance) (also known as Sverdrup balance). The Sverdrup relation is perhaps the most important balance in the ocean, because it connects *transport* (of major ocean currents) to *wind stress* (an atmospheric parameter). **It is proof that winds are the dominant driving factor for the large-scale ocean circulation.** \n",
"\n",
"As a formula, it reads:\n",
"\n",
"\\begin{equation}\n",
"V = \\frac{1}{\\rho_0 \\beta} \\nabla \\times \\tau\n",
"\\end{equation}\n",
"\n",
"Here, $V$ is the meridional (north-south) transport, $\\rho_0 \\approx 1024 ~ \\text{kg m$^{-3}$}$ the density of sea water, $\\beta$ the Rossby parameter, and $\\tau$ the surface wind stress. \n",
"\n",
"Specifically, we study the connection of the Sverdrup relation to the barotropic streamfunction $\\Psi$, for which\n",
"\n",
"\\begin{equation}\n",
"\\frac{\\partial}{\\partial y} \\Psi = -U, \\quad \\frac{\\partial}{\\partial x} \\Psi = V\n",
"\\end{equation}\n",
"\n",
"$U$ and $V$ are defined as\n",
"\n",
"\\begin{equation}\n",
"U(x, y, t) = \\int_{-D}^0 u(x, y, z, t) ~ \\mathrm{d}z, \\quad\n",
"V(x, y, t) = \\int_{-D}^0 v(x, y, z, t) ~ \\mathrm{d}z\n",
"\\end{equation}\n",
"\n",
"where $u$ is the velocity in zonal (east-west) direction, and $v$ the velocity in meridional (north-south) direction.\n",
"\n",
"$\\Psi$ is a handy tool to visualize the ocean circulation in 2 dimensions (without having to deal with $u$ and $v$ separately), because flow will always follow its contours."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 0: Using Jupyter notebooks"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This environment is called a [Jupyter notebook](https://jupyter.org/). Jupyter notebooks typically consist of text cells (like this one) and code cells (like the following one):"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(\"hello jupyter!\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can execute Python code in a code cell by pressing SHIFT + ENTER. Jupyter will execute the code and then show you the result.\n",
"\n",
"For the rest of this exercise we will assume that you are somewhat familiar with the scientific Python ecosystem - specifically, NumPy and matplotlib."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 1: Run Veros\n",
"\n",
"[Veros](https://veros.readthedocs.io) is a pure Python ocean model that integrates the equations of motion forward in time and writes the result to netCDF4 files.\n",
"\n",
"To tell Veros what to do, you need to write a so-called *setup*. [There are many predefined setups in Veros](https://veros.readthedocs.io/en/stable/reference/setup-gallery.html), so you will not have to write one from scratch.\n",
"\n",
"The following cell contains one of the example setups, `acc_basic`. Usually, you would use something like\n",
"\n",
"```bash\n",
"$ veros copy-setup acc_basic\n",
"```\n",
"\n",
"from the command line to get a copy of this template, but here we have already included it in the next cell.\n",
"\n",
"*Don't be intimidated by the following code, you will only have to interact with very little of it.*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%writefile acc_basic.py\n",
"\n",
"from veros import VerosSetup, veros_routine\n",
"from veros.variables import allocate, Variable\n",
"from veros.distributed import global_min, global_max\n",
"from veros.core.operators import numpy as npx, update, at\n",
"\n",
"\n",
"class ACCBasicSetup(VerosSetup):\n",
" \"\"\"A model using spherical coordinates with a partially closed domain representing the Atlantic and ACC.\n",
"\n",
" Wind forcing over the channel part and buoyancy relaxation drive a large-scale meridional overturning circulation.\n",
"\n",
" This setup demonstrates:\n",
" - setting up an idealized geometry\n",
" - updating surface forcings\n",
" - constant horizontal and vertical mixing (switched off IDEMIX and GM_EKE)\n",
" - basic usage of diagnostics\n",
"\n",
" :doc:`Adapted from ACC channel model </reference/setups/acc>`.\n",
" \"\"\"\n",
"\n",
" @veros_routine\n",
" def set_parameter(self, state):\n",
" settings = state.settings\n",
" settings.identifier = \"UNNAMED\"\n",
"\n",
" settings.nx, settings.ny, settings.nz = 30, 42, 15\n",
" settings.dt_mom = 4800\n",
" settings.dt_tracer = 86400 / 2\n",
" settings.runlen = 86400 * 360\n",
"\n",
" settings.x_origin = 0.0\n",
" settings.y_origin = -40.0\n",
"\n",
" settings.coord_degree = True\n",
" settings.enable_cyclic_x = True\n",
"\n",
" settings.enable_neutral_diffusion = False\n",
" settings.K_iso_0 = 1000.0\n",
" settings.K_iso_steep = 500.0\n",
" settings.iso_dslope = 0.005\n",
" settings.iso_slopec = 0.01\n",
" settings.enable_skew_diffusion = True\n",
"\n",
" settings.enable_hor_friction = True\n",
" settings.A_h = (2 * settings.degtom) ** 3 * 2e-11\n",
" settings.enable_hor_friction_cos_scaling = True\n",
" settings.hor_friction_cosPower = 1\n",
"\n",
" settings.enable_bottom_friction = True\n",
" settings.r_bot = 1e-5\n",
"\n",
" settings.enable_implicit_vert_friction = True\n",
"\n",
" settings.enable_tke = True\n",
" settings.c_k = 0.1\n",
" settings.c_eps = 0.7\n",
" settings.alpha_tke = 30.0\n",
" settings.mxl_min = 1e-8\n",
" settings.tke_mxl_choice = 2\n",
" settings.kappaM_min = 2e-4\n",
" settings.kappaH_min = 2e-5\n",
" settings.enable_Prandtl_tke = False\n",
" settings.enable_kappaH_profile = True\n",
"\n",
" settings.K_gm_0 = 1000.0\n",
" settings.enable_eke = False\n",
" settings.enable_idemix = False\n",
"\n",
" settings.eq_of_state_type = 3\n",
"\n",
" var_meta = state.var_meta\n",
" var_meta.update(\n",
" t_star=Variable(\"t_star\", (\"yt\",), \"deg C\", \"Reference surface temperature\"),\n",
" t_rest=Variable(\"t_rest\", (\"xt\", \"yt\"), \"1/s\", \"Surface temperature restoring time scale\"),\n",
" )\n",
"\n",
" @veros_routine\n",
" def set_grid(self, state):\n",
" vs = state.variables\n",
"\n",
" ddz = npx.array(\n",
" [50.0, 70.0, 100.0, 140.0, 190.0, 240.0, 290.0, 340.0, 390.0, 440.0, 490.0, 540.0, 590.0, 640.0, 690.0]\n",
" )\n",
" vs.dxt = update(vs.dxt, at[...], 2.0)\n",
" vs.dyt = update(vs.dyt, at[...], 2.0)\n",
" vs.dzt = ddz[::-1] / 2.5\n",
"\n",
" @veros_routine\n",
" def set_coriolis(self, state):\n",
" vs = state.variables\n",
" settings = state.settings\n",
" vs.coriolis_t = update(\n",
" vs.coriolis_t, at[:, :], 2 * settings.omega * npx.sin(vs.yt[None, :] / 180.0 * settings.pi)\n",
" )\n",
"\n",
" @veros_routine\n",
" def set_topography(self, state):\n",
" vs = state.variables\n",
" x, y = npx.meshgrid(vs.xt, vs.yt, indexing=\"ij\")\n",
" vs.kbot = npx.logical_or(x > 1.0, y < -20).astype(\"int\")\n",
"\n",
" @veros_routine\n",
" def set_initial_conditions(self, state):\n",
" vs = state.variables\n",
" settings = state.settings\n",
"\n",
" # initial conditions\n",
" vs.temp = update(vs.temp, at[...], ((1 - vs.zt[None, None, :] / vs.zw[0]) * 15 * vs.maskT)[..., None])\n",
" vs.salt = update(vs.salt, at[...], 35.0 * vs.maskT[..., None])\n",
"\n",
" # wind stress forcing\n",
" yt_min = global_min(vs.yt.min())\n",
" yu_min = global_min(vs.yu.min())\n",
" yt_max = global_max(vs.yt.max())\n",
" yu_max = global_max(vs.yu.max())\n",
"\n",
" taux = allocate(state.dimensions, (\"yt\",))\n",
" taux = npx.where(vs.yt < -20, 0.1 * npx.sin(settings.pi * (vs.yu - yu_min) / (-20.0 - yt_min)), taux)\n",
" taux = npx.where(vs.yt > 10, 0.1 * (1 - npx.cos(2 * settings.pi * (vs.yu - 10.0) / (yu_max - 10.0))), taux)\n",
" vs.surface_taux = taux * vs.maskU[:, :, -1]\n",
"\n",
" # surface heatflux forcing\n",
" vs.t_star = allocate(state.dimensions, (\"yt\",), fill=15)\n",
" vs.t_star = npx.where(vs.yt < -20, 15 * (vs.yt - yt_min) / (-20 - yt_min), vs.t_star)\n",
" vs.t_star = npx.where(vs.yt > 20, 15 * (1 - (vs.yt - 20) / (yt_max - 20)), vs.t_star)\n",
" vs.t_rest = vs.dzt[npx.newaxis, -1] / (30.0 * 86400.0) * vs.maskT[:, :, -1]\n",
"\n",
" if settings.enable_tke:\n",
" vs.forc_tke_surface = update(\n",
" vs.forc_tke_surface,\n",
" at[2:-2, 2:-2],\n",
" npx.sqrt(\n",
" (0.5 * (vs.surface_taux[2:-2, 2:-2] + vs.surface_taux[1:-3, 2:-2]) / settings.rho_0) ** 2\n",
" + (0.5 * (vs.surface_tauy[2:-2, 2:-2] + vs.surface_tauy[2:-2, 1:-3]) / settings.rho_0) ** 2\n",
" )\n",
" ** (1.5),\n",
" )\n",
"\n",
" @veros_routine\n",
" def set_forcing(self, state):\n",
" vs = state.variables\n",
" vs.forc_temp_surface = vs.t_rest * (vs.t_star - vs.temp[:, :, -1, vs.tau])\n",
"\n",
" @veros_routine\n",
" def set_diagnostics(self, state):\n",
" settings = state.settings\n",
" diagnostics = state.diagnostics\n",
"\n",
" diagnostics[\"averages\"].output_variables = (\n",
" \"salt\",\n",
" \"temp\",\n",
" \"u\",\n",
" \"v\",\n",
" \"w\",\n",
" \"psi\",\n",
" \"surface_taux\",\n",
" \"surface_tauy\",\n",
" )\n",
" diagnostics[\"averages\"].output_frequency = 360 * 86400. / 12 \n",
" diagnostics[\"averages\"].sampling_frequency = settings.dt_tracer * 10\n",
"\n",
" @veros_routine\n",
" def after_timestep(self, state):\n",
" pass\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Can you figure out what the different parts of the setup do?\n",
"\n",
"**Now, make the following changes to the file above:**\n",
"\n",
"- Change the run length to 50 years\n",
"- Change the identifier to `acc_sverdrup`\n",
"- Change the output frequency of the average diagnostic to 1 year\n",
"\n",
"**After you made the changes and executed the cell above, can you figure out how to run the setup?**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!veros run --help"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Running the model takes about 40 minutes, so now would be a good time to get a break.** \n",
"\n",
"If you can't get the model to work, or don't want to wait, there is some prepared output in the file `acc_sverdrup_igiveup.averages.nc`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 2: Load output and inspect $\\Psi$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With the model output ready, we can now begin to work with it. \n",
"\n",
"First, we need to import the packages that we will use in the exercise. You are likely already familiar with [NumPy](https://numpy.org/doc/stable/) and [matplotlib](https://matplotlib.org/stable/contents.html), which are Python packages that deal with mathematical operations and plotting, respectively. \n",
"\n",
"You might have not have used [xarray](http://xarray.pydata.org/en/stable/) yet. It is one of the most commonly used Python packages when dealing with multi-dimensional datasets. In climate modeling, we often have to deal with very complicated output files, and xarray makes dealing with them a lot easier."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import xarray as xr\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To open the generated output, you simply call `xr.open_dataset()`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"ds = xr.open_dataset('acc_sverdrup.averages.nc', engine='h5netcdf')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the cell below, you can inspect the model output. The dataset is divided into two compartments, **coordinates** and **variables**. \n",
"\n",
"The coordinates describe the grid *dimensions*: width, length, depth and time.\n",
"\n",
"The variables show the physical *values* assigned to each grid cell, such as temperature, density, velocity, etc."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ds"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### A quick guide to xarray\n",
"*(feel free to skip if you are already familiar with xarray or want to figure it out on your own)*\n",
"\n",
"- `ds['var_name']` or `ds.var_name` allow you to inspect and work with specific variables from the set; 'var_name' could be for example 'temp' for temperature, 'u' for zonal speed, or 'salt' for salinity. \n",
"\n",
"- You can plot the output using `ds['var_name'].plot()`. A 1D array will be shown as a line plot, a 2D array as a heat map, and any higher dimensions as a histogram.\n",
"\n",
"- You can select coordinates using `ds['var_name'].isel(dim_name=n)`, where *'dim_name'* is the dimension you want to select and *n* is the index. For example, if you'd like to see the 2D plot of sea surface temperature at the last timestep, you should use `ds['temp'].isel(Time=-1, zt=-1).plot()`\n",
"\n",
"- It is also possible to perform array operations on the output variables, such as finding maxima, minima, sums or arithmetic means over specific dimensions, such as:\n",
" - `ds['var_name'].mean(dim=\"Time\")`\n",
" - `ds['var_name'].sum(dim=(\"xu, yu\"))`\n",
" - `ds['var_name'].max(dim=(\"xu\", \"zw\"))`\n",
" \n",
"- xarray stores metadata about the variables alongside its values. If you wish to convert a specific variable to a plain NumPy array, use: `ds['var_name'].values`\n",
" \n",
"- Most commands can be chained, for example: `ds['psi'].isel(xu=15).mean(dim=\"yu\").plot()` shows the meridional mean of the barotropic stream function at the zonal position xu\\[15\\]=30°E for the entire runtime of the simulation. The plot will show the $\\Psi$ values in units of \\[m$^3$/s\\] on the y-axis and the time in years on the x-axis."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Try out some of the commands above (or make up your own!) in this cell.\n",
"ds['psi']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now use your knowledge of xarray and inspect $\\Psi$ to see if the model run has reached a steady state.\n",
"\n",
"(In the steady state, the barotropic stream function is approximately constant in time: $\\partial \\Psi / \\partial t = 0$.)\n",
"\n",
"**Plot the 1D curve of the maximum value of $\\Psi$ over time and check whether the model is fully spun up, i.e., whether it is approximately constant.**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you are satisfied with the plot, go ahead and re-define `ds` as the last model year of the original output set.\n",
"\n",
"*Note: In a Jupyter notebook, you can run cells in arbitrary order, which has its advantages, but also weaknesses. You have to keep track of the meaning of your variable when you use the same name in multiple spots in the code. If you wish to work with the full dataset again, you have to re-run the original cell with `xr.open_dataset()`.*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ds = ds.isel(Time=-1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 3: Compute wind stress curl"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With all the technical model details in order, we can now get back to physics.\n",
"\n",
"Before you can compute $V_{Sv}$, you must define some constants and compute the curl of the wind stress $\\tau$.\n",
"\n",
"In the cell below, the Rossby parameter $\\beta$ is defined. When you attempt to run it, you will get an error, as some constants are missing. \n",
"\n",
"**Define $\\rho_0$, $\\Omega$, and the Earth's radius, $a$.**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"beta = 2 * omega * np.cos(np.radians(ds['yu'].values)) / earth_radius"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now you need to extract some variables from the input dataset. Namely, the output barotropic stream function (to compare with the computed $\\Psi_{Sv}$), and the grid coordinates $x$ and $y$.\n",
"\n",
"**Extract and define variables `psi`, `yt` and `xt`** as NumPy arrays from `ds`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert isinstance(psi, np.ndarray)\n",
"assert isinstance(xt, np.ndarray)\n",
"assert isinstance(yt, np.ndarray)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lastly, you need the values for the grid spacings $dx$ and $dy$ in order to compute the gradients of windstress components and the integral $\\int V ~ \\mathrm{d}x$. The grid spacing $dx$ changes with latitude due to the Earth's sphericity. \n",
"\n",
"What are the units of `xt` and `yt`?\n",
"\n",
"**Correct the definitions below to express $dx$ and $dy$ in meters.**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"dx = (xt[1] - xt[0]) * np.cos(np.radians(yt))\n",
"dy = (yt[1] - yt[0])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The following two functions can integrate and compute the gradient of an array. \n",
"\n",
"The function `integrate` takes 3 arguments: the array you wish to integrate over, the direction (either $x$ or $y$), and a boolean flag `reverse` which, when set to `True`, will flip the limits of the integral. For example, if we have:\n",
"\n",
"\\begin{equation}\n",
"\\int^{x_1}_{x_0} f(x,y) ~ \\mathrm{d}x,\n",
"\\end{equation}\n",
"\n",
"setting the `reverse=True` will result in the function output $\\int_{x_1}^{x_0} f(x,y) ~ \\mathrm{d}x$.\n",
"\n",
"The function `gradient` takes 2 arguments: the array you wish to differentiate over, and the direction (either $x$ or $y$). Let the input function be $g(x,y)$ and the direction $x$, the function output is then:\n",
"\n",
"\\begin{equation}\n",
"\\frac{\\partial}{\\partial x} g(x,y)\n",
"\\end{equation}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def integrate(q, direction, reverse=False):\n",
" \"\"\"Integrate a 2D variable on a C-grid\"\"\"\n",
" if direction == 'x':\n",
" dxi = dx[:, np.newaxis]\n",
" axis = 1\n",
" \n",
" if reverse:\n",
" q = q[:, ::-1]\n",
" \n",
" elif direction == 'y':\n",
" dxi = dy\n",
" axis = 0\n",
" \n",
" if reverse:\n",
" q = q[::-1]\n",
" \n",
" else:\n",
" raise ValueError('direction must be \"x\" or \"y\"')\n",
" \n",
" out = np.nancumsum(q * dxi, axis=axis)\n",
" out[np.isnan(q)] = np.nan\n",
" \n",
" if reverse:\n",
" if direction == 'x':\n",
" out = -out[:, ::-1]\n",
" else:\n",
" out = -out[::-1, :]\n",
" \n",
" return out\n",
"\n",
"\n",
"def gradient(q, direction):\n",
" \"\"\"Compute the gradient of a 2D variable on a C-grid\"\"\"\n",
" if direction == 'x':\n",
" arr_pad = np.pad(q, ([0], [1]), 'wrap')\n",
" return (arr_pad[:, 2:] - arr_pad[:, 1:-1]) / dx[:, np.newaxis]\n",
"\n",
" elif direction == 'y':\n",
" arr_pad = np.pad(q, ([1], [0]), 'constant', constant_values=np.nan)\n",
" return (arr_pad[2:, :] - arr_pad[1:-1, :]) / dy\n",
"\n",
" raise ValueError('direction must be \"x\" or \"y\"')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Now compute $\\nabla \\times \\tau$.**\n",
"\n",
"*Hint: The definition of the curl in 2D is $\\nabla \\times f(x,y) = \\frac{\\partial}{\\partial x} f_y(x,y) - \\frac{\\partial}{\\partial y} f_x(x,y)$*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Check your solution by ploting $\\nabla \\times \\tau$, using `imshow` or another 2D plotting command.**\n",
"\n",
"Does the calculated $\\nabla \\times \\tau$ make sense?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 4: Compute $\\Psi_\\text{Sv}$ and compare"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Well done so far, you are almost there! \n",
"\n",
"The task is now to compare the output barotropic streamfunction `ds['psi']` to $\\Psi_{Sv}$ calculated from the Sverdrup relation:\n",
"\n",
"\\begin{equation}\n",
"\\Psi_{Sv} = \\int_{east}^{west}V_{Sv} ~ \\mathrm{d}x\n",
"\\end{equation}\n",
"\n",
"*Note that the integral goes from east to west, as we define $\\Psi(x_\\text{east}) = 0$.*\n",
"\n",
"**Use the definition of $V_{Sv}$ to compute it from the defined constants and the calculated curl of $\\tau$. Then, compute the barotropic stream function $\\Psi_{Sv}$ and compare it to the $\\Psi$ from the model output by examining their respective plots.**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 5: Conclusions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Congratulations, you're almost done! Let's take a moment to reflect on what we have learned doing all this technical work. To complete the exercise, answer the following:\n",
"\n",
"**1. How does the diagnostic streamfunction differ from the one calculated from the Sverdrup balance?**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**2. How do these two solutions compare in the open ocean vs. at the boundaries?**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**3. How well does this idealized model reflect real winds and ocean currents?**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"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.9.2"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file has been truncated, but you can view the full file.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment