diff --git a/lessons/landlab/landlab/create_a_component-solution.ipynb b/lessons/landlab/landlab/create_a_component-solution.ipynb
new file mode 100644
index 0000000..abbe69f
--- /dev/null
+++ b/lessons/landlab/landlab/create_a_component-solution.ipynb
@@ -0,0 +1,880 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "7403e724",
+ "metadata": {
+ "tags": [
+ "toc"
+ ]
+ },
+ "source": [
+ "# Table of Contents\n",
+ "* [Building a landlab component](#Building-a-landlab-component)\n",
+ " * [1. Linear Diffusion as a Function](#1.-Linear-Diffusion-as-a-Function)\n",
+ " * [2. Represent the function as a class](#2.-Represent-the-function-as-a-class)\n",
+ " * [3. Create a landlab component class](#3.-Create-a-landlab-component-class)\n",
+ " * [4. Create a LandscapeUplifter component](#4.-Create-a-LandscapeUplifter-component)\n",
+ " * [5. Couple LandscapeDiffuser with LandscapeUplifter](#5.-Couple-LandscapeDiffuser-with-LandscapeUplifter)\n",
+ " * [6. Construct a landscape evolution model](#6.-Construct-a-landscape-evolution-model)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "571cb775",
+ "metadata": {},
+ "source": [
+ "# Building a landlab component\n",
+ "\n",
+ "In this next section we will build two landlab components: one with my help and one on your own. These two components will model physical processes that we will use later on to build a landscape evolution model.\n",
+ "\n",
+ "The first component will be a linear diffusion component to model soil creep on hillslopes and the second will uplift (or subside) the landscape through time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1dd92223",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from landlab import LinkStatus, NodeStatus, RasterModelGrid\n",
+ "from tqdm import trange"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17edd289",
+ "metadata": {},
+ "source": [
+ "## 1. Linear Diffusion as a Function\n",
+ "\n",
+ "We will begin with a function that solves the diffusion equation on a landlab grid using the equations from the previous section. This will be the function we will turn into a *landlab* component.\n",
+ "\n",
+ "Our function takes a *landlab* grid, an array of values to diffuse, and a keyword argument to specify the diffusion coeffiecient. The result will be the diffusion rate at each node of the grid."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ff989542",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def calc_diffusion_rate(grid, value_at_node, diffusion_coefficient=1.0):\n",
+ " \"\"\"Calculate the rate of diffusion of a quantity defined on a landlab grid.\n",
+ "\n",
+ " Parameters\n",
+ " ----------\n",
+ " grid : ModelGrid\n",
+ " A landlab grid.\n",
+ " value_at_node : ndarray of float\n",
+ " The quantity to diffuse, defined at the grid's nodes.\n",
+ " diffusion_coefficient : float, optional\n",
+ " Diffusion coefficent to use for the diffusion.\n",
+ "\n",
+ " Returns\n",
+ " -------\n",
+ " value_at_node\n",
+ " Input array of values after diffusion.\n",
+ " \"\"\"\n",
+ " qs = -diffusion_coefficient * grid.calc_grad_at_link(value_at_node)\n",
+ " qs[grid.status_at_link != LinkStatus.ACTIVE] = 0.0\n",
+ "\n",
+ " dzdt = -grid.calc_flux_div_at_node(qs)\n",
+ " return dzdt"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7794b864",
+ "metadata": {},
+ "source": [
+ "Now try to run this function on a *landlab* grid to diffuse some quantity. For simplicity, use a `RasterModelGrid`. Although `calc_diffusion_rate` function is agnostic as to what grid you pass it, it's will be easier to visualize a raster grid.\n",
+ "\n",
+ "To begin with, see if you can,\n",
+ "* Create a `RasterModelGrid`\n",
+ "* Create an array of values (at grid nodes) to diffuse\n",
+ "* Set boundary conditions\n",
+ "\n",
+ "To make things interesting, you will need something to diffuse. Try to initialize your array of values with a step that goes from 0.0 for $x <= 100$ to 10.0 for $x > 100.0$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "419e31d9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "77187962",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "grid = RasterModelGrid((100, 200))\n",
+ "\n",
+ "z = grid.zeros(at=\"node\")\n",
+ "z[(grid.x_of_node > 100)] = 10.0\n",
+ "\n",
+ "grid.status_at_node[grid.nodes_at_left_edge] = NodeStatus.FIXED_VALUE\n",
+ "grid.status_at_node[grid.nodes_at_right_edge] = NodeStatus.FIXED_VALUE\n",
+ "grid.status_at_node[grid.nodes_at_top_edge] = NodeStatus.CLOSED\n",
+ "grid.status_at_node[grid.nodes_at_bottom_edge] = NodeStatus.CLOSED\n",
+ "\n",
+ "grid.imshow(\"node\", z)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8d907c9b",
+ "metadata": {},
+ "source": [
+ "Before we start to run diffusion through time, we need to determine a stable time step so our solution doesn't blow up.\n",
+ "\n",
+ "We will use a timestep with a [CourantโFriedrichsโLewy condition](https://en.wikipedia.org/wiki/CourantโFriedrichsโLewy_condition) of $C_{cfl}=0.2$. This will keep our solution numerically stable. \n",
+ "\n",
+ "$C_{cfl} = \\frac{\\Delta t D}{\\Delta x^2} = 0.2$\n",
+ "\n",
+ "Write a function that calculates a stable time step, $dt$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c39d1f6c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def calc_stable_time_step(grid, diffusion_coefficient):\n",
+ " return # Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a0dc43b0",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "def calc_stable_time_step(grid, diffusion_coefficient):\n",
+ " return 0.2 * grid.length_of_link.min()**2 / diffusion_coefficient"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "21aa1418",
+ "metadata": {},
+ "source": [
+ "And now for some landform evolution! First calculate a stable time step for your simulation and then write a 1000 iteration loop that, for each iteration, calculates the diffusion rate and then adds that to the current landscape.\n",
+ "\n",
+ "If you like, you can experiment with different values for the diffusion coefficient and the time step."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d61578d2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here: calculate time step\n",
+ "\n",
+ "for _ in trange(1000):\n",
+ " # Type your code here: calculate diffusion and update *z*\n",
+ "\n",
+ "grid.imshow(z)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "85edf236",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "diffusion_coefficient = 1.0\n",
+ "dt = calc_stable_time_step(grid, diffusion_coefficient)\n",
+ "\n",
+ "for _ in trange(1000):\n",
+ " z += calc_diffusion_rate(\n",
+ " grid, z, diffusion_coefficient=diffusion_coefficient\n",
+ " ) * dt\n",
+ "\n",
+ "grid.imshow(\"node\", z)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8309a0ac",
+ "metadata": {},
+ "source": [
+ "## 2. Represent the function as a class\n",
+ "\n",
+ "The next step in turning a function into a component is to represent your function as a class. To begin with, we implement two methods:\n",
+ "* `__init__`: this method is called when the class is instantiated and will accept to arguments. The first MUST be a landlab grid, the second will be a keyword that sets the diffusion coefficient.\n",
+ "* `calc_rate`: this method will take an array of values and return the diffusion rate at each element.\n",
+ "* `calc_stable_time_step`: this method will calculate a stable time step for the diffuser.\n",
+ "\n",
+ "So that we're all on the same page, let's call the new class `LandscapeDiffuser`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3d366d12",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e70ccb2e",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "class LandscapeDiffuser:\n",
+ " def __init__(self, grid, diffusion_coefficient=1.0):\n",
+ " self._grid = grid\n",
+ " self._diffusion_coefficient = diffusion_coefficient\n",
+ "\n",
+ " def calc_rate(self, values):\n",
+ " return calc_diffusion_rate(\n",
+ " self._grid, values, diffusion_coefficient=self._diffusion_coefficient\n",
+ " )\n",
+ "\n",
+ " def calc_stable_time_step(self):\n",
+ " return 0.2 * self._grid.length_of_link.min()**2 / self._diffusion_coefficient"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "204d192d",
+ "metadata": {},
+ "source": [
+ "Now, just as you did before, run diffusion on a *landlab* grid only this time using the new `LandscapeDiffuser` class.\n",
+ "\n",
+ "As before, create a grid, a quantity to diffuse, and set boundary conditions."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a7860504",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "grid = RasterModelGrid((100, 200))\n",
+ "\n",
+ "z = grid.add_zeros(\"topographic__elevation\", at=\"node\")\n",
+ "z[(grid.x_of_node > 100) & (grid.y_of_node > 50)] = 10.0\n",
+ "\n",
+ "grid.status_at_node[grid.nodes_at_left_edge] = NodeStatus.CLOSED\n",
+ "grid.status_at_node[grid.nodes_at_right_edge] = NodeStatus.CLOSED\n",
+ "grid.status_at_node[grid.nodes_at_top_edge] = NodeStatus.FIXED_VALUE\n",
+ "grid.status_at_node[grid.nodes_at_bottom_edge] = NodeStatus.CLOSED\n",
+ "\n",
+ "grid.imshow(\"node\", z)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e4d0e36c",
+ "metadata": {},
+ "source": [
+ "Instantiate the `LandscapeDiffuser` and, again, run it over successive time steps."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3f9af856",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6c3ad9ed",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "diffusion_coefficient = 0.5\n",
+ "diffuse = LandscapeDiffuser(grid, diffusion_coefficient=diffusion_coefficient)\n",
+ "dt = diffuse.calc_stable_time_step()\n",
+ "\n",
+ "for _ in trange(10000):\n",
+ " z += diffuse.calc_rate(z) * dt\n",
+ "\n",
+ "grid.imshow(\"node\", z)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0ce7e067",
+ "metadata": {},
+ "source": [
+ "## 3. Create a landlab component class\n",
+ "\n",
+ "We need to add just a few more things to the `LandscapeDiffuser` before we can call it a complete *landlab* component.\n",
+ "\n",
+ "* All components MUST inherit from `landlab.Component`\n",
+ "* All components MUST have a `run_one_step` function\n",
+ "* All components MUST call `super().__init__(grid)` as part of its `__init__` method\n",
+ "* All components MUST have two attributes:\n",
+ " * `_info` is a dictionary that provides information about a component's input and output fields\n",
+ " * `_unit_agnostic` is a boolean that indicates for input and output fields MUST be given in specific units.\n",
+ "\n",
+ "The `run_one_step` method acts on a *landlab* field and can accept an optional *dt* keyword that tells it how long to run for. For the `LandscapeDiffuser` let's have it operate on a field called *topographic__elevation* that's defined at *nodes*.\n",
+ "\n",
+ "The `_info` attribute describes each field the component uses. It is a dictionary where keys are field names and values are descriptions. For example, you can look at the `_info` field of an existing component."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "58ca7ad2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from landlab.components import BedrockLandslider\n",
+ "\n",
+ "BedrockLandslider._info"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fd05c3da",
+ "metadata": {},
+ "source": [
+ "Attributes for each field include,\n",
+ "* *dtype*: the data type of the field (float, int, etc.)\n",
+ "* *intent*: if the field is an input ('in') output ('out') or both ('inout')\n",
+ "* *optional*: boolean to indicate if the field is required\n",
+ "* *unit*: the units of the field as a string\n",
+ "* *mapping*: on what grid element the quantity is defined ('node', 'cell', 'link', etc.)\n",
+ "* *doc*: a short description of the field"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "066ee4d1",
+ "metadata": {},
+ "source": [
+ "Fill in the template below with the *landlab* component requirements."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0b6d3327",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from landlab import Component\n",
+ "\n",
+ "\n",
+ "class LandscapeDiffuser(Component):\n",
+ " _info = {\n",
+ " # Add description of input/output fields here\n",
+ " }\n",
+ "\n",
+ " _unit_agnostic = True or False # Should this component be unit agnostic or not?\n",
+ "\n",
+ " def __init__(self, grid, diffusion_coefficient=1.0):\n",
+ " self._grid = grid\n",
+ " self._diffusion_coefficient = diffusion_coefficient\n",
+ "\n",
+ " super().__init__(grid)\n",
+ "\n",
+ " def calc_rate(self, values):\n",
+ " return calc_diffusion_rate(\n",
+ " self._grid, values, diffusion_coefficient=self._diffusion_coefficient\n",
+ " )\n",
+ "\n",
+ " def calc_stable_time_step(self):\n",
+ " return 0.2 * self._grid.length_of_link.min() ** 2 / self._diffusion_coefficient\n",
+ "\n",
+ " def run_one_step(self, dt=1.0):\n",
+ " \"\"\"Run diffusion on *topographic__elevation*\"\"\"\n",
+ " # Type your code here."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f66da306",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "from landlab import Component\n",
+ "\n",
+ "\n",
+ "class LandscapeDiffuser(Component):\n",
+ " _info = {\n",
+ " \"topographic__elevation\": {\n",
+ " \"dtype\": float,\n",
+ " \"intent\": \"inout\",\n",
+ " \"optional\": False,\n",
+ " \"units\": \"m\",\n",
+ " \"mapping\": \"node\",\n",
+ " \"doc\": \"Diffuse sediment over a landscape\",\n",
+ " },\n",
+ " }\n",
+ "\n",
+ " _unit_agnostic = True\n",
+ "\n",
+ " def __init__(self, grid, diffusion_coefficient=1.0):\n",
+ " self._grid = grid\n",
+ " self._diffusion_coefficient = diffusion_coefficient\n",
+ "\n",
+ " super().__init__(grid)\n",
+ "\n",
+ " def calc_rate(self, values):\n",
+ " return calc_diffusion_rate(\n",
+ " self._grid, values, diffusion_coefficient=self._diffusion_coefficient\n",
+ " )\n",
+ "\n",
+ " def calc_stable_time_step(self):\n",
+ " return 0.2 * self._grid.length_of_link.min()**2 / self._diffusion_coefficient\n",
+ "\n",
+ " def run_one_step(self, dt=1.0):\n",
+ " time_step = self.calc_stable_time_step()\n",
+ " n_steps = int(dt / time_step)\n",
+ " for _ in range(n_steps):\n",
+ " dzdt = self.calc_rate(grid.at_node[\"topographic__elevation\"])\n",
+ " grid.at_node[\"topographic__elevation\"] += dzdt * time_step\n",
+ "\n",
+ " remaining = dt - (n_steps * time_step)\n",
+ " if remaining > 0:\n",
+ " dzdt = self.calc_rate(grid.at_node[\"topographic__elevation\"])\n",
+ " grid.at_node[\"topographic__elevation\"] += dzdt * remaining"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "37a2dfd3",
+ "metadata": {},
+ "source": [
+ "Now run the component as before, this time using the `run_one_step` method and ***adding a topographic__elevation field***."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "aadaf948",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "grid = RasterModelGrid((100, 200))\n",
+ "\n",
+ "z = grid.add_zeros(\"topographic__elevation\", at=\"node\")\n",
+ "z[(grid.x_of_node > 100) & (grid.y_of_node > 50)] = 10.0\n",
+ "\n",
+ "grid.status_at_node[grid.nodes_at_left_edge] = NodeStatus.FIXED_VALUE\n",
+ "grid.status_at_node[grid.nodes_at_right_edge] = NodeStatus.CLOSED\n",
+ "grid.status_at_node[grid.nodes_at_top_edge] = NodeStatus.CLOSED\n",
+ "grid.status_at_node[grid.nodes_at_bottom_edge] = NodeStatus.CLOSED\n",
+ "\n",
+ "grid.imshow(\"node\", grid.at_node[\"topographic__elevation\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6f4db883",
+ "metadata": {},
+ "source": [
+ "Once again, write some code that will run the diffuser over the grid some number of times. Let's say for $1000$ time steps with a $dt=0.2$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4ebd6335",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d61f2340",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "diffuse = LandscapeDiffuser(grid, diffusion_coefficient=0.5)\n",
+ "for _ in trange(1000):\n",
+ " diffuse.run_one_step(dt=0.2)\n",
+ "grid.imshow(\"node\", grid.at_node[\"topographic__elevation\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "eedfcca1",
+ "metadata": {},
+ "source": [
+ "## 4. Create a LandscapeUplifter component\n",
+ "\n",
+ "\n",
+ "Now create a `LandscapeUplifter` component based on a function that uplifts and landscape at a constant rate.\n",
+ "\n",
+ "To be compatible with the `LandscapeDiffuser`, have the uplifter also act on a field called *topographic__elevation* defined at nodes."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5b6159c1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6ff96a43",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "class LandscapeUplifter(Component):\n",
+ " _info = {\n",
+ " \"topographic__elevation\": {\n",
+ " \"dtype\": float,\n",
+ " \"intent\": \"inout\",\n",
+ " \"optional\": False,\n",
+ " \"units\": \"m\",\n",
+ " \"mapping\": \"node\",\n",
+ " \"doc\": \"Elevation of a landspace surface\",\n",
+ " },\n",
+ " }\n",
+ " \n",
+ " _unit_agnostic = True\n",
+ "\n",
+ " def __init__(self, grid, uplift_rate=0.0):\n",
+ " self._uplift_rate = uplift_rate\n",
+ " super().__init__(grid)\n",
+ " \n",
+ " def run_one_step(self, dt=1.0):\n",
+ " self.grid.at_node[\"topographic__elevation\"][self.grid.core_nodes] += self._uplift_rate * dt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10c5eced",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "grid = RasterModelGrid((100, 200))\n",
+ "\n",
+ "z = grid.add_zeros(\"topographic__elevation\", at=\"node\")\n",
+ "\n",
+ "grid.status_at_node[grid.nodes_at_left_edge] = NodeStatus.FIXED_VALUE\n",
+ "grid.status_at_node[grid.nodes_at_right_edge] = NodeStatus.FIXED_VALUE\n",
+ "grid.status_at_node[grid.nodes_at_top_edge] = NodeStatus.CLOSED\n",
+ "grid.status_at_node[grid.nodes_at_bottom_edge] = NodeStatus.CLOSED\n",
+ "\n",
+ "grid.imshow(\"node\", grid.at_node[\"topographic__elevation\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "edc6c969",
+ "metadata": {},
+ "source": [
+ "As with the `LandscapeDiffuser` try writing code to run your `LandscapeUplifter` within a time loop."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1b47edc3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c92f1b5e",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "uplift = LandscapeUplifter(grid, uplift_rate=.001)\n",
+ "\n",
+ "dt = 1000.0\n",
+ "for _ in trange(20):\n",
+ " uplift.run_one_step(dt=dt)\n",
+ "\n",
+ "grid.imshow(\"node\", grid.at_node[\"topographic__elevation\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "424d5487",
+ "metadata": {},
+ "source": [
+ "## 5. Couple LandscapeDiffuser with LandscapeUplifter\n",
+ "\n",
+ "To couple the `LandscapeDiffuser` and the `LandscapeUplifter` we first instantiate both components *with the same grid*. Within the time loop, we call one component's *run_one_step* method, and then the other's.\n",
+ "\n",
+ "Try writing the code to couple these two components."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2778501b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3cb6f3be",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "diffuse = LandscapeDiffuser(grid, diffusion_coefficient=0.2)\n",
+ "uplift = LandscapeUplifter(grid, uplift_rate=.001)\n",
+ "\n",
+ "dt = 1000.0\n",
+ "for _ in trange(20):\n",
+ " uplift.run_one_step(dt=dt)\n",
+ " diffuse.run_one_step(dt=dt)\n",
+ "\n",
+ "grid.imshow(\"node\", grid.at_node[\"topographic__elevation\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b2f97627",
+ "metadata": {},
+ "source": [
+ "## 6. Construct a landscape evolution model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "da7539b6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from landlab.components import FastscapeEroder, FlowAccumulator\n",
+ "from numpy.random import default_rng\n",
+ "\n",
+ "rng = default_rng()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d889ebf3",
+ "metadata": {},
+ "source": [
+ "Create an initial grid that's pretty much flat except for a small amount of noise. If you like, feel free to play around with the size of the grid, the grid spacing, or the boundary conditions."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4cdb947c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "fc35998a",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "grid = RasterModelGrid((100, 200), xy_spacing=0.02)\n",
+ "\n",
+ "z = grid.add_field(\n",
+ " \"topographic__elevation\",\n",
+ " rng.random(grid.number_of_nodes) * 1e-5,\n",
+ " at=\"node\",\n",
+ ")\n",
+ "\n",
+ "grid.status_at_node[grid.nodes_at_left_edge] = NodeStatus.CLOSED\n",
+ "grid.status_at_node[grid.nodes_at_right_edge] = NodeStatus.CLOSED\n",
+ "grid.status_at_node[grid.nodes_at_top_edge] = NodeStatus.FIXED_VALUE\n",
+ "grid.status_at_node[grid.nodes_at_bottom_edge] = NodeStatus.FIXED_VALUE\n",
+ "\n",
+ "grid.imshow(\"node\", grid.at_node[\"topographic__elevation\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "308fe320",
+ "metadata": {},
+ "source": [
+ "Our LEM will couple our `LandscapeDiffuser` and `LandscapeUplifter` along with two new components from the *landlab* component library: `FlowAccumulator` and `FastscapeEroder`. The `FlowAccumulator` will route flow over the landscape and the `FastscapeEroder` will erode and transport sediment over the landscape."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "800dbfd6",
+ "metadata": {},
+ "source": [
+ "Step 1: Instantiate all of the components."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6905a6e6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b6e59f93",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "accumulate = FlowAccumulator(grid)\n",
+ "erode = FastscapeEroder(grid, K_sp=0.3, m_sp=0.5)\n",
+ "diffuse = LandscapeDiffuser(grid, diffusion_coefficient=0.0004)\n",
+ "uplift = LandscapeUplifter(grid, uplift_rate=0.001)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d70d1be7",
+ "metadata": {},
+ "source": [
+ "Step 2: Run each component within a time loop"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "48ea30be",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Type your code here"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ddda11e8",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "dt = 0.5\n",
+ "for _ in trange(100):\n",
+ " uplift.run_one_step(dt=dt)\n",
+ " diffuse.run_one_step(dt=dt)\n",
+ " accumulate.run_one_step()\n",
+ " erode.run_one_step(dt=dt)\n",
+ "\n",
+ "grid.imshow(\"node\", grid.at_node[\"topographic__elevation\"])"
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Tags",
+ "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.11.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/lessons/landlab/landlab/create_a_component.ipynb b/lessons/landlab/landlab/create_a_component.ipynb
index 910fd76..e1372ad 100644
--- a/lessons/landlab/landlab/create_a_component.ipynb
+++ b/lessons/landlab/landlab/create_a_component.ipynb
@@ -2,14 +2,21 @@
"cells": [
{
"cell_type": "markdown",
- "id": "a863305e",
- "metadata": {},
+ "id": "7403e724",
+ "metadata": {
+ "tags": [
+ "toc"
+ ]
+ },
"source": [
- "# Remember to change your kernel to *CSDMS*\n",
- "\n",
- "To run this code, you will need to use the *CSDMS* kernel.\n",
- "\n",
- "**From the *Kernel* menu: *Change kernel* -> *CSDMS***"
+ "# Table of Contents\n",
+ "* [Building a landlab component](#Building-a-landlab-component)\n",
+ " * [1. Linear Diffusion as a Function](#1.-Linear-Diffusion-as-a-Function)\n",
+ " * [2. Represent the function as a class](#2.-Represent-the-function-as-a-class)\n",
+ " * [3. Create a landlab component class](#3.-Create-a-landlab-component-class)\n",
+ " * [4. Create a LandscapeUplifter component](#4.-Create-a-LandscapeUplifter-component)\n",
+ " * [5. Couple LandscapeDiffuser with LandscapeUplifter](#5.-Couple-LandscapeDiffuser-with-LandscapeUplifter)\n",
+ " * [6. Construct a landscape evolution model](#6.-Construct-a-landscape-evolution-model)"
]
},
{
@@ -17,7 +24,7 @@
"id": "571cb775",
"metadata": {},
"source": [
- "# Building a *landlab* component\n",
+ "# Building a landlab component\n",
"\n",
"In this next section we will build two landlab components: one with my help and one on your own. These two components will model physical processes that we will use later on to build a landscape evolution model.\n",
"\n",
@@ -31,7 +38,7 @@
"metadata": {},
"outputs": [],
"source": [
- "from landlab import RasterModelGrid, NodeStatus, LinkStatus\n",
+ "from landlab import LinkStatus, NodeStatus, RasterModelGrid\n",
"from tqdm import trange"
]
},
@@ -65,7 +72,7 @@
" The quantity to diffuse, defined at the grid's nodes.\n",
" diffusion_coefficient : float, optional\n",
" Diffusion coefficent to use for the diffusion.\n",
- " \n",
+ "\n",
" Returns\n",
" -------\n",
" value_at_node\n",
@@ -73,7 +80,7 @@
" \"\"\"\n",
" qs = -diffusion_coefficient * grid.calc_grad_at_link(value_at_node)\n",
" qs[grid.status_at_link != LinkStatus.ACTIVE] = 0.0\n",
- " \n",
+ "\n",
" dzdt = -grid.calc_flux_div_at_node(qs)\n",
" return dzdt"
]
@@ -105,11 +112,15 @@
},
{
"cell_type": "markdown",
- "id": "86b86016",
- "metadata": {},
+ "id": "77187962",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"grid = RasterModelGrid((100, 200))\n",
@@ -124,7 +135,6 @@
"\n",
"grid.imshow(\"node\", z)\n",
"```\n",
- "\n",
" "
]
},
@@ -150,16 +160,20 @@
"outputs": [],
"source": [
"def calc_stable_time_step(grid, diffusion_coefficient):\n",
- " return # Type your code here"
+ " return # Type your code here"
]
},
{
"cell_type": "markdown",
- "id": "def40fd0",
- "metadata": {},
+ "id": "a0dc43b0",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"def calc_stable_time_step(grid, diffusion_coefficient):\n",
@@ -195,11 +209,15 @@
},
{
"cell_type": "markdown",
- "id": "1a7fb0d9",
- "metadata": {},
+ "id": "85edf236",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"diffusion_coefficient = 1.0\n",
@@ -212,7 +230,6 @@
"\n",
"grid.imshow(\"node\", z)\n",
"```\n",
- " \n",
" "
]
},
@@ -243,11 +260,15 @@
},
{
"cell_type": "markdown",
- "id": "577d55f6",
- "metadata": {},
+ "id": "e70ccb2e",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"class LandscapeDiffuser:\n",
@@ -262,7 +283,6 @@
"\n",
" def calc_stable_time_step(self):\n",
" return 0.2 * self._grid.length_of_link.min()**2 / self._diffusion_coefficient\n",
- "\n",
"```\n",
" "
]
@@ -317,11 +337,15 @@
},
{
"cell_type": "markdown",
- "id": "0ec12c86",
- "metadata": {},
+ "id": "6c3ad9ed",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"diffusion_coefficient = 0.5\n",
@@ -332,7 +356,6 @@
" z += diffuse.calc_rate(z) * dt\n",
"\n",
"grid.imshow(\"node\", z)\n",
- "\n",
"```\n",
" "
]
@@ -342,7 +365,7 @@
"id": "0ce7e067",
"metadata": {},
"source": [
- "# 3. Create a landlab component class\n",
+ "## 3. Create a landlab component class\n",
"\n",
"We need to add just a few more things to the `LandscapeDiffuser` before we can call it a complete *landlab* component.\n",
"\n",
@@ -403,7 +426,6 @@
"\n",
"\n",
"class LandscapeDiffuser(Component):\n",
- " \n",
" _info = {\n",
" # Add description of input/output fields here\n",
" }\n",
@@ -413,16 +435,16 @@
" def __init__(self, grid, diffusion_coefficient=1.0):\n",
" self._grid = grid\n",
" self._diffusion_coefficient = diffusion_coefficient\n",
- " \n",
+ "\n",
" super().__init__(grid)\n",
- " \n",
+ "\n",
" def calc_rate(self, values):\n",
" return calc_diffusion_rate(\n",
" self._grid, values, diffusion_coefficient=self._diffusion_coefficient\n",
" )\n",
"\n",
" def calc_stable_time_step(self):\n",
- " return 0.2 * self._grid.length_of_link.min()**2 / self._diffusion_coefficient\n",
+ " return 0.2 * self._grid.length_of_link.min() ** 2 / self._diffusion_coefficient\n",
"\n",
" def run_one_step(self, dt=1.0):\n",
" \"\"\"Run diffusion on *topographic__elevation*\"\"\"\n",
@@ -431,12 +453,15 @@
},
{
"cell_type": "markdown",
- "id": "ed788e3a",
- "metadata": {},
+ "id": "f66da306",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
- "\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"from landlab import Component\n",
@@ -533,12 +558,15 @@
},
{
"cell_type": "markdown",
- "id": "c4c4f360",
- "metadata": {},
+ "id": "d61f2340",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
- "\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"diffuse = LandscapeDiffuser(grid, diffusion_coefficient=0.5)\n",
@@ -554,7 +582,7 @@
"id": "eedfcca1",
"metadata": {},
"source": [
- "## 4. Create an `LandscapeUplifter` component\n",
+ "## 4. Create a LandscapeUplifter component\n",
"\n",
"\n",
"Now create a `LandscapeUplifter` component based on a function that uplifts and landscape at a constant rate.\n",
@@ -574,11 +602,15 @@
},
{
"cell_type": "markdown",
- "id": "639587c7",
- "metadata": {},
+ "id": "6ff96a43",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"class LandscapeUplifter(Component):\n",
@@ -602,7 +634,6 @@
" def run_one_step(self, dt=1.0):\n",
" self.grid.at_node[\"topographic__elevation\"][self.grid.core_nodes] += self._uplift_rate * dt\n",
"```\n",
- "\n",
" "
]
},
@@ -645,11 +676,15 @@
},
{
"cell_type": "markdown",
- "id": "d797aaf1",
- "metadata": {},
+ "id": "c92f1b5e",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"uplift = LandscapeUplifter(grid, uplift_rate=.001)\n",
@@ -660,7 +695,6 @@
"\n",
"grid.imshow(\"node\", grid.at_node[\"topographic__elevation\"])\n",
"```\n",
- "\n",
" "
]
},
@@ -669,7 +703,7 @@
"id": "424d5487",
"metadata": {},
"source": [
- "## 5. Couple `LandscapeDiffuser` with `LandscapeUplifter`\n",
+ "## 5. Couple LandscapeDiffuser with LandscapeUplifter\n",
"\n",
"To couple the `LandscapeDiffuser` and the `LandscapeUplifter` we first instantiate both components *with the same grid*. Within the time loop, we call one component's *run_one_step* method, and then the other's.\n",
"\n",
@@ -688,12 +722,15 @@
},
{
"cell_type": "markdown",
- "id": "b8c087a4",
- "metadata": {},
+ "id": "3cb6f3be",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
- "\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"diffuse = LandscapeDiffuser(grid, diffusion_coefficient=0.2)\n",
@@ -724,7 +761,7 @@
"metadata": {},
"outputs": [],
"source": [
- "from landlab.components import FlowAccumulator, FastscapeEroder\n",
+ "from landlab.components import FastscapeEroder, FlowAccumulator\n",
"from numpy.random import default_rng\n",
"\n",
"rng = default_rng()"
@@ -750,11 +787,15 @@
},
{
"cell_type": "markdown",
- "id": "fdaa910b",
- "metadata": {},
+ "id": "fc35998a",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"grid = RasterModelGrid((100, 200), xy_spacing=0.02)\n",
@@ -803,12 +844,15 @@
},
{
"cell_type": "markdown",
- "id": "12e8e254",
- "metadata": {},
+ "id": "b6e59f93",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
- "\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"accumulate = FlowAccumulator(grid)\n",
@@ -816,7 +860,6 @@
"diffuse = LandscapeDiffuser(grid, diffusion_coefficient=0.0004)\n",
"uplift = LandscapeUplifter(grid, uplift_rate=0.001)\n",
"```\n",
- "\n",
" "
]
},
@@ -840,11 +883,15 @@
},
{
"cell_type": "markdown",
- "id": "a87ae604",
- "metadata": {},
+ "id": "ddda11e8",
+ "metadata": {
+ "tags": [
+ "solution"
+ ]
+ },
"source": [
"\n",
- "(click to see solution)
\n",
+ " ๐ click to see solution
\n",
"\n",
"```python\n",
"dt = 0.5\n",
@@ -856,20 +903,12 @@
"\n",
"grid.imshow(\"node\", grid.at_node[\"topographic__elevation\"])\n",
"```\n",
- " \n",
" "
]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "9b928951",
- "metadata": {},
- "outputs": [],
- "source": []
}
],
"metadata": {
+ "celltoolbar": "Tags",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
@@ -885,7 +924,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.8.13"
+ "version": "3.11.0"
}
},
"nbformat": 4,
diff --git a/noxfile.py b/noxfile.py
index 960fa05..927f95c 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -54,7 +54,7 @@ def insert_toc(session: nox.Session) -> None:
notebooks = (
"lessons/landlab/landlab/bedrock_landslides_on_dems.ipynb",
- "lessons/landlab/landlab/create_a_component.ipynb",
+ "lessons/landlab/landlab/create_a_component-solution.ipynb",
"lessons/landlab/landlab/fault-scarp.ipynb",
"lessons/landlab/landlab/intro-to-grids-solution.ipynb",
"lessons/landlab/landlab/intro_part_for_component_clinic.ipynb",
@@ -87,6 +87,10 @@ def hide_solutions(session: nox.Session) -> None:
"lessons/landlab/landlab/landlab-fault-scarp-for-espin-solution.ipynb",
"lessons/landlab/landlab/landlab-fault-scarp-for-espin.ipynb",
),
+ (
+ "lessons/landlab/landlab/create_a_component-solution.ipynb",
+ "lessons/landlab/landlab/create_a_component.ipynb",
+ ),
)
for solution, lesson in solution_notebooks: