# Source code for landlab.components.lateral_erosion.lateral_erosion

```
"""Grid-based simulation of lateral erosion by channels in a drainage network.
ALangston
"""
import numpy as np
from landlab import Component, RasterModelGrid
from landlab.components.flow_accum import FlowAccumulator
from .node_finder import node_finder
# Hard coded constants
cfl_cond = 0.3 # CFL timestep condition
wid_coeff = 0.4 # coefficient for calculating channel width
wid_exp = 0.35 # exponent for calculating channel width
[docs]class LateralEroder(Component):
"""Laterally erode neighbor node through fluvial erosion.
Landlab component that finds a neighbor node to laterally erode and
calculates lateral erosion.
See the publication:
Langston, A.L., Tucker, G.T.: Developing and exploring a theory for the
lateral erosion of bedrock channels for use in landscape evolution models.
Earth Surface Dynamics, 6, 1-27,
`https://doi.org/10.5194/esurf-6-1-2018 <https://www.earth-surf-dynam.net/6/1/2018/>`_
Examples
--------
>>> import numpy as np
>>> from landlab import RasterModelGrid
>>> from landlab.components import FlowAccumulator, LateralEroder
>>> np.random.seed(2010)
Define grid and initial topography
* 5x4 grid with baselevel in the lower left corner
* All other boundary nodes closed
* Initial topography is plane tilted up to the upper right with noise
>>> mg = RasterModelGrid((5, 4), xy_spacing=10.0)
>>> mg.set_status_at_node_on_edges(
... right=mg.BC_NODE_IS_CLOSED,
... top=mg.BC_NODE_IS_CLOSED,
... left=mg.BC_NODE_IS_CLOSED,
... bottom=mg.BC_NODE_IS_CLOSED,
... )
>>> mg.status_at_node[1] = mg.BC_NODE_IS_FIXED_VALUE
>>> mg.add_zeros("topographic__elevation", at="node")
array([ 0., 0., 0., 0.,
0., 0., 0., 0.,
0., 0., 0., 0.,
0., 0., 0., 0.,
0., 0., 0., 0.])
>>> rand_noise=np.array(
... [
... 0.00436992, 0.03225985, 0.03107455, 0.00461312,
... 0.03771756, 0.02491226, 0.09613959, 0.07792969,
... 0.08707156, 0.03080568, 0.01242658, 0.08827382,
... 0.04475065, 0.07391732, 0.08221057, 0.02909259,
... 0.03499337, 0.09423741, 0.01883171, 0.09967794,
... ]
... )
>>> mg.at_node["topographic__elevation"] += (
... mg.node_y / 10. + mg.node_x / 10. + rand_noise
... )
>>> U = 0.001
>>> dt = 100
Instantiate flow accumulation and lateral eroder and run each for one step
>>> fa = FlowAccumulator(
... mg,
... surface="topographic__elevation",
... flow_director="FlowDirectorD8",
... runoff_rate=None,
... depression_finder=None,
... )
>>> latero = LateralEroder(mg, latero_mech="UC", Kv=0.001, Kl_ratio=1.5)
Run one step of flow accumulation and lateral erosion to get the dzlat array
needed for the next part of the test.
>>> fa.run_one_step()
>>> mg, dzlat = latero.run_one_step(dt)
Evolve the landscape until the first occurence of lateral erosion. Save arrays
volume of lateral erosion and topographic elevation before and after the first
occurence of lateral erosion
>>> while min(dzlat) == 0.0:
... oldlatvol = mg.at_node["volume__lateral_erosion"].copy()
... oldelev = mg.at_node["topographic__elevation"].copy()
... fa.run_one_step()
... mg, dzlat = latero.run_one_step(dt)
... newlatvol = mg.at_node["volume__lateral_erosion"]
... newelev = mg.at_node["topographic__elevation"]
... mg.at_node["topographic__elevation"][mg.core_nodes] += U * dt
Before lateral erosion occurs, *volume__lateral_erosion* has values at
nodes 6 and 10.
>>> np.around(oldlatvol, decimals=0)
array([ 0., 0., 0., 0.,
0., 0., 79., 0.,
0., 0., 24., 0.,
0., 0., 0., 0.,
0., 0., 0., 0.])
After lateral erosion occurs at node 6, *volume__lateral_erosion* is reset to 0
>>> np.around(newlatvol, decimals=0)
array([ 0., 0., 0., 0.,
0., 0., 0., 0.,
0., 0., 24., 0.,
0., 0., 0., 0.,
0., 0., 0., 0.])
After lateral erosion at node 6, elevation at node 6 is reduced by -1.41
(the elevation change stored in dzlat[6]). It is also provided as the
at-node grid field *lateral_erosion__depth_increment*.
>>> np.around(oldelev, decimals=2)
array([ 0. , 1.03, 2.03, 3. ,
1.04, 1.77, 2.45, 4.08,
2.09, 2.65, 3.18, 5.09,
3.04, 3.65, 4.07, 6.03,
4.03, 5.09, 6.02, 7.1 ])
>>> np.around(newelev, decimals=2)
array([ 0. , 1.03, 2.03, 3. ,
1.04, 1.77, 1.03, 4.08,
2.09, 2.65, 3.18, 5.09,
3.04, 3.65, 4.07, 6.03,
4.03, 5.09, 6.02, 7.1 ])
>>> np.around(dzlat, decimals=2)
array([ 0. , 0. , 0. , 0. ,
0. , 0. , -1.41, 0. ,
0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. ])
References
----------
**Required Software Citation(s) Specific to this Component**
Langston, A., Tucker, G. (2018). Developing and exploring a theory for the
lateral erosion of bedrock channels for use in landscape evolution models.
Earth Surface Dynamics 6(1), 1--27.
https://dx.doi.org/10.5194/esurf-6-1-2018
**Additional References**
None Listed
"""
_name = "LateralEroder"
_unit_agnostic = False
_cite_as = """
@article{langston2018developing,
author = {Langston, A. L. and Tucker, G. E.},
title = {{Developing and exploring a theory for the lateral erosion of
bedrock channels for use in landscape evolution models}},
doi = {10.5194/esurf-6-1-2018},
pages = {1---27},
number = {1},
volume = {6},
journal = {Earth Surface Dynamics},
year = {2018}
}
"""
_info = {
"drainage_area": {
"dtype": float,
"intent": "in",
"optional": False,
"units": "m**2",
"mapping": "node",
"doc": "Upstream accumulated surface area contributing to the node's discharge",
},
"flow__receiver_node": {
"dtype": int,
"intent": "in",
"optional": False,
"units": "-",
"mapping": "node",
"doc": "Node array of receivers (node that receives flow from current node)",
},
"flow__upstream_node_order": {
"dtype": int,
"intent": "in",
"optional": False,
"units": "-",
"mapping": "node",
"doc": "Node array containing downstream-to-upstream ordered list of node IDs",
},
"lateral_erosion__depth_increment": {
"dtype": float,
"intent": "out",
"optional": False,
"units": "m",
"mapping": "node",
"doc": "Change in elevation at each node from lateral erosion during time step",
},
"sediment__influx": {
"dtype": float,
"intent": "out",
"optional": False,
"units": "m3/y",
"mapping": "node",
"doc": "Sediment flux (volume per unit time of sediment entering each node)",
},
"topographic__elevation": {
"dtype": float,
"intent": "inout",
"optional": False,
"units": "m",
"mapping": "node",
"doc": "Land surface topographic elevation",
},
"topographic__steepest_slope": {
"dtype": float,
"intent": "in",
"optional": False,
"units": "-",
"mapping": "node",
"doc": "The steepest *downhill* slope",
},
"volume__lateral_erosion": {
"dtype": float,
"intent": "out",
"optional": False,
"units": "m3",
"mapping": "node",
"doc": "Array tracking volume eroded at each node from lateral erosion",
},
}
[docs] def __init__(
self,
grid,
latero_mech="UC",
alph=0.8,
Kv=0.001,
Kl_ratio=1.0,
solver="basic",
inlet_on=False,
inlet_node=None,
inlet_area=None,
qsinlet=0.0,
flow_accumulator=None,
):
"""
Parameters
----------
grid : ModelGrid
A Landlab square cell raster grid object
latero_mech : string, optional (defaults to UC)
Lateral erosion algorithm, choices are "UC" for undercutting-slump
model and "TB" for total block erosion
alph : float, optional (defaults to 0.8)
Parameter describing potential for deposition, dimensionless
Kv : float, node array, or field name
Bedrock erodibility in vertical direction, 1/years
Kl_ratio : float, optional (defaults to 1.0)
Ratio of lateral to vertical bedrock erodibility, dimensionless
solver : string
Solver options:
(1) 'basic' (default): explicit forward-time extrapolation.
Simple but will become unstable if time step is too large or
if bedrock erodibility is vry high.
(2) 'adaptive': subdivides global time step as needed to
prevent slopes from reversing.
inlet_node : integer, optional
Node location of inlet (source of water and sediment)
inlet_area : float, optional
Drainage area at inlet node, must be specified if inlet node is "on", m^2
qsinlet : float, optional
Sediment flux supplied at inlet, optional. m3/year
flow_accumulator : Instantiated Landlab FlowAccumulator, optional
When solver is set to "adaptive", then a valid Landlab FlowAccumulator
must be passed. It will be run within sub-timesteps in order to update
the flow directions and drainage area.
"""
super().__init__(grid)
assert isinstance(
grid, RasterModelGrid
), "LateralEroder requires a sqare raster grid."
if "flow__receiver_node" in grid.at_node and grid.at_node[
"flow__receiver_node"
].size != grid.size("node"):
raise NotImplementedError(
"A route-to-multiple flow director has been "
"run on this grid. The LateralEroder is not currently "
"compatible with route-to-multiple methods. Use a route-to-"
"one flow director."
)
if solver not in ("basic", "adaptive"):
raise ValueError(
"value for solver not understood ({val} not one of {valid})".format(
val=solver, valid=", ".join(("basic", "adaptive"))
)
)
if latero_mech not in ("UC", "TB"):
raise ValueError(
"value for latero_mech not understood ({val} not one of {valid})".format(
val=latero_mech, valid=", ".join(("UC", "TB"))
)
)
if inlet_on and (inlet_node is None or inlet_area is None):
raise ValueError(
"inlet_on is True, but no inlet_node or inlet_area is provided."
)
if Kv is None:
raise ValueError(
"Kv must be set as a float, node array, or field name. It was None."
)
if solver == "adaptive":
if not isinstance(flow_accumulator, FlowAccumulator):
raise ValueError(
"When the adaptive solver is used, a valid "
"FlowAccumulator must be passed on "
"instantiation."
)
self._flow_accumulator = flow_accumulator
# Create fields needed for this component if not already existing
if "volume__lateral_erosion" not in grid.at_node:
grid.add_zeros("volume__lateral_erosion", at="node")
self._vol_lat = grid.at_node["volume__lateral_erosion"]
if "sediment__influx" not in grid.at_node:
grid.add_zeros("sediment__influx", at="node")
self._qs_in = grid.at_node["sediment__influx"]
if "lateral_erosion__depth_increment" not in grid.at_node:
grid.add_zeros("lateral_erosion__depth_increment", at="node")
self._dzlat = grid.at_node["lateral_erosion__depth_increment"]
# for backward compatibility (remove in version 3.0.0+)
grid.at_node["sediment__flux"] = grid.at_node["sediment__influx"]
# you can specify the type of lateral erosion model you want to use.
# But if you don't the default is the undercutting-slump model
if latero_mech == "TB":
self._TB = True
self._UC = False
else:
self._UC = True
self._TB = False
# option use adaptive time stepping. Default is fixed dt supplied by user
if solver == "basic":
self.run_one_step = self.run_one_step_basic
elif solver == "adaptive":
self.run_one_step = self.run_one_step_adaptive
self._alph = alph
self._Kv = Kv # can be overwritten with spatially variable
self._Klr = float(Kl_ratio) # default ratio of Kv/Kl is 1. Can be overwritten
self._dzdt = grid.add_zeros(
"dzdt", at="node", clobber=True
) # elevation change rate (M/Y)
# optional inputs
self._inlet_on = inlet_on
if inlet_on:
self._inlet_node = inlet_node
self._inlet_area = inlet_area
# runoff is an array with values of the area of each node (dx**2)
runoffinlet = np.full(grid.number_of_nodes, grid.dx**2, dtype=float)
# Change the runoff at the inlet node to node area + inlet node
runoffinlet[inlet_node] += inlet_area
grid.add_field("water__unit_flux_in", runoffinlet, at="node", clobber=True)
# set qsinlet at inlet node. This doesn't have to be provided, defaults
# to 0.
self._qsinlet = qsinlet
self._qs_in[self._inlet_node] = self._qsinlet
# handling Kv for floats (inwhich case it populates an array N_nodes long) or
# for arrays of Kv. Checks that length of Kv array is good.
self._Kv = np.ones(self._grid.number_of_nodes, dtype=float) * Kv
[docs] def run_one_step_basic(self, dt=1.0):
"""Calculate vertical and lateral erosion for a time period 'dt'.
Parameters
----------
dt : float
Model timestep [T]
"""
Klr = self._Klr
grid = self._grid
UC = self._UC
TB = self._TB
inlet_on = self._inlet_on # this is a true/false flag
Kv = self._Kv
qs_in = self._qs_in
dzdt = self._dzdt
alph = self._alph
vol_lat = self._grid.at_node["volume__lateral_erosion"]
kw = 10.0
F = 0.02
# May 2, runoff calculated below (in m/s) is important for calculating
# discharge and water depth correctly. renamed runoffms to prevent
# confusion with other uses of runoff
runoffms = (Klr * F / kw) ** 2
# Kl is calculated from ratio of lateral to vertical K parameters
Kl = Kv * Klr
z = grid.at_node["topographic__elevation"]
# clear qsin for next loop
qs_in = grid.add_zeros("sediment__influx", at="node", clobber=True)
qs = grid.add_zeros("qs", at="node", clobber=True)
lat_nodes = np.zeros(grid.number_of_nodes, dtype=int)
dzver = np.zeros(grid.number_of_nodes)
vol_lat_dt = np.zeros(grid.number_of_nodes)
# dz_lat needs to be reset. Otherwise, once a lateral node
# erode's once, it will continue eroding at every subsequent
# time setp. If you want to track all lateral erosion, create
# another attribute, or add self.dzlat to itself after each time step.
self._dzlat.fill(0.0)
if inlet_on is True:
inlet_node = self._inlet_node
qsinlet = self._qsinlet
qs_in[inlet_node] = qsinlet
q = grid.at_node["surface_water__discharge"]
da = q / grid.dx**2
# if inlet flag is not on, proceed as normal.
else:
da = grid.at_node["drainage_area"]
# flow__upstream_node_order is node array contianing downstream to
# upstream order list of node ids
s = grid.at_node["flow__upstream_node_order"]
max_slopes = grid.at_node["topographic__steepest_slope"]
flowdirs = grid.at_node["flow__receiver_node"]
# make a list l, where node status is interior (signified by label 0) in s
interior_s = s[np.where(grid.status_at_node[s] == 0)[0]]
dwnst_nodes = interior_s.copy()
# reverse list so we go from upstream to down stream
dwnst_nodes = dwnst_nodes[::-1]
max_slopes[:] = max_slopes.clip(0)
for i in dwnst_nodes:
# calc deposition and erosion
dep = alph * qs_in[i] / da[i]
ero = -Kv[i] * da[i] ** (0.5) * max_slopes[i]
dzver[i] = dep + ero
# potential lateral erosion initially set to 0
petlat = 0.0
# water depth in meters, needed for lateral erosion calc
wd = wid_coeff * (da[i] * runoffms) ** wid_exp
# Choose lateral node for node i. If node i flows downstream, continue.
# if node i is the first cell at the top of the drainage network, don't go
# into this loop because in this case, node i won't have a "donor" node
if i in flowdirs:
# node_finder picks the lateral node to erode based on angle
# between segments between three nodes
[lat_node, inv_rad_curv] = node_finder(grid, i, flowdirs, da)
# node_finder returns the lateral node ID and the radius of curvature
lat_nodes[i] = lat_node
# if the lateral node is not 0 or -1 continue. lateral node may be
# 0 or -1 if a boundary node was chosen as a lateral node. then
# radius of curavature is also 0 so there is no lateral erosion
if lat_node > 0 and z[lat_node] > z[i]:
# if the elevation of the lateral node is higher than primary node,
# calculate a new potential lateral erosion (L/T), which is negative
petlat = -Kl[i] * da[i] * max_slopes[i] * inv_rad_curv
# the calculated potential lateral erosion is mutiplied by
# the length of the node and the bank height, then added
# to an array, vol_lat_dt, for volume eroded laterally
# *per timestep* at each node. This vol_lat_dt is reset to zero for
# each timestep loop. vol_lat_dt is added to itself in case
# more than one primary nodes are laterally eroding this lat_node
# volume of lateral erosion per timestep
vol_lat_dt[lat_node] += abs(petlat) * grid.dx * wd
# send sediment downstream. sediment eroded from vertical incision
# and lateral erosion is sent downstream
# print("debug before 406")
qs_in[flowdirs[i]] += (
qs_in[i] - (dzver[i] * grid.dx**2) - (petlat * grid.dx * wd)
) # qsin to next node
qs[:] = qs_in - (dzver * grid.dx**2)
dzdt[:] = dzver * dt
vol_lat[:] += vol_lat_dt * dt
# this loop determines if enough lateral erosion has happened to change
# the height of the neighbor node.
for i in dwnst_nodes:
lat_node = lat_nodes[i]
wd = wid_coeff * (da[i] * runoffms) ** wid_exp
# greater than zero now bc inactive neighbors are value -1
if lat_node > 0 and z[lat_node] > z[i]:
# vol_diff is the volume that must be eroded from lat_node so that its
# elevation is the same as node downstream of primary node
# UC model: this would represent undercutting (the water height at
# node i), slumping, and instant removal.
if UC == 1:
voldiff = (z[i] + wd - z[flowdirs[i]]) * grid.dx**2
# TB model: entire lat node must be eroded before lateral erosion
# occurs
if TB == 1:
voldiff = (z[lat_node] - z[flowdirs[i]]) * grid.dx**2
# if the total volume eroded from lat_node is greater than the volume
# needed to be removed to make node equal elevation,
# then instantaneously remove this height from lat node. already has
# timestep in it
if vol_lat[lat_node] >= voldiff:
self._dzlat[lat_node] = z[flowdirs[i]] - z[lat_node] # -0.001
# after the lateral node is eroded, reset its volume eroded to
# zero
vol_lat[lat_node] = 0.0
# combine vertical and lateral erosion.
dz = dzdt + self._dzlat
# change height of landscape
z[:] += dz
return grid, self._dzlat
[docs] def run_one_step_adaptive(self, dt=1.0):
"""Run time step with adaptive time stepping to prevent slope
flattening."""
Klr = self._Klr
grid = self._grid
UC = self._UC
TB = self._TB
inlet_on = self._inlet_on # this is a true/false flag
Kv = self._Kv
qs_in = self._qs_in
dzdt = self._dzdt
alph = self._alph
vol_lat = self._grid.at_node["volume__lateral_erosion"]
kw = 10.0
F = 0.02
runoffms = (Klr * F / kw) ** 2
Kl = Kv * Klr
z = grid.at_node["topographic__elevation"]
# clear qsin for next loop
qs_in = grid.add_zeros("sediment__influx", at="node", clobber=True)
qs = grid.add_zeros("qs", at="node", clobber=True)
lat_nodes = np.zeros(grid.number_of_nodes, dtype=int)
dzver = np.zeros(grid.number_of_nodes)
vol_lat_dt = np.zeros(grid.number_of_nodes)
# dz_lat needs to be reset. Otherwise, once a lateral node erode's
# once, it will continue eroding at every subsequent time setp.
# If you want to track all lateral erosion, create another attribute,
# or add self.dzlat to itself after each time step.
self._dzlat.fill(0.0)
if inlet_on is True:
# define inlet_node
inlet_node = self._inlet_node
qsinlet = self._qsinlet
qs_in[inlet_node] = qsinlet
q = grid.at_node["surface_water__discharge"]
da = q / grid.dx**2
# if inlet flag is not on, proceed as normal.
else:
# renamed this drainage area set by flow router
da = grid.at_node["drainage_area"]
s = grid.at_node["flow__upstream_node_order"]
max_slopes = grid.at_node["topographic__steepest_slope"]
flowdirs = grid.at_node["flow__receiver_node"]
interior_s = s[np.where(grid.status_at_node[s] == 0)[0]]
dwnst_nodes = interior_s.copy()
# reverse list so we go from upstream to down stream
dwnst_nodes = dwnst_nodes[::-1]
# local time
time = 0.0
globdt = dt
while time < globdt:
max_slopes[:] = max_slopes.clip(0)
# here calculate dzdt for each node, with initial time step
for i in dwnst_nodes:
dep = alph * qs_in[i] / da[i]
ero = -Kv[i] * da[i] ** (0.5) * max_slopes[i]
dzver[i] = dep + ero
petlat = 0.0
# water depth in meters, needed for lateral erosion calc
wd = wid_coeff * (da[i] * runoffms) ** wid_exp
if i in flowdirs:
# node_finder picks the lateral node to erode based on angle
# between segments between three nodes
[lat_node, inv_rad_curv] = node_finder(grid, i, flowdirs, da)
# node_finder returns the lateral node ID and the radius of
# curvature
lat_nodes[i] = lat_node
# if the lateral node is not 0 or -1 continue.
if lat_node > 0 and z[lat_node] > z[i]:
# if the elevation of the lateral node is higher than
# primary node, calculate a new potential lateral
# erosion (L/T), which is negative
petlat = -Kl[i] * da[i] * max_slopes[i] * inv_rad_curv
# the calculated potential lateral erosion is mutiplied
# by the length of the node and the bank height, then
# added to an array, vol_lat_dt, for volume eroded
# laterally *per timestep* at each node. This vol_lat_dt
# is reset to zero for each timestep loop. vol_lat_dt
# is added to itself more than one primary nodes are
# laterally eroding this lat_node volume of lateral
# erosion per timestep
vol_lat_dt[lat_node] += abs(petlat) * grid.dx * wd
# send sediment downstream. sediment eroded from vertical incision
# and lateral erosion is sent downstream
qs_in[flowdirs[i]] += (
qs_in[i] - (dzver[i] * grid.dx**2) - (petlat * grid.dx * wd)
) # qsin to next node
# summing qs for this entire timestep
qs[:] += qs_in - (dzver * grid.dx**2)
dzdt[:] = dzver
# Do a time-step check
# If the downstream node is eroding at a slower rate than the
# upstream node, there is a possibility of flow direction reversal,
# or at least a flattening of the landscape.
# Limit dt so that this flattening or reversal doesn't happen.
# How close you allow these two points to get to eachother is
# determined by the cfl timestep condition, hard coded to equal 0.3
# dtn is an arbitrarily large number to begin with, but will be
# adapted as we step through the nodes
dtn = dt * 50 # starting minimum timestep for this round
for i in dwnst_nodes:
# are points converging? ie, downstream eroding slower than upstream
dzdtdif = dzdt[flowdirs[i]] - dzdt[i]
# if points converging, find time to zero slope
if dzdtdif > 1.0e-5 and max_slopes[i] > 1e-5:
# time to flat between points
dtflat = (z[i] - z[flowdirs[i]]) / dzdtdif
# if time to flat is smaller than dt, take the lower value
if dtflat < dtn:
dtn = dtflat
# assert dtn>0, "dtn <0 at dtflat"
# if dzdtdif*dtflat will make upstream lower than downstream, find
# time to flat
if dzdtdif * dtflat > (z[i] - z[flowdirs[i]]):
dtn = (z[i] - z[flowdirs[i]]) / dzdtdif
dtn *= cfl_cond
# new minimum timestep for this round of nodes
dt = min(abs(dtn), dt)
assert dt > 0.0, "timesteps less than 0."
# vol_lat is the total volume eroded from the lateral nodes through
# the entire model run. So vol_lat is itself plus vol_lat_dt (for current loop)
# times stable timestep size
vol_lat[:] += vol_lat_dt * dt
# this loop determines if enough lateral erosion has happened to change
# the height of the neighbor node.
for i in dwnst_nodes:
lat_node = lat_nodes[i]
wd = wid_coeff * (da[i] * runoffms) ** wid_exp
# greater than zero now bc inactive neighbors are value -1
if lat_node > 0 and z[lat_node] > z[i]:
# vol_diff is the volume that must be eroded from lat_node so that its
# elevation is the same as node downstream of primary node
# UC model: this would represent undercutting (the water height
# at node i), slumping, and instant removal.
if UC == 1:
voldiff = (z[i] + wd - z[flowdirs[i]]) * grid.dx**2
# TB model: entire lat node must be eroded before lateral
# erosion occurs
if TB == 1:
voldiff = (z[lat_node] - z[flowdirs[i]]) * grid.dx**2
# if the total volume eroded from lat_node is greater than the volume
# needed to be removed to make node equal elevation,
# then instantaneously remove this height from lat node. already
# has timestep in it
if vol_lat[lat_node] >= voldiff:
self._dzlat[lat_node] = z[flowdirs[i]] - z[lat_node] # -0.001
# after the lateral node is eroded, reset its volume eroded
# to zero
vol_lat[lat_node] = 0.0
# multiply dzdt by timestep size and combine with lateral erosion
# self._dzlat, which is already a length for the calculated time step
dz = dzdt * dt + self._dzlat
# change height of landscape
z[:] += dz
# update elapsed time
time = dt + time
# check to see that you are within 0.01% of the global timestep, if so
# done, if not continue
if time > 0.9999 * globdt:
time = globdt
else:
dt = globdt - time
qs_in = grid.zeros(centering="node")
# recalculate flow directions
(da, q) = self._flow_accumulator.accumulate_flow()
if inlet_on:
# if inlet on, reset drainage area and qsin to reflect inlet conditions
# this is the drainage area needed for code below with an inlet
# set by spatially varible runoff.
da = q / grid.dx**2
qs_in[inlet_node] = qsinlet
else:
# otherwise, drainage area is just drainage area.
da = grid.at_node["drainage_area"]
s = grid.at_node["flow__upstream_node_order"]
max_slopes = grid.at_node["topographic__steepest_slope"]
q = grid.at_node["surface_water__discharge"]
flowdirs = grid.at_node["flow__receiver_node"]
interior_s = s[np.where(grid.status_at_node[s] == 0)[0]]
dwnst_nodes = interior_s.copy()
dwnst_nodes = dwnst_nodes[::-1]
lat_nodes = np.zeros(grid.number_of_nodes, dtype=int)
self._dzlat = np.zeros(grid.number_of_nodes)
vol_lat_dt = np.zeros(grid.number_of_nodes)
dzver = np.zeros(grid.number_of_nodes)
return grid, self._dzlat
```