{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Welcome to PyCO2SYS!\n", "\n", "***If you're totally new to PyCO2SYS, this is the right place to be!***\n", "\n", "If you're also new to Python, you should still be able to follow what's going on here and understand how to make some changes to the code - but this is not intended as a Python tutorial.\n", "\n", "We will work through a series of examples of PyCO2SYS calculations, starting with the most basic and gradually building up the complexity.\n", "\n", "> **How does this work?**\n", "> \n", "> This is a [Jupyter notebook](https://realpython.com/jupyter-notebook-introduction/). If you're viewing this [through Binder](https://mybinder.org/v2/gh/mvdh7/PyCO2SYS-examples/master), then Python is running live in your web browser!\n", ">\n", "> You can run the different sections of code below by clicking on them with your mouse. The selected code section gets outlined by a box with a thick green or blue bar down the left-hand side. Then either click the Run button at the top or press ctrl+enter on your keyboard to run the selected section.\n", ">\n", "> To the upper left of each code section you will see `In [ ]:`. An asterisk (`*`) appears between the brackets while the code is running, which changes to a number once each section of code has successfully run. Once it has been run (but not before!), the results calculated in one code section can be used in any other. **Some sections do rely on earlier ones having been run, so make sure you run them all in sequence!**\n", ">\n", "> You can edit the code freely and run it again to see what happens. Don't worry, your changes are only local, so they won't affect anyone else. If you close the notebook and start it up again it will all be reset (unless you've downloaded this and are running it on your own Jupyter server).\n", "\n", "## Import CO2SYS\n", "\n", "Before we can use any package like PyCO2SYS in Python we must import it. This is very simple to do, using the following convention. Not much appears to happen, but check that a number appears in the brackets at the top left (e.g. `In [1]:`):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import PyCO2SYS as pyco2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Solve the marine carbonate system!\n", "\n", "Now that we've imported PyCO2SYS, we can define some seawater conditions and solve the marine carbonate system from them.\n", "\n", "Imagine we measured the pH and dissolved inorganic carbon (DIC) of a seawater sample in the lab at 25 °C. The sample was collected from a seawater pressure of 5 dbar (roughly 5 m depth - pressure excludes atmospheric pressure) and it had a temperature of 10 °C there. We want to know what the saturation state of aragonite was *in situ* when and where the sample was collected.\n", "\n", "The lab environment sets the \"input conditions\" for PyCO2SYS's arguments and results (i.e. temperature = 25 °C, pressure = 0), while the in situ environment sets the \"output conditions\" (i.e. temperature = 10 °C, pressure = 100 dbar).\n", "\n", "> For more information on the arguments and results below please consult the [online docs](https://pyco2sys.readthedocs.io/en/latest/co2sys_nd/).\n", ">\n", "> You can also find the [full citations](https://pyco2sys.readthedocs.io/en/latest/refs/) for the abbreviated references (e.g. `LDK00`) mentioned in the code." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Everything on a line after a # symbol is just a comment!\n", "\n", "# Set up a dict for the keyword arguments, for convenience\n", "pyco2_kws = {}\n", "\n", "# Define the known marine carbonate system parameters\n", "pyco2_kws[\"par1\"] = 8.05 # pH measured in the lab, Total scale\n", "pyco2_kws[\"par2\"] = 2050 # DIC measured in the lab in μmol/kg-sw\n", "pyco2_kws[\"par1_type\"] = 3 # tell PyCO2SYS: \"par1 is a pH value\"\n", "pyco2_kws[\"par2_type\"] = 2 # tell PyCO2SYS: \"par2 is a DIC value\"\n", "\n", "# Define the seawater conditions and add them to the dict\n", "pyco2_kws[\"salinity\"] = 35 # practical salinity\n", "pyco2_kws[\"temperature\"] = 25 # lab temperature (input conditions) in °C\n", "pyco2_kws[\"temperature_out\"] = 10 # in-situ temperature (output conditions) in °C\n", "pyco2_kws[\"pressure\"] = 0 # lab pressure (input conditions) in dbar, ignoring the atmosphere\n", "pyco2_kws[\"pressure_out\"] = 5 # in-situ pressure (output conditions) in dbar, ignoring the atmosphere\n", "pyco2_kws[\"total_silicate\"] = 8.2 # total silicate in μmol/kg-sw\n", "pyco2_kws[\"total_phosphate\"] = 0.3 # total phosphate in μmol/kg-sw\n", "pyco2_kws[\"total_ammonia\"] = 0.1 # total ammonia in μmol/kg-sw\n", "pyco2_kws[\"total_sulfide\"] = 0.2 # total sulfide in μmol/kg-sw\n", "\n", "# Define PyCO2SYS settings and add them to the dict\n", "pyco2_kws[\"opt_pH_scale\"] = 1 # tell PyCO2SYS: \"the pH input is on the Total scale\"\n", "pyco2_kws[\"opt_k_carbonic\"] = 10 # tell PyCO2SYS: \"use carbonate equilibrium constants of LDK00\"\n", "pyco2_kws[\"opt_k_bisulfate\"] = 1 # tell PyCO2SYS: \"use bisulfate dissociation constant of D90a\"\n", "pyco2_kws[\"opt_total_borate\"] = 1 # tell PyCO2SYS: \"use borate:salinity of LKB10\"\n", "\n", "# Now calculate everything with PyCO2SYS!\n", "results = pyco2.sys(**pyco2_kws)\n", "\n", "# `results` contains all the different calculated seawater properties as a dict.\n", "# Aragonite saturation state under the output conditions has the key \"saturation_aragonite_out\".\n", "# Here we extract that result only and store it as `omega_arag_insitu`:\n", "omega_arag_insitu = results[\"saturation_aragonite_out\"]\n", "\n", "# Now, tell us the answer!\n", "print(\"The calculated result is:\", omega_arag_insitu)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If all went well then you should see the aragonite saturation state (about 3.2 for the example values used) printed out directly above here.\n", "\n", "### Try changing the arguments\n", "\n", "You can edit the code above and then re-run it to see the effect on the result. Some things you might try:\n", "\n", " 1. Change the **seawater conditions** (any of `par1`, `par2`, `salinity`, `temperature`, `temperature_out`, `pressure`, `pressure_out`, `total_silicate`, `total_phosphate`, `total_ammonia` and `total_sulfide`).\n", " \n", " 2. Change the **equilibrium constants** used for the calculations (`opt_k_carbonic`, `opt_k_bisulfate` and `opt_total_borate`; see the [settings docs](https://pyco2sys.readthedocs.io/en/latest/co2sys_nd/#settings) for your options).\n", " \n", " 3. Change the **pH scale** that the input pH is declared as being on (`opt_pH_scale`; see the [settings docs](https://pyco2sys.readthedocs.io/en/latest/co2sys_nd/#settings)).\n", " \n", " 4. Change the input pair of **marine carbonate system parameters** (`par1` and `par1_type` and/or `par2` and `par2_type`; see the [carbonate system parameters docs](https://pyco2sys.readthedocs.io/en/latest/co2sys/#carbonate-system-parameters)).\n", " \n", " 5. Change the **results variable** that is printed out (change `\"saturation_aragonite_out\"` to something different; see the [results docs](https://pyco2sys.readthedocs.io/en/latest/co2sys_nd/#results))." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calculations with arrays\n", "\n", "PyCO2SYS doesn't have to process one calculation at a time - it is optimised to run calculations over many different solution compositions at the same time.\n", "\n", "> In the example below, only one of the arguments is an array, while the others are all single values.\n", ">\n", "> But every single input variable given to PyCO2SYS can be provided either as an array, or as a single value, in any combination - including those controlling the [settings](https://pyco2sys.readthedocs.io/en/latest/co2sys/#settings).\n", ">\n", "> Arrays should be [NumPy ndarrays](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html?highlight=ndarray#numpy.ndarray) but they can have as many dimensions as you like, in any combination that is compatible for NumPy broadcasting. Results that depend only on scalar arguments are also scalar. Results that depend on non-scalar arguments will all be in the shape of all arguments fully broadcasted together.\n", "\n", "### Effect of increasing pCO2\n", "\n", "Let's continue from the example above and see how increasing seawater pCO2 alone would change the saturation state of aragonite for that sample, assuming that total alkalinity remains constant.\n", "\n", "#### Get starting conditions\n", "\n", "We'll use the same seawater conditions and PyCO2SYS settings as we defined previously, so we don't need to write those out again. But we will redefine the marine carbonate system, starting from the total alkalinity and seawater pCO2 that we calculated above as part of `results`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Get total alkalinity and initial seawater pCO2\n", "TAlk = results[\"alkalinity\"]\n", "pCO2_now = results[\"pCO2_out\"]\n", "\n", "# See what they are!\n", "print(\"Total alkalinity is: {:.1f} μmol/kg-sw\".format(TAlk))\n", "print(\"Initial seawater pCO2 is: {:.1f} μatm\".format(pCO2_now))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Generate array of increasing pCO2 values\n", "\n", "We can use a tool from the Python package [NumPy](https://numpy.org/doc/stable/) to conveniently generate an array of increasing seawater pCO2 values:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np # now we have access to NumPy's tools\n", "\n", "pCO2_end = 1000 # maximum pCO2 value to go to in μatm\n", "n_steps = 20 # number of regularly-spaced values to make from `pCO2_now` to `pCO2_end`\n", "pCO2_increasing = np.linspace(pCO2_now, pCO2_end, n_steps) # generate the array of pCO2 values\n", "\n", "print(\"Increasing pCO2 values in μatm:\")\n", "print(pCO2_increasing)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Solve the marine carbonate system\n", "\n", "Now, we can simply pass in this array as one of the inputs to PyCO2SYS along with `TAlk` and the other values that we defined before. We also need to:\n", "\n", " * Update the `par1_type` and `par2_type` inputs to `1` for total alkalinity and `4` for seawater pCO2 (see the [input docs](https://pyco2sys.readthedocs.io/en/latest/co2sys_nd/#carbonate-system-parameters)).\n", " \n", " * Use `temperature_out` and `pressure_out` as the \\\"input\\\" conditions (i.e. `temperature` and `pressure`). This is because the pCO2 we're now using as our input was calculated at the \"output\" conditions of the original calculation (i.e. in situ). When we do calculations entirely using in situ values, then there's no distinction between \"input\" and \"output\" conditions.\n", "\n", "We access the calculated results in exactly the same way, but now they are all either scalar or arrays of the same size as `pCO2_increasing`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Copy original kwargs dict\n", "import copy\n", "pyco2_kws_response = copy.deepcopy(pyco2_kws)\n", "\n", "# Copy original output temperature and pressure as new input conditions\n", "pyco2_kws_response[\"temperature\"] = pyco2_kws[\"temperature_out\"]\n", "pyco2_kws_response[\"pressure\"] = pyco2_kws[\"pressure_out\"]\n", "\n", "# Remove now-unnecessary output conditions (optional)\n", "pyco2_kws_response.pop(\"temperature_out\")\n", "pyco2_kws_response.pop(\"pressure_out\")\n", "\n", "# Switch to the new known parameter values\n", "pyco2_kws_response[\"par1\"] = TAlk\n", "pyco2_kws_response[\"par2\"] = pCO2_increasing\n", "pyco2_kws_response[\"par1_type\"] = 1\n", "pyco2_kws_response[\"par2_type\"] = 4\n", "\n", "# Solve the marine carbonate system with increasing seawater pCO2\n", "results_response = pyco2.sys(**pyco2_kws_response)\n", "\n", "# See how aragonite saturation state changes\n", "omega_arag_response = results_response[\"saturation_aragonite\"]\n", "print(\"Aragonite saturation state response to increasing seawater pCO2:\")\n", "print(omega_arag_response)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Make a figure of the results\n", "\n", "The Python package [Matplotlib](https://matplotlib.org/) can help us visualise this result:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from matplotlib import pyplot as plt # now we can use Matplotlib's plotting tools\n", "# Make the figures interactive:\n", "%matplotlib notebook\n", "\n", "# Draw a very basic plot\n", "fig, ax = plt.subplots()\n", "ax.scatter(pCO2_increasing, omega_arag_response)\n", "ax.set_xlabel('Seawater $p$CO$_2$ / μatm')\n", "ax.set_ylabel('Ω(aragonite)');" ] } ], "metadata": { "@webio": { "lastCommId": null, "lastKernelId": null }, "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.4" } }, "nbformat": 4, "nbformat_minor": 4 }