diff --git a/lessons/bmi/bmi-run-model-from-bmi.ipynb b/lessons/bmi/bmi-run-model-from-bmi.ipynb
new file mode 100644
index 0000000..ddd3639
--- /dev/null
+++ b/lessons/bmi/bmi-run-model-from-bmi.ipynb
@@ -0,0 +1,429 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Run the `Heat` model through its BMI"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "`Heat` models the diffusion of temperature on a uniform rectangular plate with Dirichlet boundary conditions. This is the canonical example used in the [bmi-example-python](https://github.com/csdms/bmi-example-python) repository. View the source code for the [model](https://github.com/csdms/bmi-example-python/blob/master/heat/heat.py) and its [BMI](https://github.com/csdms/bmi-example-python/blob/master/heat/bmi_heat.py) on GitHub."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Start by importing `os`, `numpy` and the `Heat` BMI:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import numpy as np\n",
+ "\n",
+ "from heat import BmiHeat"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Create an instance of the model's BMI."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x = BmiHeat()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "What's the name of this model?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The 2D Heat Equation\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(x.get_component_name())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Start the `Heat` model through its BMI using a configuration file:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "# Heat model configuration\r\n",
+ "shape:\r\n",
+ " - 6\r\n",
+ " - 8\r\n",
+ "spacing:\r\n",
+ " - 1.0\r\n",
+ " - 1.0\r\n",
+ "origin:\r\n",
+ " - 0.0\r\n",
+ " - 0.0\r\n",
+ "alpha: 1.0\r\n"
+ ]
+ }
+ ],
+ "source": [
+ "cat heat.yaml"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x.initialize('heat.yaml')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Check the time information for the model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Start time: 0.0\n",
+ "End time: 1.7976931348623157e+308\n",
+ "Current time: 0.0\n",
+ "Time step: 0.25\n",
+ "Time units: s\n"
+ ]
+ }
+ ],
+ "source": [
+ "print('Start time:', x.get_start_time())\n",
+ "print('End time:', x.get_end_time())\n",
+ "print('Current time:', x.get_current_time())\n",
+ "print('Time step:', x.get_time_step())\n",
+ "print('Time units:', x.get_time_units())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Show the input and output variables for the component (aside on [Standard Names](https://csdms.colorado.edu/wiki/CSDMS_Standard_Names)):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "('plate_surface__temperature',)\n",
+ "('plate_surface__temperature',)\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(x.get_input_var_names())\n",
+ "print(x.get_output_var_names())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, get attributes of the grid on which the temperature variable is defined:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Grid id: 0\n",
+ "Grid rank: 2\n",
+ "Grid shape: (6, 8)\n",
+ "Grid spacing: [1.0, 1.0]\n",
+ "Grid type: uniform_rectilinear_grid\n"
+ ]
+ }
+ ],
+ "source": [
+ "grid_id = x.get_var_grid('plate_surface__temperature')\n",
+ "print('Grid id:', grid_id)\n",
+ "print('Grid rank:', x.get_grid_rank(grid_id))\n",
+ "print('Grid shape:', x.get_grid_shape(grid_id))\n",
+ "print('Grid spacing:', x.get_grid_spacing(grid_id))\n",
+ "print('Grid type:', x.get_grid_type(grid_id))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Through the model's BMI, zero out the initial temperature field, except for an impulse:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "temperature = np.zeros(x.get_grid_shape(grid_id))\n",
+ "temperature[3, 4] = 100.0\n",
+ "x.set_value('plate_surface__temperature', temperature)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Check that the temperature field has been updated:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[[ 0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 100. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0.]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "initial_temperature = x.get_value('plate_surface__temperature')\n",
+ "print(initial_temperature)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now advance the model by a single time step:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x.update()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "View the new state of the temperature field:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[[ 0. 0. 0. 0. 0. 0. 0. 0. ]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0. ]\n",
+ " [ 0. 0. 0. 0. 12.5 0. 0. 0. ]\n",
+ " [ 0. 0. 0. 12.5 50. 12.5 0. 0. ]\n",
+ " [ 0. 0. 0. 0. 12.5 0. 0. 0. ]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0. ]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "updated_temperature = x.get_value('plate_surface__temperature')\n",
+ "print(updated_temperature)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There's diffusion!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Advance the model to some distant time:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "distant_time = 2.0\n",
+ "while x.get_current_time() < distant_time:\n",
+ " x.update()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "View the final state of the temperature field:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[[ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0]\n",
+ " [ 0.0 0.2 0.9 2.1 2.8 2.1 0.9 0.0]\n",
+ " [ 0.0 0.7 2.2 4.7 6.2 4.7 2.1 0.0]\n",
+ " [ 0.0 0.9 3.0 6.1 7.9 6.1 2.8 0.0]\n",
+ " [ 0.0 0.6 2.0 4.1 5.3 4.1 1.8 0.0]\n",
+ " [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "final_temperature = x.get_value('plate_surface__temperature')\n",
+ "np.set_printoptions(formatter={'float': '{: 5.1f}'.format})\n",
+ "print(final_temperature)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that temperature isn't conserved on the plate:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "74.10263419151306\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(final_temperature.sum())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "End the model:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x.finalize()"
+ ]
+ }
+ ],
+ "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.8.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/lessons/bmi/bmi-run-model.ipynb b/lessons/bmi/bmi-run-model.ipynb
new file mode 100644
index 0000000..8ef4114
--- /dev/null
+++ b/lessons/bmi/bmi-run-model.ipynb
@@ -0,0 +1,295 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Run the `Heat` model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "`Heat` models the diffusion of temperature on a uniform rectangular plate with Dirichlet boundary conditions. This is the canonical example used in the [bmi-example-python](https://github.com/csdms/bmi-example-python) repository. View the [source code](https://github.com/csdms/bmi-example-python/blob/master/heat/heat.py) for the model on GitHub."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Start by importing `numpy` and `Heat`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "from heat.heat import Heat"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Create an instance of the model, setting `shape` and `alpha` parameters:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_rows = 6\n",
+ "n_cols = 8\n",
+ "conductivity = 1.0\n",
+ "m = Heat(shape=(n_rows,n_cols), alpha=conductivity)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Show derived parameters from the model:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Grid spacing: (1.0, 1.0)\n",
+ "Time step: 0.25\n"
+ ]
+ }
+ ],
+ "source": [
+ "print('Grid spacing:', m.spacing)\n",
+ "print('Time step:', m.time_step)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "What does the initial temperature field look like?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[[0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [0. 0. 0. 0. 0. 0. 0. 0.]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "m.temperature = np.zeros_like(m.temperature)\n",
+ "print(m.temperature)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Add an impulse to the temperature field: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[[ 0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 100. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0.]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0.]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "m.temperature[3, 4] = 100.0\n",
+ "print(m.temperature)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Advance the model by a single time step:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "m.advance_in_time()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "View the new state of the temperature field:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[[ 0. 0. 0. 0. 0. 0. 0. 0. ]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0. ]\n",
+ " [ 0. 0. 0. 0. 12.5 0. 0. 0. ]\n",
+ " [ 0. 0. 0. 12.5 50. 12.5 0. 0. ]\n",
+ " [ 0. 0. 0. 0. 12.5 0. 0. 0. ]\n",
+ " [ 0. 0. 0. 0. 0. 0. 0. 0. ]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(m.temperature)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There's diffusion!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Advance the model to some distant time:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "distant_time = 2.0\n",
+ "while m.time < distant_time:\n",
+ " m.advance_in_time()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "View the new state of the temperature field (with help from `np.set_printoptions`):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[[ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0]\n",
+ " [ 0.0 0.2 0.9 2.1 2.8 2.1 0.9 0.0]\n",
+ " [ 0.0 0.7 2.2 4.7 6.2 4.7 2.1 0.0]\n",
+ " [ 0.0 0.9 3.0 6.1 7.9 6.1 2.8 0.0]\n",
+ " [ 0.0 0.6 2.0 4.1 5.3 4.1 1.8 0.0]\n",
+ " [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "np.set_printoptions(formatter={'float': '{: 5.1f}'.format})\n",
+ "print(m.temperature)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that temperature is set to zero at the boundaries."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "74.10263419151306"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.temperature.sum()"
+ ]
+ }
+ ],
+ "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.8.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/lessons/bmi/heat.yaml b/lessons/bmi/heat.yaml
new file mode 100644
index 0000000..a5bd435
--- /dev/null
+++ b/lessons/bmi/heat.yaml
@@ -0,0 +1,11 @@
+# Heat model configuration
+shape:
+ - 6
+ - 8
+spacing:
+ - 1.0
+ - 1.0
+origin:
+ - 0.0
+ - 0.0
+alpha: 1.0
diff --git a/lessons/bmi/index.ipynb b/lessons/bmi/index.ipynb
new file mode 100644
index 0000000..4795d3b
--- /dev/null
+++ b/lessons/bmi/index.ipynb
@@ -0,0 +1,53 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Exploring Surface Processes using CSDMS Tools: How to Build Coupled Models\n",
+ "\n",
+ "## Day 2: The Basic Model Interface (BMI)\n",
+ "\n",
+ "Today we'll explore through a pair of Jupyter Notebooks what the Basic Model Interface is, and how to use it.\n",
+ "\n",
+ "* [Run the *Heat* model](bmi-run-model.ipynb)\n",
+ " * View model source code\n",
+ " * Set up the model\n",
+ " * Run the model\n",
+ " * View output\n",
+ "\n",
+ "* [Run the *Heat* model through its BMI](bmi-run-model-from-bmi.ipynb)\n",
+ " * View model BMI source code\n",
+ " * Set up the model through its BMI\n",
+ " * Run the model\n",
+ " * View output\n",
+ "\n",
+ "Additional information:\n",
+ " * [BMI documentation](https://bmi.readthedocs.io) \n",
+ " * [Python BMI spec](https://github.com/csdms/bmi-python)\n",
+ " * [Python BMI example](https://github.com/csdms/bmi-example-python)\n"
+ ]
+ }
+ ],
+ "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.7.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}