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: