{ "cells": [ { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/neuromatch/climate-course-content/blob/main/tutorials/W1D2_Ocean-AtmosphereReanalysis/W1D2_Tutorial4.ipynb)   \"Open" ] }, { "cell_type": "markdown", "metadata": { "execution": {}, "tags": [] }, "source": [ "# Tutorial 4: Oceanic Wind-Driven Circulation\n", "\n", "**Week 1, Day 2: Ocean and Atmospheric Reanalysis**\n", "\n", "**Content creators:** Abigail Bodner\n", "\n", "**Content reviewers:** Katrina Dobson, Danika Gupta, Maria Gonzalez, Will Gregory, Nahid Hasan, Paul Heubel, Sherry Mi, Beatriz Cosenza Muralles, Jenna Pearson, Chi Zhang, Ohad Zivan\n", "\n", "**Content editors:** Paul Heubel, Brodie Pearson, Jenna Pearson, Chi Zhang, Ohad Zivan\n", "\n", "**Production editors:** Wesley Banfield, Paul Heubel, Jenna Pearson, Konstantine Tsafatinos, Chi Zhang, Ohad Zivan\n", "\n", "**Our 2024 Sponsors:** NFDI4Earth, CMIP\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Tutorial Objectives\n", "\n", "*Estimated timing of tutorial: 20 mins*\n", "\n", "The ocean's motion is driven by radiation from the sun, winds, and various sources & sinks of fresh water (precipitation, rivers, melting and freezing ice). In the previous tutorial, you quantified the surface winds around the world. The surface winds drag on the surface of the ocean which results in water mass transport, known as Ekman transport.\n", "\n", "In this tutorial you will use the [ECCO (Estimating the Circulation and Climate of the Ocean)](https://www.ecco-group.org) reanalysis data, to visualize the ocean's surface currents and to compare these currents against local atmospheric conditions.\n", "\n", "At the end of this tutorial, you will be able to\n", "\n", "- Access and manipulate ocean reanalysis data\n", "- Plot annual mean ocean surface currents and atmospheric surface winds\n", "- Compare oceanic and atmospheric circulation patterns" ] }, { "cell_type": "markdown", "metadata": { "execution": {}, "tags": [] }, "source": [ "# Setup" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "tags": [ "colab" ] }, "outputs": [], "source": [ "# installations ( uncomment and run this cell ONLY when using google colab or kaggle )\n", "\n", "# !pip install cartopy" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import os\n", "import pooch\n", "import tempfile\n", "import xarray as xr\n", "import warnings\n", "from cartopy import crs as ccrs\n", "\n", "# suppress warnings issued by Cartopy when downloading data files\n", "warnings.filterwarnings(\"ignore\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install and import feedback gadget\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Install and import feedback gadget\n", "\n", "!pip3 install vibecheck datatops --quiet\n", "\n", "from vibecheck import DatatopsContentReviewContainer\n", "def content_review(notebook_section: str):\n", " return DatatopsContentReviewContainer(\n", " \"\", # No text prompt\n", " notebook_section,\n", " {\n", " \"url\": \"https://pmyvdlilci.execute-api.us-east-1.amazonaws.com/klab\",\n", " \"name\": \"comptools_4clim\",\n", " \"user_key\": \"l5jpxuee\",\n", " },\n", " ).render()\n", "\n", "\n", "feedback_prefix = \"W1D2_T4\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Helper functions\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Helper functions\n", "\n", "def pooch_load(filelocation=None, filename=None, processor=None):\n", " shared_location = \"/home/jovyan/shared/Data/tutorials/W1D2_Ocean-AtmosphereReanalysis\" # this is different for each day\n", " user_temp_cache = tempfile.gettempdir()\n", "\n", " if os.path.exists(os.path.join(shared_location, filename)):\n", " file = os.path.join(shared_location, filename)\n", " else:\n", " file = pooch.retrieve(\n", " filelocation,\n", " known_hash=None,\n", " fname=os.path.join(user_temp_cache, filename),\n", " processor=processor,\n", " )\n", "\n", " return file" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure Settings\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Figure Settings\n", "import ipywidgets as widgets # interactive display\n", "\n", "%config InlineBackend.figure_format = 'retina'\n", "plt.style.use(\n", " \"https://raw.githubusercontent.com/neuromatch/climate-course-content/main/cma.mplstyle\"\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Video 1: Ocean Wind-driven Circulation\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 1: Ocean Wind-driven Circulation\n", "\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'k-rMUYEskHY'), ('Bilibili', 'BV1tW4y1o7DH')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Ocean_Wind_Driven_Circulation_Video\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "pycharm": { "name": "#%%\n" }, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @markdown\n", "from ipywidgets import widgets\n", "from IPython.display import IFrame\n", "\n", "link_id = \"f8c5r\"\n", "\n", "print(f\"If you want to download the slides: https://osf.io/download/{link_id}/\")\n", "IFrame(src=f\"https://mfr.ca-1.osf.io/render?url=https://osf.io/{link_id}/?direct%26mode=render%26action=download%26mode=render\", width=854, height=480)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Ocean_Wind_Driven_Circulation_Slides\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Section 1: Load Ocean & Atmosphere Reanalysis Data" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Here you will load atmospheric near-surface winds (at 10-meter height), and then load the oceanic surface currents from [ECCO reanalysis data](https://www.ecco-group.org/products-ECCO-V4r4.htm).\n", "\n", "*Note, that each of these variables is a velocity with two components (zonal and meridional). These two velocity components must be loaded separately for each variable, so you will load four datasets.*\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "both", "execution": {} }, "outputs": [], "source": [ "# load data: atmospheric 10m wind from ECCO\n", "# wind in east/west direction labeled here as 'u'\n", "fname_atm_wind_u = \"wind_evel_monthly_2016.nc\"\n", "url_atm_wind_u = \"https://osf.io/ke9yp/download\"\n", "atm_wind_u = xr.open_dataarray(pooch_load(url_atm_wind_u, fname_atm_wind_u))\n", "atm_wind_u" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# wind in north/south direction labeled here as 'v'\n", "fname_atm_wind_v = \"wind_nvel_monthly_2016.nc\"\n", "url_atm_wind_v = \"https://osf.io/9zkgd/download\"\n", "atm_wind_v = xr.open_dataarray(pooch_load(url_atm_wind_v, fname_atm_wind_v))\n", "atm_wind_v" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# load data: oceanic surface current from ECCO\n", "# current in east/west direction labeled here as 'u'\n", "fname_ocn_surface_current_u = \"evel_monthly_2016.nc\"\n", "url_ocn_surface_current_u = \"https://osf.io/tyfbv/download\"\n", "ocn_surface_current_u = xr.open_dataarray(\n", " pooch_load(url_ocn_surface_current_u, fname_ocn_surface_current_u)\n", ")\n", "ocn_surface_current_u" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# current in east/west direction labeled here as 'v'\n", "fname_ocn_surface_current_v = \"nvel_monthly_2016.nc\"\n", "url_ocn_surface_current_v = \"https://osf.io/vzdn4/download\"\n", "ocn_surface_current_v = xr.open_dataarray(\n", " pooch_load(url_ocn_surface_current_v, fname_ocn_surface_current_v)\n", ")\n", "ocn_surface_current_v" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 1.1: Exploring the Reanalysis Data\n", "\n", "Let's examine the time (or temporal/output) frequency, which descibes the rate at which the reanalysis data is provided, for one of the ECCO variables (```atm_wind_u```). \n", "\n", "*Note that all the variables should have the same output frequency.*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "atm_wind_u.time" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "### Questions 1.1\n", "1. Why do you think the atmospheric reanalysis dataset from the previous tutorial (ERA5) has higher output frequency (hourly) than the ECCO ocean reanalysis dataset (daily)?\n", "2. What can you infer about the role of these two systems at different timescales in the climate system?" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "execution": {} }, "source": [ "[*Click for solution*](https://github.com/neuromatch/climate-course-content/tree/main/tutorials/W1D2_Ocean-AtmosphereReanalysis/solutions/W1D2_Tutorial4_Solution_3561fefc.py)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Questions_1_1\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {}, "tags": [] }, "source": [ "# Section 2: Plotting the Annual Mean of Global Surface Wind Stress\n", "\n", "In this section you will create global maps displaying the annual mean of atmospheric 10m winds.\n", "\n", "First, you should compute the annual mean of the surface wind variables. You can do so by averaging over the time dimension using `.mean(dim='time')`. Since you have monthly data spanning only one year, `.mean(dim='time')` will give the annual mean for the year 2016.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# compute the annual mean of atm_wind_u\n", "atm_wind_u_an_mean = atm_wind_u.mean(dim=\"time\")\n", "atm_wind_u_an_mean" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# take the annual mean of atm_wind_stress_v\n", "atm_wind_v_an_mean = atm_wind_v.mean(dim=\"time\")\n", "atm_wind_v_an_mean" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "You are now almost ready to plot! \n", "\n", "However, you currently have separate zonal and meridional wind velocity components $(u,v)$. An effective way of visualizing the total surface wind stress is to create a global map of the *magnitude* and *direction* of the wind velocity vector. This type of plot is known as a vector field. A [vector](https://glossary.ametsoc.org/wiki/Vector) is a special mathematical quantity that has both magnitude and direction, just like the wind! The velocity components describe the intensity of wind blowing in the zonal $\\left(u\\right)$ or meridional $\\left(v\\right)$ directions. Specifically, wind can blow eastward (positive $u$) or westward (negative $u$), as well as northward (positive $v$) or southward (negative $v$).\n", "\n", "The total velocity vector is the *vector sum* of these two components and exhibits varying magnitude and direction. The magnitude $\\left(||u||\\right)$ and direction $\\left(\\varphi\\right)$ of the total velocity vector can be determined using the following equations:\n", "\n", "\\begin{align}\n", "||u|| = \\sqrt{u^2 + v^2}, \\ \\ \\ \\ \\varphi = \\text{tan}^{-1}(\\frac{v}{u})\n", "\\end{align}\n", "\n", "When plotting a vector field using a computer, it is commonly referred to as a quiver plot. In our case, we will utilize a [function created by Ryan Abernathey's](https://rabernat.github.io/intro_to_physical_oceanography/07_ekman.html) called `quick_quiver()` that calculates the magnitude and direction of the total velocity vector based on the given zonal and meridional components.\n", "\n", "We will overlay the quiver plot on top of the annual mean ocean surface temperature (labeled here as theta)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "fname_surface_temp = \"surface_theta.nc\"\n", "url_fname_surface_temp = \"https://osf.io/98ksr/download\"\n", "ocn_surface_temp = xr.open_dataarray(\n", " pooch.retrieve(url_fname_surface_temp, known_hash=None, fname=fname_surface_temp)\n", ")\n", "ocn_surface_temp" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# longitude ad latitude coordinates for plotting\n", "lon = atm_wind_u_an_mean.longitude\n", "lat = atm_wind_u_an_mean.latitude\n", "\n", "# calculate magnitude of total velocity\n", "mag = (atm_wind_u_an_mean**2 + atm_wind_v_an_mean**2) ** 0.5\n", "\n", "# coarsen the grid so the arrows are distinguishable by only selecting\n", "# some longitudes and latitudes defined by the last entry of the tuples.\n", "slx = slice(None, None, 20)\n", "sly = slice(None, None, 20)\n", "sl2d = (slx, sly)\n", "\n", "fig, ax = plt.subplots(\n", " figsize=(12, 6), subplot_kw={\"projection\": ccrs.Robinson(central_longitude=180)}\n", ")\n", "\n", "c = ax.contourf(lon, lat, ocn_surface_temp, alpha=0.65)\n", "\n", "# plot quiver arrows indicating vector direction (winds are in blue, alpha is for opacity)\n", "q = ax.quiver(\n", " lon[slx],\n", " lat[sly],\n", " atm_wind_u_an_mean[sl2d],\n", " atm_wind_v_an_mean[sl2d],\n", " color=\"b\",\n", " alpha=0.5,\n", ")\n", "\n", "ax.quiverkey(q, 175, 95, 5, \"5 m/s\", coordinates=\"data\")\n", "\n", "fig.colorbar(c, label=\"Sea Surface Temperature (°C)\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Section 3: Comparing Global Maps of Surface Currents and Winds" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "In this section you will compute the annual mean of the ocean surface currents, similar to your above analyses of atmospheric winds, and you will create a global map that shows both of these variables simultaneously." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "tags": [] }, "outputs": [], "source": [ "# take the annual mean of ocn_surface_current_u\n", "ocn_surface_current_u_an_mean = ocn_surface_current_u.mean(dim=\"time\")\n", "ocn_surface_current_u_an_mean" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# take the annual mean of ocn_surface_current_v\n", "ocn_surface_current_v_an_mean = ocn_surface_current_v.mean(dim=\"time\")\n", "ocn_surface_current_v_an_mean" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Let's add ocean surface currents to the previous plot above, using **red** quivers. Note the scale of the arrows for the ocean and atmosphere are very different." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# longitude ad latitude coordinates for plotting\n", "lon = atm_wind_u_an_mean.longitude\n", "lat = atm_wind_u_an_mean.latitude\n", "\n", "# calculate magnitude of total velocity\n", "mag = (atm_wind_u_an_mean**2 + atm_wind_v_an_mean**2) ** 0.5\n", "\n", "# coarsen the grid so the arrows are distinguishable by only selecting\n", "# some longitudes and latitudes defined by slx and sly.\n", "slx = slice(None, None, 20)\n", "sly = slice(None, None, 20)\n", "sl2d = (slx, sly)\n", "\n", "# fig, ax = plt.subplots(**kwargs)\n", "fig, ax = plt.subplots(\n", " figsize=(12, 6), subplot_kw={\"projection\": ccrs.Robinson(central_longitude=180)}\n", ")\n", "\n", "c = ax.contourf(lon, lat, ocn_surface_temp, alpha=0.5)\n", "\n", "# plot quiver arrows indicating vector direction (winds are in blue, alpha is for opacity)\n", "q1 = ax.quiver(\n", " lon[slx],\n", " lat[sly],\n", " atm_wind_u_an_mean[sl2d],\n", " atm_wind_v_an_mean[sl2d],\n", " color=\"b\",\n", " alpha=0.5,\n", ")\n", "\n", "# plot quiver arrows indicating vector direction (ocean currents are in red, alpha is for opacity)\n", "q2 = ax.quiver(\n", " lon[slx],\n", " lat[sly],\n", " ocn_surface_current_u_an_mean[sl2d],\n", " ocn_surface_current_v_an_mean[sl2d],\n", " color=\"r\",\n", " alpha=0.5,\n", ")\n", "\n", "ax.quiverkey(q1, 175, 95, 5, \"5 m/s \", coordinates=\"data\")\n", "\n", "ax.quiverkey(q2, 150, 95, 0.05, \"5 cm/s \", coordinates=\"data\")\n", "\n", "fig.colorbar(c, label=\"Sea Surface Temperature (°C)\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Questions 3\n", "\n", "You may notice that the surface currents (red) are typically not aligned with the wind direction (blue). In fact, the surface ocean currents flow at an angle of approximately 45 degrees to the wind direction! \n", "\n", "The combination of [Coriolis force](https://en.wikipedia.org/wiki/Coriolis_force) and the friction between ocean layers causes the wind-driven currents to turn and weaken with depth, eventually disappearing entirely at depth. The resulting current *profile* is called the **[Ekman Spiral](https://en.wikipedia.org/wiki/Ekman_spiral)**, and the depth over which the spiral is present is called the **Ekman layer**. While the shape of this spiral can vary in time and space, the depth-integrated transport of water within the Ekman layer is called [**Ekman transport**](https://en.wikipedia.org/wiki/Ekman_transport). Ekman transport is always *90 degrees to the right* of the wind in the Northern Hemisphere, and 90 degrees to the *left* of the wind in the Southern Hemisphere. Under certain wind patterns or near coastlines, Ekman transport can lead to **Ekman Pumping**, where water is upwelled or downwelled depending on the wind direction. This process is particularly important for coastal ecosystems that rely on this upwelling (which is often seasonal) to bring nutrient-rich waters near the surface. These nutrients fuel the growth of tiny marine plants that support the entire food chain, upon which coastal economies often rely. These tiny plants are also responsible for most of the oxygen we breathe!\n", "\n", "1. Do you observe a deflection of the surface currents relative to the wind? \n", "2. Is this deflection the same in both the Northern and Southern Hemispheres?\n" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "execution": {} }, "source": [ "[*Click for solution*](https://github.com/neuromatch/climate-course-content/tree/main/tutorials/W1D2_Ocean-AtmosphereReanalysis/solutions/W1D2_Tutorial4_Solution_c7eff2a7.py)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Questions_3\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Summary\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "In this tutorial, you explored how atmospheric winds can shape ocean surface currents.\n", "\n", "You calculated the surface wind vector and assessed its influence on surface ocean currents called the wind-driven circulation.\n", "\n", "This circulation influences global climate (e.g., moving heat between latitudes) and biogeochemical cycles (e.g., by driving upwelling of nutrient-rich water). In the next tutorial, you will explore another mechanism for ocean movement, *density-driven circulation*.\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Resources\n", "\n", "Data for this tutorial can be accessed [here](https://www.ecco-group.org/products-ECCO-V4r4.htm)." ] } ], "metadata": { "colab": { "collapsed_sections": [], "include_colab_link": true, "name": "W1D2_Tutorial4", "provenance": [], "toc_visible": true }, "kernel": { "display_name": "Python 3", "language": "python", "name": "python3" }, "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.9.19" }, "toc-autonumbering": true }, "nbformat": 4, "nbformat_minor": 4 }