{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Using the Landlab NetworkSedimentTransporter component\n", "\n", "
\n", "For more Landlab tutorials, click here: https://landlab.readthedocs.io/en/latest/user_guide/tutorials.html\n", "
\n", "\n", "This tutorial illustrates how to model the transport of coarse sediment through a synthetic river network using the NetworkSedimentTransporter Landlab component. \n", "\n", "For an equivalent tutorial demonstrating initialization of the NetworkSedimentTransporter with a *shapefile river network*, [click here](../network_sediment_transporter/network_sediment_transporter_shapefile_network.ipynb).\n", "\n", "In this example we will: \n", "- create a synthetic Landlab grid to represent a river network\n", "- create sediment \"parcels\" that will transport through the river network, represented as items in a Landlab DataRecord\n", "- run the component\n", "- plot the results of the model run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import the necessary libraries, plus a bit of magic so that we can plot within this notebook:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import warnings\n", "warnings.filterwarnings('ignore')\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from landlab.components import FlowDirectorSteepest, NetworkSedimentTransporter\n", "from landlab.data_record import DataRecord\n", "from landlab.grid.network import NetworkModelGrid\n", "from landlab.plot import graph\n", "from landlab.plot import plot_network_and_parcels\n", "\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Create the river network model grid\n", "\n", "First, we need to create a Landlab NetworkModelGrid to represent the river network. Each link on the grid represents a reach of river. Each node represents a break between reaches. All tributary junctions must be associated with grid nodes. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y_of_node = (0, 100, 200, 200, 300, 400, 400, 125)\n", "x_of_node = (0, 0, 100, -50, -100, 50, -150, -100)\n", "\n", "nodes_at_link = ((1, 0), (2, 1), (1, 7), (3, 1), (3, 4), (4, 5), (4, 6))\n", "\n", "grid = NetworkModelGrid((y_of_node, x_of_node), nodes_at_link)\n", "\n", "plt.figure(0)\n", "graph.plot_graph(grid, at=\"node,link\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our network consists of seven links between 8 nodes. X and Y, above, represent the plan-view coordinates of the node locations. Notes_at_link describes the node indices that are connedted by each link. For example, link 2 connects node 1 and node 7. \n", "\n", "Next, we need to populate the grid with the relevant topographic information: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "grid.at_node[\"topographic__elevation\"] = [0.0, 0.08, 0.25, 0.15, 0.25, 0.4, 0.8, 0.8]\n", "grid.at_node[\"bedrock__elevation\"] = [0.0, 0.08, 0.25, 0.15, 0.25, 0.4, 0.8, 0.8]\n", "\n", "grid.at_link[\"flow_depth\"] = 2.5 * np.ones(grid.number_of_links) # m\n", "grid.at_link[\"reach_length\"] = 200*np.ones(grid.number_of_links) # m\n", "grid.at_link[\"channel_width\"] = 1*np.ones(grid.number_of_links) # m" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We must distinguish between topographic elevation (the top surface of the bed sediment) and bedrock elevation (the surface of the river in the absence of modeled sediment). \n", "\n", "Note that \"reach_length\" is defined by the user, rather than calculated as the minimum distance between nodes. This accounts for channel sinuosity. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Create sediment 'parcels' in a DataRecord\n", "\n", "We represent sediment in the network as discrete parcels (or packages) of grains of uniform size and characteristics. Each parcel is tracked through the network grid according to sediment transport and stratigraphic constraints. \n", "\n", "Parcels are tracked using the Landlab [DataRecord](../data_record/DataRecord_tutorial.ipynb).\n", "\n", "First, let's create arrays with all of the essential sediment parcel variables: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# element_id is the link on which the parcel begins. \n", "element_id = np.repeat(np.arange(grid.number_of_links),30)\n", "element_id = np.expand_dims(element_id, axis=1)\n", "\n", "volume = 0.05*np.ones(np.shape(element_id)) # (m3)\n", "active_layer = np.ones(np.shape(element_id)) # 1= active, 0 = inactive\n", "density = 2650 * np.ones(np.size(element_id)) # (kg/m3)\n", "abrasion_rate = 0 * np.ones(np.size(element_id)) # (mass loss /m)\n", "\n", "# Lognormal GSD\n", "medianD = 0.085 # m\n", "mu = np.log(medianD)\n", "sigma = np.log(2) #assume that D84 = sigma*D50\n", "np.random.seed(0)\n", "D = np.random.lognormal(\n", " mu,\n", " sigma,\n", " np.shape(element_id)\n", ") # (m) the diameter of grains in each parcel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to track sediment motion, we classify parcels as either active (representing mobile surface sediment) or inactive (immobile subsurface) during each timestep. The active parcels are the most recent parcels to arrive in the link. During a timestep, active parcels are transported downstream (increasing their `location_in_link`, which ranges from 0 to 1) according to a sediment transport formula. \n", "\n", "We begin by assigning each parcel an arbitrary (and small) arrival time and location in the link. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "time_arrival_in_link = np.random.rand(np.size(element_id), 1) \n", "location_in_link = np.random.rand(np.size(element_id), 1) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition to the required parcel attributes listed above, you can designate optional parcel characteristics, depending on your needs. For example: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lithology = [\"quartzite\"] * np.size(element_id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now collect the arrays into a dictionary of variables, some of which will be tracked through time (`[\"item_id\", \"time\"]`), and others of which will remain constant through time :" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "variables = {\n", " \"abrasion_rate\": ([\"item_id\"], abrasion_rate),\n", " \"density\": ([\"item_id\"], density),\n", " \"lithology\": ([\"item_id\"], lithology),\n", " \"time_arrival_in_link\": ([\"item_id\", \"time\"], time_arrival_in_link),\n", " \"active_layer\": ([\"item_id\", \"time\"], active_layer),\n", " \"location_in_link\": ([\"item_id\", \"time\"], location_in_link),\n", " \"D\": ([\"item_id\", \"time\"], D),\n", " \"volume\": ([\"item_id\", \"time\"], volume)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With all of the required attributes collected, we can create the parcels DataRecord. Often, parcels will eventually transport off of the downstream-most link. To track these parcels, we have designated a \"`dummy_element`\" here, which has index value `-2`. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "items = {\"grid_element\": \"link\", \"element_id\": element_id}\n", "\n", "parcels = DataRecord(\n", " grid,\n", " items=items,\n", " time=[0.0],\n", " data_vars=variables,\n", " dummy_elements={\"link\": [NetworkSedimentTransporter.OUT_OF_NETWORK]},\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Run the NetworkSedimentTransporter\n", "\n", "With the parcels and grid set up, we can move on to setting up the model. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "timesteps = 10 # total number of timesteps\n", "dt = 60 * 60 * 24 *1 # length of timestep (seconds) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before running the NST, we need to determine flow direction on the grid (upstream and downstream for each link). To do so, we initalize and run a Landlab flow director component: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fd = FlowDirectorSteepest(grid, \"topographic__elevation\")\n", "fd.run_one_step()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, we initialize the network sediment transporter: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "nst = NetworkSedimentTransporter( \n", " grid,\n", " parcels,\n", " fd,\n", " bed_porosity=0.3,\n", " g=9.81,\n", " fluid_density=1000,\n", " transport_method=\"WilcockCrowe\",\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we are ready to run the model forward in time: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for t in range(0, (timesteps * dt), dt):\n", " nst.run_one_step(dt) \n", " print(\"Model time: \", t/dt, \"timesteps passed\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Plot the model results\n", "\n", "\n", "There are landlab plotting tools specific to the `NetworkSedimentTransporter`. In particular, `plot_network_and_parcels` creates a plan-view map of the network and parcels (represented as dots along the network). We can color both the parcels and the links by attributes. \n", "\n", "Here, we demonstrate one example use of `plot_network_and_parcels`, which creates a plan-view map of the network and parcels (represented as dots along the network). We can color both the parcels and the links by attributes. For a thorough tutorial on the plotting tools, see [this notebook](../network_sediment_transporter/network_plotting_examples.ipynb).\n", "\n", "Below, each link (represented as a line) is colored by the total volume of sediment on the link. Each parcel is colored by the parcel grain size. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plot_network_and_parcels(\n", " grid, parcels, \n", " parcel_time_index=0, \n", " parcel_color_attribute=\"D\",\n", " link_attribute=\"sediment_total_volume\", \n", " parcel_size=10, \n", " parcel_alpha=1.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition, the results of the NST can be visualized by directly accessing information about the grid, the parcels, and by accessing variables stored after the run of NST. \n", "\n", "As a simple example, we can plot the total transport distance of all parcels through the model run as a function of parcel diameter. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "plt.loglog(parcels.dataset.D[:,-1],\n", " nst._distance_traveled_cumulative,\n", " '.'\n", " )\n", "plt.xlabel('Parcel grain size (m)')\n", "plt.ylabel('Cumulative parcel travel distance')\n", "\n", "# Note: some of the smallest grain travel distances can exceed the length of the \n", "# grid by \"overshooting\" during a single timestep of high transport rate\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Great, smaller parcels transport farther! " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Click here for more Landlab tutorials" ] } ], "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.3" } }, "nbformat": 4, "nbformat_minor": 2 }