예제 #1
0
def test_raise_error():
    mg = RasterModelGrid((5, 5))
    soilTh = mg.add_zeros('node', 'soil__depth')
    z = mg.add_zeros('node', 'topographic__elevation')
    BRz = mg.add_zeros('node', 'bedrock__elevation')
    z += mg.node_x.copy()**2
    BRz = z.copy() - 1.0
    soilTh[:] = z - BRz
    expweath = ExponentialWeatherer(mg)
    DDdiff = DepthDependentCubicDiffuser(mg)
    expweath.calc_soil_prod_rate()
    assert_raises(RuntimeError, DDdiff.soilflux, 10, if_unstable='raise')
예제 #2
0
def test_4x7_grid_vs_analytical_solution():
    """Test against known analytical solution."""

    # Create a 4-row by 7-column grid with 10 m spacing
    mg = RasterModelGrid((4, 7), xy_spacing=10.0)

    # Close off top and bottom (N and S) boundaries so it becomes a 1D problem
    mg.set_closed_boundaries_at_grid_edges(False, True, False, True)

    # Create an elevation field, initially zero
    z = mg.add_zeros("topographic__elevation", at="node")
    mg.add_zeros("soil__depth", at="node")

    # Instantiate components, and set their parameters. Note that traditional
    # diffusivity, D, is D = SCE x H*, where SCE is soil-creep efficiency.
    # Here we want D = 0.01 m2/yr and H* = 0,.5 m, so cwe set SCE = 0.02.
    weatherer = ExponentialWeatherer(mg,
                                     soil_production__maximum_rate=0.0002,
                                     soil_production__decay_depth=0.5)

    diffuser = DepthDependentTaylorDiffuser(mg,
                                            linear_diffusivity=0.01,
                                            slope_crit=0.8,
                                            soil_transport_decay_depth=0.5)

    # Get a reference to bedrock elevation field
    z_bedrock = mg.at_node["bedrock__elevation"]

    # Estimate a reasonable time step. Here we take advantage of the fact that
    # we know the final slope at the outer links will be about 1.33. Stability
    # for the cubic term involves an "effective D" parameter, Deff, that should
    # be Deff = D (S / Sc)^2. (see notebook calcs)
    baselevel_rate = 0.0001
    dt = 250.0

    # Run for 750 ky
    for i in range(3000):

        z[mg.core_nodes] += baselevel_rate * dt
        z_bedrock[mg.core_nodes] += baselevel_rate * dt

        weatherer.calc_soil_prod_rate()
        diffuser.run_one_step(dt)

    # Test: these numbers represent equilibrium. See Jupyter notebook for
    # calculations.
    my_nodes = mg.nodes[2, :]
    assert_array_equal(np.round(z[my_nodes], 1),
                       np.array([0.0, 6.2, 10.7, 12.6, 10.7, 6.2, 0.0]))
    assert_array_equal(
        np.round(mg.at_node["soil__depth"][8:13], 2),
        np.array([0.35, 0.35, 0.35, 0.35, 0.35]),
    )
def test_raise_stability_error():
    mg = RasterModelGrid((5, 5))
    soilTh = mg.add_zeros('node', 'soil__depth')
    z = mg.add_zeros('node', 'topographic__elevation')
    BRz = mg.add_zeros('node', 'bedrock__elevation')
    z += mg.node_x.copy()**2
    BRz = z.copy() - 1.0
    soilTh[:] = z - BRz
    expweath = ExponentialWeatherer(mg)
    DDdiff = DepthDependentTaylorDiffuser(mg)
    expweath.calc_soil_prod_rate()
    assert_raises(RuntimeError, DDdiff.soilflux, 10, if_unstable='raise')
예제 #4
0
def test_infinite_taylor_error():
    
    mg = RasterModelGrid((5, 5))
    soilTh = mg.add_zeros('node', 'soil__depth')
    z = mg.add_zeros('node', 'topographic__elevation')
    BRz = mg.add_zeros('node', 'bedrock__elevation')
    z += mg.node_x.copy()**4
    BRz = z.copy() - 1.0
    soilTh[:] = z - BRz
    expweath = ExponentialWeatherer(mg)
    DDdiff = DepthDependentTaylorDiffuser(mg, nterms=400)
    expweath.calc_soil_prod_rate()
    assert_raises(RuntimeError, DDdiff.soilflux, 10)
def test_raise_stability_error():
    mg = RasterModelGrid((5, 5))
    soilTh = mg.add_zeros("node", "soil__depth")
    z = mg.add_zeros("node", "topographic__elevation")
    BRz = mg.add_zeros("node", "bedrock__elevation")
    z += mg.node_x.copy() ** 2
    BRz = z.copy() - 1.0
    soilTh[:] = z - BRz
    expweath = ExponentialWeatherer(mg)
    DDdiff = DepthDependentTaylorDiffuser(mg)
    expweath.calc_soil_prod_rate()
    with pytest.raises(RuntimeError):
        DDdiff.soilflux(10, if_unstable="raise")
def test_4x7_grid_vs_analytical_solution():
    """Test against known analytical solution."""

    # Create a 4-row by 7-column grid with 10 m spacing
    mg = RasterModelGrid((4, 7), xy_spacing=10.0)

    # Close off top and bottom (N and S) boundaries so it becomes a 1D problem
    mg.set_closed_boundaries_at_grid_edges(False, True, False, True)

    # Create an elevation field, initially zero
    z = mg.add_zeros("node", "topographic__elevation")

    # Instantiate components, and set their parameters. Note that traditional
    # diffusivity, D, is D = SCE x H*, where SCE is soil-creep efficiency.
    # Here we want D = 0.01 m2/yr and H* = 0,.5 m, so cwe set SCE = 0.02.
    diffuser = DepthDependentTaylorDiffuser(
        mg, linear_diffusivity=0.01, slope_crit=0.8, soil_transport_decay_depth=0.5
    )
    weatherer = ExponentialWeatherer(
        mg, soil_production__maximum_rate=0.0002, soil_production__decay_depth=0.5
    )

    # Get a reference to bedrock elevation field
    z_bedrock = mg.at_node["bedrock__elevation"]

    # Estimate a reasonable time step. Here we take advantage of the fact that
    # we know the final slope at the outer links will be about 1.33. Stability
    # for the cubic term involves an "effective D" parameter, Deff, that should
    # be Deff = D (S / Sc)^2. (see notebook calcs)
    baselevel_rate = 0.0001
    dt = 250.0

    # Run for 750 ky
    for i in range(3000):

        z[mg.core_nodes] += baselevel_rate * dt
        z_bedrock[mg.core_nodes] += baselevel_rate * dt

        weatherer.calc_soil_prod_rate()
        diffuser.run_one_step(dt)

    # Test: these numbers represent equilibrium. See Jupyter notebook for
    # calculations.
    my_nodes = mg.nodes[2, :]
    assert_array_equal(
        np.round(z[my_nodes], 1), np.array([0.0, 4.0, 6.7, 7.7, 6.7, 4.0, 0.0])
    )
    assert_array_equal(
        np.round(mg.at_node["soil__depth"][8:13], 2),
        np.array([0.35, 0.35, 0.35, 0.35, 0.35]),
    )
예제 #7
0
def test_infinite_taylor_error():

    mg = RasterModelGrid((5, 5))
    soilTh = mg.add_zeros("soil__depth", at="node")
    z = mg.add_zeros("topographic__elevation", at="node")
    BRz = mg.add_zeros("bedrock__elevation", at="node")
    z += mg.node_x.copy()**4
    BRz = z.copy() - 1.0
    soilTh[:] = z - BRz
    expweath = ExponentialWeatherer(mg)
    DDdiff = DepthDependentTaylorDiffuser(mg, nterms=400)
    expweath.calc_soil_prod_rate()
    with pytest.raises(RuntimeError):
        DDdiff.soilflux(10)
            blocks.erode_blocks(is_block_in_cell, block__erodibility,
                                block__critical_stress, timestep,
                                raw_shear_stress_array[x],
                                adjusted_shear_stress_array[x],
                                block_channel_weathering_rate)

            #delete blocks that have passed out of domain or eroded down to nothing
            blocks.delete_eroded_or_gone_blocks(baselevel_node,
                                                blocks.for_slicing)
        else:
            pass

    #now, the hillslope evolution processes

    #weathering
    weatheringrate.calc_soil_prod_rate()

    hog.set_soil_production_rate_on_hard_layer(soil_production_rate)

    mg.at_node['soil_production__rate'][channel_nodes] = 0

    hog.weather_blocks(soil_production_rate, max_block_weathering_rate,
                       underlying_soil_depth, soil_depth, timestep)

    #flux
    hillslopeflux.soilflux(timestep)

    mg.at_node['topographic__elevation'][channel_nodes] = mg.at_node[
        'bedrock__elevation'][channel_nodes]
    mg.at_node['soil__depth'][channel_nodes] = 0
예제 #9
0
class BasicChSa(ErosionModel):
    r"""**BasicChSa** model program.

    This model program combines models :py:class:`BasicCh` and
    :py:class:`BasicSa`. A soil layer is produced by weathering that decays
    exponentially with soil thickness and hillslope transport is soil-depth
    dependent. Given a spatially varying soil thickness :math:`H` and a
    spatially varying bedrock elevation :math:`\eta_b`, model **BasicChSa**
    evolves a topographic surface described by :math:`\eta` with the following
    governing equations:

    .. math::

        \eta = \eta_b + H

        \frac{\partial H}{\partial t} = P_0 \exp (-H/H_s)
                                        - \delta (H) K Q^{m} S^{n}
                                        -\nabla q_h

        \frac{\partial \eta_b}{\partial t} = -P_0 \exp (-H/H_s)
                                             - (1 - \delta (H) ) K Q^{m} S^{n}

        q_h = -D S H^* \left[ 1 + \left( \frac{S}{S_c} \right)^2
              + \left( \frac{S}{S_c} \right)^4
              + ... \left( \frac{S}{S_c} \right)^{2(N-1)} \right]

    where :math:`Q` is the local stream discharge, :math:`S` is the local
    slope, :math:`m` and :math:`n` are the discharge and slope exponent
    parameters, :math:`K` is the erodibility by water, :math:`D` is the
    regolith transport parameter, :math:`H_s` is the sediment production decay
    depth, :math:`H_s` is the sediment production decay depth, :math:`P_0` is
    the maximum sediment production rate, and :math:`H_0` is the sediment
    transport decay depth. :math:`q_h` is the hillslope sediment flux per unit
    width. :math:`S_c` is the critical slope parameter and :math:`N` is the
    number of terms in the Taylor Series expansion.

    The function :math:`\delta (H)` is used to indicate that water erosion will
    act on soil where it exists, and on the underlying lithology where soil is
    absent. To achieve this, :math:`\delta (H)` is defined to equal 1 when
    :math:`H > 0` (meaning soil is present), and 0 if :math:`H = 0` (meaning
    the underlying parent material is exposed).

    Refer to
    `Barnhart et al. (2019) <https://doi.org/10.5194/gmd-12-1267-2019>`_
    Table 5 for full list of parameter symbols, names, and dimensions.

    The following at-node fields must be specified in the grid:
        - ``topographic__elevation``
        - ``soil__depth``
    """

    _required_fields = ["topographic__elevation", "soil__depth"]

    def __init__(
        self,
        clock,
        grid,
        m_sp=0.5,
        n_sp=1.0,
        water_erodibility=0.0001,
        regolith_transport_parameter=0.1,
        critical_slope=0.3,
        number_of_taylor_terms=11,
        soil_production__maximum_rate=0.001,
        soil_production__decay_depth=0.5,
        soil_transport_decay_depth=0.5,
        **kwargs
    ):
        """
        Parameters
        ----------
        clock : terrainbento Clock instance
        grid : landlab model grid instance
            The grid must have all required fields.
        m_sp : float, optional
            Drainage area exponent (:math:`m`). Default is 0.5.
        n_sp : float, optional
            Slope exponent (:math:`n`). Default is 1.0.
        water_erodibility : float, optional
            Water erodibility (:math:`K`). Default is 0.0001.
        regolith_transport_parameter : float, optional
            Regolith transport efficiency (:math:`D`). Default is 0.1.
        critical_slope : float, optional
            Critical slope (:math:`S_c`, unitless). Default is 0.3.
        number_of_taylor_terms : int, optional
            Number of terms in the Taylor Series Expansion (:math:`N`). Default
            is 11.
        soil_production__maximum_rate : float, optional
            Maximum rate of soil production (:math:`P_{0}`). Default is 0.001.
        soil_production__decay_depth : float, optional
            Decay depth for soil production (:math:`H_{s}`). Default is 0.5.
        soil_transport_decay_depth : float, optional
            Decay depth for soil transport (:math:`H_{0}`). Default is 0.5.
        **kwargs :
            Keyword arguments to pass to :py:class:`ErosionModel`. Importantly
            these arguments specify the precipitator and the runoff generator
            that control the generation of surface water discharge (:math:`Q`).

        Returns
        -------
        BasicChSa : model object

        Examples
        --------
        This is a minimal example to demonstrate how to construct an instance
        of model **BasicChSa**. For more detailed examples, including
        steady-state test examples, see the terrainbento tutorials.

        To begin, import the model class.

        >>> from landlab import RasterModelGrid
        >>> from landlab.values import constant
        >>> from terrainbento import Clock, BasicChSa
        >>> clock = Clock(start=0, stop=100, step=1)
        >>> grid = RasterModelGrid((5,5))
        >>> _ = constant(grid, "topographic__elevation", value=1.0)
        >>> _ = constant(grid, "soil__depth", value=1.0)

        Construct the model.

        >>> model = BasicChSa(clock, grid)

        Running the model with ``model.run()`` would create output, so here we
        will just run it one step.

        >>> model.run_one_step(10)
        >>> model.model_time
        10.0

        """

        # Call ErosionModel"s init
        super(BasicChSa, self).__init__(clock, grid, **kwargs)

        # verify correct fields are present.
        self._verify_fields(self._required_fields)

        self.m = m_sp
        self.n = n_sp
        self.K = water_erodibility

        # Create bedrock elevation field
        soil_thickness = self.grid.at_node["soil__depth"]
        bedrock_elev = self.grid.add_zeros("node", "bedrock__elevation")
        bedrock_elev[:] = self.z - soil_thickness

        # Instantiate a FastscapeEroder component
        self.eroder = FastscapeEroder(
            self.grid,
            K_sp=self.K,
            m_sp=self.m,
            n_sp=self.n,
            discharge_name="surface_water__discharge",
        )

        # Instantiate a weathering component
        self.weatherer = ExponentialWeatherer(
            self.grid,
            soil_production__maximum_rate=soil_production__maximum_rate,
            soil_production__decay_depth=soil_production__decay_depth,
        )

        # Instantiate a soil-transport component
        self.diffuser = DepthDependentTaylorDiffuser(
            self.grid,
            linear_diffusivity=regolith_transport_parameter,
            slope_crit=critical_slope,
            soil_transport_decay_depth=soil_transport_decay_depth,
            nterms=number_of_taylor_terms,
        )

    def run_one_step(self, step):
        """Advance model **BasicChSa** for one time-step of duration step.

        The **run_one_step** method does the following:

        1. Creates rain and runoff, then directs and accumulates flow.

        2. Assesses the location, if any, of flooded nodes where erosion should
           not occur.

        3. Assesses if a :py:mod:`PrecipChanger` is an active boundary handler
           and if so, uses it to modify the erodibility by water.

        4. Calculates detachment-limited erosion by water.

        5. Produces soil and calculates soil depth with exponential weathering.

        6. Calculates topographic change by depth-dependent nonlinear
           diffusion.

        7. Finalizes the step using the :py:mod:`ErosionModel` base class
           function **finalize__run_one_step**. This function updates all
           boundary handlers handlers by ``step`` and increments model time by
           ``step``.

        Parameters
        ----------
        step : float
            Increment of time for which the model is run.
        """
        # create and move water
        self.create_and_move_water(step)

        # Get IDs of flooded nodes, if any
        if self.flow_accumulator.depression_finder is None:
            flooded = []
        else:
            flooded = np.where(
                self.flow_accumulator.depression_finder.flood_status == 3
            )[0]

        # Do some erosion (but not on the flooded nodes)
        # (if we're varying K through time, update that first)
        if "PrecipChanger" in self.boundary_handlers:
            self.eroder.K = (
                self.K
                * self.boundary_handlers[
                    "PrecipChanger"
                ].get_erodibility_adjustment_factor()
            )

        self.eroder.run_one_step(step, flooded_nodes=flooded)

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node["bedrock__elevation"]
        b[:] = np.minimum(b, self.grid.at_node["topographic__elevation"])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Do some soil creep
        self.diffuser.run_one_step(
            step, dynamic_dt=True, if_unstable="raise", courant_factor=0.1
        )

        # Finalize the run_one_step_method
        self.finalize__run_one_step(step)
예제 #10
0
class BasicChSa(_ErosionModel):
    """
    A BasicChSa model computes erosion using depth-dependent cubic diffusion
    with a soil layer, basic stream power, and Q~A.
    """

    def __init__(self, input_file=None, params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicChSa model."""

        # Call ErosionModel's init
        super(BasicChSa, self).__init__(input_file=input_file,
                                        params=params,
                                        BaselevelHandlerClass=BaselevelHandlerClass)

        self.K_sp = self.get_parameter_from_exponent('K_sp')
        linear_diffusivity = (self._length_factor**2.)*self.get_parameter_from_exponent('linear_diffusivity') # has units length^2/time
        try:
            initial_soil_thickness = (self._length_factor)*self.params['initial_soil_thickness'] # has units length
        except KeyError:
            initial_soil_thickness = 1.0  # default value
        soil_transport_decay_depth = (self._length_factor)*self.params['soil_transport_decay_depth']  # has units length
        max_soil_production_rate = (self._length_factor)*self.params['max_soil_production_rate'] # has units length per time
        soil_production_decay_depth = (self._length_factor)*self.params['soil_production_decay_depth']   # has units length

        # Create soil thickness (a.k.a. depth) field
        if 'soil__depth' in self.grid.at_node:
            soil_thickness = self.grid.at_node['soil__depth']
        else:
            soil_thickness = self.grid.add_zeros('node', 'soil__depth')

        # Create bedrock elevation field
        if 'bedrock__elevation' in self.grid.at_node:
            bedrock_elev = self.grid.at_node['bedrock__elevation']
        else:
            bedrock_elev = self.grid.add_zeros('node', 'bedrock__elevation')

        soil_thickness[:] = initial_soil_thickness
        bedrock_elev[:] = self.z - initial_soil_thickness

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(self.grid,
                                           flow_director='D8',
                                           depression_finder = DepressionFinderAndRouter)

        # Instantiate a FastscapeEroder component
        self.eroder = FastscapeEroder(self.grid,
                                      K_sp=self.K_sp,
                                      m_sp=self.params['m_sp'],
                                      n_sp=self.params['n_sp'])

        # Instantiate a weathering component
        self.weatherer = ExponentialWeatherer(self.grid,
                                              max_soil_production_rate=max_soil_production_rate,
                                              soil_production_decay_depth=soil_production_decay_depth)

        # Instantiate a soil-transport component
        self.diffuser = DepthDependentTaylorDiffuser(self.grid,
                                                    linear_diffusivity=linear_diffusivity,
                                                    slope_crit=self.params['slope_crit'],
                                                    soil_transport_decay_depth=soil_transport_decay_depth,
                                                    nterms=11)

    def run_one_step(self, dt):
        """
        Advance model for one time-step of duration dt.
        """

        # Route flow
        self.flow_router.run_one_step()

        # Get IDs of flooded nodes, if any
        flooded = np.where(self.flow_router.depression_finder.flood_status==3)[0]

        # Do some erosion (but not on the flooded nodes)
        # (if we're varying K through time, update that first)
        if self.opt_var_precip:
            self.eroder.K = (self.K_sp
                             * self.pc.get_erodibility_adjustment_factor(self.model_time))

        self.eroder.run_one_step(dt, flooded_nodes=flooded)

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node['bedrock__elevation']
        b[:] = np.minimum(b, self.grid.at_node['topographic__elevation'])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Do some soil creep
        self.diffuser.run_one_step(dt,
                                   dynamic_dt=True,
                                   if_unstable='raise',
                                   courant_factor=0.1)

        # calculate model time
        self.model_time += dt

        # Lower outlet
        self.update_outlet(dt)

        # Check walltime
        self.check_walltime()
예제 #11
0
class BasicHySa(ErosionModel):
    r"""**BasicHySa** program.

    This model program combines :py:class:`BasicHy` and :py:class:`BasicSa` to
    evolve a topographic surface described by :math:`\eta` with the following
    governing equation:

    .. math::

        \eta = \eta_b + H

        \frac{\partial H}{\partial t} = P_0 \exp (-H/H_s)
                          + \frac{V_s Q_s}{Q(A)\left(1 - \phi \right)}
                          - K_s Q(A)^{m}S^{n} (1 - e^{-H/H_*})
                          -\nabla q_h

        \frac{\partial \eta_b}{\partial t} = -P_0 \exp (-H/H_s)
                                             - K_r Q(A)^{m}S^{n} e^{-H/H_*}

        Q_s = \int_0^A \left(K_s Q(A)^{m}S^{n} (1-e^{-H/H_*})
              + K_r (1-F_f) Q(A)^{m}S^{n} e^{-H/H_*}
              - \frac{V_s Q_s}{Q(A)}\right) dA

    where :math:`\eta_b` is the bedrock elevation, :math:`H` is the soil depth,
    :math:`P_0` is the maximum soil production rate, :math:`H_s` is the soil
    production decay depth, :math:`V_s` is effective sediment settling
    velocity, :math:`Q_s` is volumetric fluvial sediment flux, :math:`A` is the
    local drainage area, :math:`Q`, is the local discharge, :math:`S` is the
    local slope, :math:`\phi` is sediment porosity, :math:`F_f` is the fraction
    of fine sediment, :math:`K_r` and :math:`K_s` are rock and sediment
    erodibility respectively, :math:`m` and :math:`n` are the discharge and
    slope exponent parameters, :math:`H_*` is the bedrock roughness length
    scale, and :math:`r` is a runoff rate. Hillslope sediment flux per unit
    width :math:`q_h` is given by:

    .. math::

        q_h = -D H^* \left[1-\exp \left( -\frac{H}{H_0} \right) \right]
              \nabla \eta.

    where :math:`D` is soil diffusivity and :math:`H_0` is the soil transport
    depth scale.

    Refer to
    `Barnhart et al. (2019) <https://doi.org/10.5194/gmd-12-1267-2019>`_
    Table 5 for full list of parameter symbols, names, and dimensions.

    The following at-node fields must be specified in the grid:
        - ``topographic__elevation``
        - ``soil__depth``
    """

    _required_fields = ["topographic__elevation", "soil__depth"]

    def __init__(
        self,
        clock,
        grid,
        m_sp=0.5,
        n_sp=1.0,
        water_erodibility_sediment=0.001,
        water_erodibility_rock=0.0001,
        regolith_transport_parameter=0.1,
        settling_velocity=0.001,
        sediment_porosity=0.3,
        fraction_fines=0.5,
        roughness__length_scale=0.5,
        solver="basic",
        soil_production__maximum_rate=0.001,
        soil_production__decay_depth=0.5,
        soil_transport_decay_depth=0.5,
        sp_crit_br=0,
        sp_crit_sed=0,
        **kwargs
    ):
        """
        Parameters
        ----------
        clock : terrainbento Clock instance
        grid : landlab model grid instance
            The grid must have all required fields.
        m_sp : float, optional
            Drainage area exponent (:math:`m`). Default is 0.5.
        n_sp : float, optional
            Slope exponent (:math:`n`). Default is 1.0.
        water_erodibility : float, optional
            Water erodibility (:math:`K`). Default is 0.0001.
        regolith_transport_parameter : float, optional
            Regolith transport efficiency (:math:`D`). Default is 0.1.
        settling_velocity : float, optional
            Normalized settling velocity of entrained sediment (:math:`V_s`).
            Default is 0.001.
        sediment_porosity : float, optional
            Sediment porosity (:math:`\phi`). Default is 0.3.
        fraction_fines : float, optional
            Fraction of fine sediment that is permanently detached
            (:math:`F_f`). Default is 0.5.
        roughness__length_scale : float, optional
            Bedrock roughness length scale. Default is 0.5.
        solver : str, optional
            Solver option to pass to the Landlab
            `Space <https://landlab.readthedocs.io/en/master/reference/components/space.html>`_
            component. Default is "basic".
        soil_production__maximum_rate : float, optional
            Maximum rate of soil production (:math:`P_{0}`). Default is 0.001.
        soil_production__decay_depth : float, optional
            Decay depth for soil production (:math:`H_{s}`). Default is 0.5.
        soil_transport_decay_depth : float, optional
            Decay depth for soil transport (:math:`H_{0}`). Default is 0.5.
        **kwargs :
            Keyword arguments to pass to :py:class:`ErosionModel`. Importantly
            these arguments specify the precipitator and the runoff generator
            that control the generation of surface water discharge (:math:`Q`).

        Returns
        -------
        BasicHySa : model object

        Examples
        --------
        This is a minimal example to demonstrate how to construct an instance
        of model **BasicHySa**. For more detailed examples, including
        steady-state test examples, see the terrainbento tutorials.

        To begin, import the model class.

        >>> from landlab import RasterModelGrid
        >>> from landlab.values import random
        >>> from terrainbento import Clock, BasicHySa
        >>> clock = Clock(start=0, stop=100, step=1)
        >>> grid = RasterModelGrid((5,5))
        >>> _ = random(grid, "topographic__elevation")
        >>> _ = random(grid, "soil__depth")

        Construct the model.

        >>> model = BasicHySa(clock, grid)

        Running the model with ``model.run()`` would create output, so here we
        will just run it one step.

        >>> model.run_one_step(1.)
        >>> model.model_time
        1.0

        """
        # Call ErosionModel"s init
        super().__init__(clock, grid, **kwargs)

        # verify correct fields are present.
        self._verify_fields(self._required_fields)

        soil_thickness = self.grid.at_node["soil__depth"]
        bedrock_elev = self.grid.add_zeros("node", "bedrock__elevation")
        bedrock_elev[:] = self.z - soil_thickness

        self.m = m_sp
        self.n = n_sp
        self.K_br = water_erodibility_rock
        self.K_sed = water_erodibility_sediment

        # Instantiate a SPACE component
        self.eroder = Space(
            self.grid,
            K_sed=self.K_sed,
            K_br=self.K_br,
            sp_crit_br=sp_crit_br,
            sp_crit_sed=sp_crit_sed,
            F_f=fraction_fines,
            phi=sediment_porosity,
            H_star=roughness__length_scale,
            v_s=settling_velocity,
            m_sp=self.m,
            n_sp=self.n,
            discharge_field="surface_water__discharge",
            solver=solver,
        )

        # Instantiate diffusion and weathering components
        self.weatherer = ExponentialWeatherer(
            self.grid,
            soil_production__maximum_rate=soil_production__maximum_rate,
            soil_production__decay_depth=soil_production__decay_depth,
        )

        self.diffuser = DepthDependentDiffuser(
            self.grid,
            linear_diffusivity=regolith_transport_parameter,
            soil_transport_decay_depth=soil_transport_decay_depth,
        )

        self.grid.at_node["soil__depth"][:] = (
            self.grid.at_node["topographic__elevation"]
            - self.grid.at_node["bedrock__elevation"]
        )

    def run_one_step(self, step):
        """Advance model **BasicHySa** for one time-step of duration step.

        The **run_one_step** method does the following:

        1. Creates rain and runoff, then directs and accumulates flow.

        2. Assesses the location, if any, of flooded nodes where erosion should
           not occur.

        3. Assesses if a :py:mod:`PrecipChanger` is an active boundary handler
           and if so, uses it to modify the erodibility by water.

        4. Calculates erosion and deposition by water.

        5. Calculates topographic change by linear diffusion.

        6. Finalizes the step using the :py:mod:`ErosionModel` base class
           function **finalize__run_one_step**. This function updates all
           boundary handlers handlers by ``step`` and increments model time by
           ``step``.

        Parameters
        ----------
        step : float
            Increment of time for which the model is run.
        """
        # create and move water
        self.create_and_move_water(step)

        # Do some erosion (but not on the flooded nodes)
        # (if we're varying K through time, update that first)
        if "PrecipChanger" in self.boundary_handlers:
            erode_factor = self.boundary_handlers[
                "PrecipChanger"
            ].get_erodibility_adjustment_factor()
            self.eroder.K_sed = self.K_sed * erode_factor
            self.eroder.K_br = self.K_br * erode_factor

        self.eroder.run_one_step(step)

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node["bedrock__elevation"]
        b[:] = np.minimum(b, self.grid.at_node["topographic__elevation"])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Generate and move soil around
        self.diffuser.run_one_step(step)

        # Finalize the run_one_step_method
        self.finalize__run_one_step(step)

        # Check stability
        self.check_stability()

    def check_stability(self):
        """Check model stability and exit if unstable."""
        fields = self.grid.at_node.keys()
        for f in fields:
            if np.any(np.isnan(self.grid.at_node[f])) or np.any(
                np.isinf(self.grid.at_node[f])
            ):
                raise SystemExit(
                    "terrainbento ModelHySa: Model became unstable"
                )
예제 #12
0
class BasicRtSa(TwoLithologyErosionModel):
    r"""**BasicRtSa** model program.

    This model program combines the :py:class:`BasicRt` and :py:class:`BasicSa`
    programs by allowing for two lithologies, an "upper" layer and a "lower"
    layer and explicitly resolving a soil layer. This soil layer is produced by
    weathering that decays exponentially with soil thickness and hillslope
    transport is soil-depth dependent. Given a spatially varying contact zone
    elevation, :math:`\eta_C(x,y))`, a spatially varying soil thickness
    :math:`H` and a spatially varying bedrock elevation :math:`\eta_b`,
    model **BasicRtSa** evolves a topographic surface described by :math:`\eta`
    with the following governing equations:

    .. math::

        \eta = \eta_b + H

        \frac{\partial H}{\partial t} = P_0 \exp (-H/H_s)
                                        - \delta (H) K Q^{m} S^{n}
                                        - \nabla q_h

        \frac{\partial \eta_b}{\partial t} = -P_0 \exp (-H/H_s)
                                             - (1 - \delta (H) ) K Q^{m} S^{n}

        q_h = -D H^* \left[1-\exp \left( -\frac{H}{H_0} \right) \right] \nabla \eta

        K(\eta, \eta_C ) = w K_1 + (1 - w) K_2

        w = \frac{1}{1+\exp \left( -\frac{(\eta -\eta_C )}{W_c}\right)}

    where :math:`Q` is the local stream discharge, :math:`S` is the local
    slope, :math:`m` and :math:`n` are the discharge and slope exponent
    parameters, :math:`W_c` is the contact-zone width, :math:`K_1` and
    :math:`K_2` are the erodabilities of the upper and lower lithologies, and
    :math:`D` is the regolith transport parameter. :math:`w` is a weight used
    to calculate the effective erodibility :math:`K(\eta, \eta_C)` based on the
    depth to the contact zone and the width of the contact zone. :math:`H_s` is
    the sediment production decay depth, :math:`H_0` is the sediment transport
    decay depth, :math:`P_0` is the maximum sediment production rate, and
    :math:`H_0` is the sediment transport decay depth. :math:`q_h` is the
    hillslope sediment flux per unit width.

    The function :math:`\delta (H)` is used to indicate that water erosion will
    act on soil where it exists, and on the underlying lithology where soil is
    absent. To achieve this, :math:`\delta (H)` is defined to equal 1 when
    :math:`H > 0` (meaning soil is present), and 0 if :math:`H = 0` (meaning
    the underlying parent material is exposed).

    The weight :math:`w` promotes smoothness in the solution of erodibility at
    a given point. When the surface elevation is at the contact elevation, the
    erodibility is the average of :math:`K_1` and :math:`K_2`; above and below
    the contact, the erodibility approaches the value of :math:`K_1` and
    :math:`K_2` at a rate related to the contact zone width. Thus, to make a
    very sharp transition, use a small value for the contact zone width.

    Refer to
    `Barnhart et al. (2019) <https://doi.org/10.5194/gmd-12-1267-2019>`_
    Table 5 for full list of parameter symbols, names, and dimensions.

    The following at-node fields must be specified in the grid:
        - ``topographic__elevation``
        - ``lithology_contact__elevation``
        - ``soil__depth``
    """

    _required_fields = [
        "topographic__elevation",
        "lithology_contact__elevation",
        "soil__depth",
    ]

    def __init__(
        self,
        clock,
        grid,
        soil_production__maximum_rate=0.001,
        soil_production__decay_depth=0.5,
        soil_transport_decay_depth=0.5,
        **kwargs
    ):
        """
        Parameters
        ----------
        clock : terrainbento Clock instance
        grid : landlab model grid instance
            The grid must have all required fields.
        m_sp : float, optional
            Drainage area exponent (:math:`m`). Default is 0.5.
        n_sp : float, optional
            Slope exponent (:math:`n`). Default is 1.0.
        water_erodibility_upper : float, optional
            Water erodibility of the upper layer (:math:`K_{1}`). Default is
            0.001.
        water_erodibility_lower : float, optional
            Water erodibility of the upper layer (:math:`K_{2}`). Default is
            0.0001.
        contact_zone__width : float, optional
            Thickness of the contact zone (:math:`W_c`). Default is 1.
        regolith_transport_parameter : float, optional
            Regolith transport efficiency (:math:`D`). Default is 0.1.
        soil_production__maximum_rate : float, optional
            Maximum rate of soil production (:math:`P_{0}`). Default is 0.001.
        soil_production__decay_depth : float, optional
            Decay depth for soil production (:math:`H_{s}`). Default is 0.5.
        soil_transport_decay_depth : float, optional
            Decay depth for soil transport (:math:`H_{0}`). Default is 0.5.
        **kwargs :
            Keyword arguments to pass to :py:class:`TwoLithologyErosionModel`.
            Importantly these arguments specify the precipitator and the runoff
            generator that control the generation of surface water discharge
            (:math:`Q`).

        Returns
        -------
        BasicRtSa : model object

        Examples
        --------
        This is a minimal example to demonstrate how to construct an instance
        of model **BasicRtSa**. For more detailed examples, including
        steady-state test examples, see the terrainbento tutorials.

        To begin, import the model class.

        >>> from landlab import RasterModelGrid
        >>> from landlab.values import random, constant
        >>> from terrainbento import Clock, BasicRtSa
        >>> clock = Clock(start=0, stop=100, step=1)
        >>> grid = RasterModelGrid((5,5))
        >>> _ = random(grid, "topographic__elevation")
        >>> _ = random(grid, "soil__depth")
        >>> _ = constant(grid, "lithology_contact__elevation", value=-10.)

        Construct the model.

        >>> model = BasicRtSa(clock, grid)

        Running the model with ``model.run()`` would create output, so here we
        will just run it one step.

        >>> model.run_one_step(1.)
        >>> model.model_time
        1.0

        """
        # Call ErosionModel"s init
        super(BasicRtSa, self).__init__(clock, grid, **kwargs)

        # verify correct fields are present.
        self._verify_fields(self._required_fields)

        # Set up rock-till boundary and associated grid fields.
        self._setup_rock_and_till()

        # Instantiate a FastscapeEroder component
        self.eroder = FastscapeEroder(
            self.grid,
            K_sp=self.erody,
            m_sp=self.m,
            n_sp=self.n,
            discharge_name="surface_water__discharge",
        )

        soil_thickness = self.grid.at_node["soil__depth"]
        bedrock_elev = self.grid.add_zeros("node", "bedrock__elevation")
        bedrock_elev[:] = self.z - soil_thickness

        # Instantiate diffusion and weathering components
        self.diffuser = DepthDependentDiffuser(
            self.grid,
            linear_diffusivity=self.regolith_transport_parameter,
            soil_transport_decay_depth=soil_transport_decay_depth,
        )

        self.weatherer = ExponentialWeatherer(
            self.grid,
            soil_production__maximum_rate=soil_production__maximum_rate,
            soil_production__decay_depth=soil_production__decay_depth,
        )

    def run_one_step(self, step):
        """Advance model **BasicRtSa** for one time-step of duration step.

        The **run_one_step** method does the following:

        1. Creates rain and runoff, then directs and accumulates flow.

        2. Assesses the location, if any, of flooded nodes where erosion should
           not occur.

        3. Assesses if a :py:mod:`PrecipChanger` is an active boundary handler
           and if so, uses it to modify the erodibility by water.

        4. Updates the spatially variable erodibility value based on the
           relative distance between the topographic surface and the lithology
           contact.

        5. Calculates detachment-limited erosion by water.

        6. Calculates topographic change by linear diffusion.

        7. Finalizes the step using the :py:mod:`ErosionModel` base class
           function **finalize__run_one_step**. This function updates all
           boundary handlers handlers by ``step`` and increments model time by
           ``step``.

        Parameters
        ----------
        step : float
            Increment of time for which the model is run.
        """
        # create and move water
        self.create_and_move_water(step)

        # Get IDs of flooded nodes, if any
        if self.flow_accumulator.depression_finder is None:
            flooded = []
        else:
            flooded = np.where(
                self.flow_accumulator.depression_finder.flood_status == 3
            )[0]

        # Update the erodibility field
        self._update_erodibility_field()

        # Do some erosion (but not on the flooded nodes)
        self.eroder.run_one_step(
            step, flooded_nodes=flooded, K_if_used=self.erody
        )

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node["bedrock__elevation"]
        b[:] = np.minimum(b, self.grid.at_node["topographic__elevation"])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Generate and move soil around
        self.diffuser.run_one_step(step)

        # Finalize the run_one_step_method
        self.finalize__run_one_step(step)
예제 #13
0
class BasicRtSa(ErosionModel):
    """
    A BasicSaRt computes erosion using linear diffusion, basic
    stream power with rock and till layers, and Q~A.

    It creates soil through weathering and consideres soil thickness
    in calculating hillslope diffusion.
    """
    def __init__(self,
                 input_file=None,
                 params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicSaRt."""

        # Call ErosionModel's init
        super(BasicSaRt,
              self).__init__(input_file=input_file,
                             params=params,
                             BaselevelHandlerClass=BaselevelHandlerClass)
        contact_zone__width = (self._length_factor) * self.params[
            'contact_zone__width']  # has units length
        self.K_rock_sp = self.get_parameter_from_exponent('K_rock_sp')
        self.K_till_sp = self.get_parameter_from_exponent('K_till_sp')
        linear_diffusivity = (
            self._length_factor**
            2.) * self.get_parameter_from_exponent('linear_diffusivity')

        # Set up rock-till
        self.setup_rock_and_till(self.params['rock_till_file__name'],
                                 self.K_rock_sp, self.K_till_sp,
                                 contact_zone__width)

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(
            self.grid,
            flow_director='D8',
            depression_finder=DepressionFinderAndRouter)

        # Instantiate a FastscapeEroder component
        self.eroder = FastscapeEroder(self.grid,
                                      K_sp=self.erody,
                                      m_sp=self.params['m_sp'],
                                      n_sp=self.params['n_sp'])

        # Create soil thickness (a.k.a. depth) field
        if 'soil__depth' in self.grid.at_node:
            soil_thickness = self.grid.at_node['soil__depth']
        else:
            soil_thickness = self.grid.add_zeros('node', 'soil__depth')

        # Create bedrock elevation field
        if 'bedrock__elevation' in self.grid.at_node:
            bedrock_elev = self.grid.at_node['bedrock__elevation']
        else:
            bedrock_elev = self.grid.add_zeros('node', 'bedrock__elevation')

        # Set soil thickness and bedrock elevation
        try:
            initial_soil_thickness = (self._length_factor) * self.params[
                'initial_soil_thickness']  # has units length
        except KeyError:
            initial_soil_thickness = 1.0  # default value

        soil_transport_decay_depth = (self._length_factor) * self.params[
            'soil_transport_decay_depth']  # has units length
        max_soil_production_rate = (self._length_factor) * self.params[
            'max_soil_production_rate']  # has units length per time
        soil_production_decay_depth = (self._length_factor) * self.params[
            'soil_production_decay_depth']  # has units length

        soil_thickness[:] = initial_soil_thickness
        bedrock_elev[:] = self.z - initial_soil_thickness

        # Instantiate diffusion and weathering components
        self.diffuser = DepthDependentDiffuser(
            self.grid,
            linear_diffusivity=linear_diffusivity,
            soil_transport_decay_depth=soil_transport_decay_depth)

        self.weatherer = ExponentialWeatherer(
            self.grid,
            max_soil_production_rate=max_soil_production_rate,
            soil_production_decay_depth=soil_production_decay_depth)

    def setup_rock_and_till(self, file_name, rock_erody, till_erody,
                            contact_width):
        """Set up lithology handling for two layers with different erodibility.

        Parameters
        ----------
        file_name : string
            Name of arc-ascii format file containing elevation of contact
            position at each grid node (or NODATA)

        Read elevation of rock-till contact from an esri-ascii format file
        containing the basal elevation value at each node, create a field for
        erodibility.

        Some considerations here:
            1. We could represent the contact between two layers either as a
               depth below present land surface, or as an altitude. Using a
               depth would allow for vertical motion, because for a fixed
               surface, the depth remains constant while the altitude changes.
               But the depth must be updated every time the surface is eroded
               or aggrades. Using an altitude avoids having to update the
               contact position every time the surface erodes or aggrades, but
               any tectonic motion would need to be applied to the contact
               position as well. Here we'll use the altitude approach because
               this model was originally written for an application with lots
               of erosion expected but no tectonics.
        """
        from landlab.io import read_esri_ascii

        # Read input data on rock-till contact elevation
        read_esri_ascii(file_name,
                        grid=self.grid,
                        name='rock_till_contact__elevation',
                        halo=1)

        # Get a reference to the rock-till field
        self.rock_till_contact = self.grid.at_node[
            'rock_till_contact__elevation']

        # Create field for erodibility
        if 'substrate__erodibility' in self.grid.at_node:
            self.erody = self.grid.at_node['substrate__erodibility']
        else:
            self.erody = self.grid.add_zeros('node', 'substrate__erodibility')

        # Create array for erodibility weighting function
        self.erody_wt = np.zeros(self.grid.number_of_nodes)

        # Read the erodibility value of rock and till
        self.rock_erody = rock_erody
        self.till_erody = till_erody

        # Read and remember the contact zone characteristic width
        self.contact_width = contact_width

    def update_erodibility_field(self):
        """Update erodibility at each node based on elevation relative to
        contact elevation.

        To promote smoothness in the solution, the erodibility at a given point
        (x,y) is set as follows:

            1. Take the difference between elevation, z(x,y), and contact
               elevation, b(x,y): D(x,y) = z(x,y) - b(x,y). This number could
               be positive (if land surface is above the contact), negative
               (if we're well within the rock), or zero (meaning the rock-till
               contact is right at the surface).
            2. Define a smoothing function as:
                $F(D) = 1 / (1 + exp(-D/D*))$
               This sigmoidal function has the property that F(0) = 0.5,
               F(D >> D*) = 1, and F(-D << -D*) = 0.
                   Here, D* describes the characteristic width of the "contact
               zone", where the effective erodibility is a mixture of the two.
               If the surface is well above this contact zone, then F = 1. If
               it's well below the contact zone, then F = 0.
            3. Set the erodibility using F:
                $K = F K_till + (1-F) K_rock$
               So, as F => 1, K => K_till, and as F => 0, K => K_rock. In
               between, we have a weighted average.

        Translating these symbols into variable names:

            z = self.elev
            b = self.rock_till_contact
            D* = self.contact_width
            F = self.erody_wt
            K_till = self.till_erody
            K_rock = self.rock_erody
        """

        # Update the erodibility weighting function (this is "F")
        self.erody_wt[self.data_nodes] = (1.0 / (1.0 + np.exp(
            -(self.z[self.data_nodes] -
              self.rock_till_contact[self.data_nodes]) / self.contact_width)))

        # (if we're varying K through time, update that first)
        if self.opt_var_precip:
            erode_factor = self.pc.get_erodibility_adjustment_factor(
                self.model_time)
            self.till_erody = self.K_till_sp * erode_factor
            self.rock_erody = self.K_rock_sp * erode_factor

        # Calculate the effective erodibilities using weighted averaging
        self.erody[:] = (self.erody_wt * self.till_erody +
                         (1.0 - self.erody_wt) * self.rock_erody)

    def run_one_step(self, dt):
        """
        Advance model for one time-step of duration dt.
        """

        # Route flow
        self.flow_router.run_one_step()

        # Get IDs of flooded nodes, if any
        flooded = np.where(
            self.flow_router.depression_finder.flood_status == 3)[0]

        # Update the erodibility field
        self.update_erodibility_field()

        # Do some erosion (but not on the flooded nodes)
        self.eroder.run_one_step(dt,
                                 flooded_nodes=flooded,
                                 K_if_used=self.erody)

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node['bedrock__elevation']
        b[:] = np.minimum(b, self.grid.at_node['topographic__elevation'])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Generate and move soil around
        self.diffuser.run_one_step(dt)

        # calculate model time
        self.model_time += dt

        # Lower outlet
        self.update_outlet(dt)

        # Check walltime
        self.check_walltime()
예제 #14
0
class BasicSaVs(ErosionModel):
    r"""**BasicSaVs** model program.

    This model program combines :py:class:`BasicSa` and :py:class:`BasicVs`.
    Given a spatially varying soil thickness :math:`H` and a spatially varying
    bedrock elevation :math:`\eta_b`, model **BasicSaVs** evolves a topographic
    surface described by :math:`\eta` with the following governing equations:

    .. math::

        \eta = \eta_b + H

        \frac{\partial H}{\partial t} = P_0 \exp (-H/H_s)
                                        - \delta (H) K A_{eff}^{M} S^{N}
                                        - \nabla q_h

        \frac{\partial \eta_b}{\partial t} = -P_0 \exp (-H/H_s)
                                       - (1 - \delta (H) ) K A_{eff}^{m} S^{N}

        q_h = -D H^* \left[1-\exp \left( -\frac{H}{H_0} \right) \right] \nabla \eta

        A_{eff} = A \exp \left( -\frac{-\alpha S}{A}\right)

        \alpha = \frac{K_{sat} H dx}{R_m}

    where :math:`Q` is the local stream discharge, :math:`S` is the local
    slope, :math:`m` and :math:`n` are the discharge and slope exponent
    parameters, :math:`K` is the erodibility by water, :math:`D` is the
    regolith transport parameter, :math:`H_s` is the sediment production decay
    depth, :math:`H_0` is the sediment transport decay depth, :math:`P_0` is
    the maximum sediment production rate, and :math:`H_0` is the sediment
    transport decay depth. :math:`q_h` is the hillslope sediment flux per unit
    width.

    :math:`\alpha` is the saturation area scale used for transforming area into
    effective area :math:`A_{eff}`. It is given as a function of the saturated
    hydraulic conductivity :math:`K_{sat}`, the soil thickness :math:`H`, the
    grid spacing :math:`dx`, and the recharge rate, :math:`R_m`.

    Refer to
    `Barnhart et al. (2019) <https://doi.org/10.5194/gmd-12-1267-2019>`_
    Table 5 for full list of parameter symbols, names, and dimensions.

    The following at-node fields must be specified in the grid:
        - ``topographic__elevation``
        - ``soil__depth``
    """

    _required_fields = ["topographic__elevation", "soil__depth"]

    def __init__(self,
                 clock,
                 grid,
                 m_sp=0.5,
                 n_sp=1.0,
                 water_erodibility=0.0001,
                 regolith_transport_parameter=0.1,
                 soil_production__maximum_rate=0.001,
                 soil_production__decay_depth=0.5,
                 soil_transport_decay_depth=0.5,
                 hydraulic_conductivity=0.1,
                 **kwargs):
        """
        Parameters
        ----------
        clock : terrainbento Clock instance
        grid : landlab model grid instance
            The grid must have all required fields.
        m_sp : float, optional
            Drainage area exponent (:math:`m`). Default is 0.5.
        n_sp : float, optional
            Slope exponent (:math:`n`). Default is 1.0.
        water_erodibility : float, optional
            Water erodibility (:math:`K`). Default is 0.0001.
        regolith_transport_parameter : float, optional
            Regolith transport efficiency (:math:`D`). Default is 0.1.
        soil_production__maximum_rate : float, optional
            Maximum rate of soil production (:math:`P_{0}`). Default is 0.001.
        soil_production__decay_depth : float, optional
            Decay depth for soil production (:math:`H_{s}`). Default is 0.5.
        soil_transport_decay_depth : float, optional
            Decay depth for soil transport (:math:`H_{0}`). Default is 0.5.
        hydraulic_conductivity : float, optional
            Hydraulic conductivity (:math:`K_{sat}`). Default is 0.1.
        **kwargs :
            Keyword arguments to pass to :py:class:`ErosionModel`. Importantly
            these arguments specify the precipitator and the runoff generator
            that control the generation of surface water discharge (:math:`Q`).

        Returns
        -------
        BasicSaVs : model object

        Examples
        --------
        This is a minimal example to demonstrate how to construct an instance
        of model **BasicSaVs**. For more detailed examples, including
        steady-state test examples, see the terrainbento tutorials.

        To begin, import the model class.

        >>> from landlab import RasterModelGrid
        >>> from landlab.values import random
        >>> from terrainbento import Clock, BasicSaVs
        >>> clock = Clock(start=0, stop=100, step=1)
        >>> grid = RasterModelGrid((5,5))
        >>> _ = random(grid, "topographic__elevation")
        >>> _ = random(grid, "soil__depth")

        Construct the model.

        >>> model = BasicSaVs(clock, grid)

        Running the model with ``model.run()`` would create output, so here we
        will just run it one step.

        >>> model.run_one_step(1.)
        >>> model.model_time
        1.0

        """
        # Call ErosionModel"s init
        super(BasicSaVs, self).__init__(clock, grid, **kwargs)

        # ensure Precipitator and RunoffGenerator are vanilla
        self._ensure_precip_runoff_are_vanilla(vsa_precip=True)

        # verify correct fields are present.
        self._verify_fields(self._required_fields)

        # Get Parameters and convert units if necessary:
        self.m = m_sp
        self.n = n_sp
        self.K = water_erodibility

        soil_thickness = self.grid.at_node["soil__depth"]
        bedrock_elev = self.grid.add_zeros("node", "bedrock__elevation")
        bedrock_elev[:] = self.z - soil_thickness

        # Get the effective-area parameter
        self._Kdx = hydraulic_conductivity * self.grid.dx

        # Instantiate a FastscapeEroder component
        self.eroder = FastscapeEroder(
            self.grid,
            discharge_name="surface_water__discharge",
            K_sp=self.K,
            m_sp=self.m,
            n_sp=self.n,
        )

        # Instantiate a DepthDependentDiffuser component
        self.diffuser = DepthDependentDiffuser(
            self.grid,
            linear_diffusivity=regolith_transport_parameter,
            soil_transport_decay_depth=soil_transport_decay_depth,
        )

        self.weatherer = ExponentialWeatherer(
            self.grid,
            soil_production__maximum_rate=soil_production__maximum_rate,
            soil_production__decay_depth=soil_production__decay_depth,
        )

    def _calc_effective_drainage_area(self):
        """Calculate and store effective drainage area."""
        area = self.grid.at_node["drainage_area"]
        slope = self.grid.at_node["topographic__steepest_slope"]
        cores = self.grid.core_nodes

        sat_param = (self._Kdx * self.grid.at_node["soil__depth"] /
                     self.grid.at_node["rainfall__flux"])

        eff_area = area[cores] * (np.exp(
            -sat_param[cores] * slope[cores] / area[cores]))

        self.grid.at_node["surface_water__discharge"][cores] = eff_area

    def run_one_step(self, step):
        """Advance model **BasicVs** for one time-step of duration step.

        The **run_one_step** method does the following:

        1. Directs flow, accumulates drainage area, and calculates effective
           drainage area.

        2. Assesses the location, if any, of flooded nodes where erosion should
           not occur.

        3. Assesses if a :py:mod:`PrecipChanger` is an active boundary handler
           and if so, uses it to modify the erodibility by water.

        4. Calculates detachment-limited erosion by water.

        5. Calculates topographic change by linear diffusion.

        6. Finalizes the step using the :py:mod:`ErosionModel` base class
           function **finalize__run_one_step**. This function updates all
           boundary boundary handlers handlers by ``step`` and increments model time by
           ``step``.

        Parameters
        ----------
        step : float
            Increment of time for which the model is run.
        """
        # create and move water
        self.create_and_move_water(step)

        # Update effective runoff ratio
        self._calc_effective_drainage_area()

        # Get IDs of flooded nodes, if any
        if self.flow_accumulator.depression_finder is None:
            flooded = []
        else:
            flooded = np.where(
                self.flow_accumulator.depression_finder.flood_status == 3)[0]

        # Zero out effective area in flooded nodes
        self.grid.at_node["surface_water__discharge"][flooded] = 0.0

        # Do some erosion (but not on the flooded nodes)
        # (if we're varying K through time, update that first)
        if "PrecipChanger" in self.boundary_handlers:
            self.eroder.K = (self.K * self.boundary_handlers["PrecipChanger"].
                             get_erodibility_adjustment_factor())
        self.eroder.run_one_step(step)

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node["bedrock__elevation"]
        b[:] = np.minimum(b, self.grid.at_node["topographic__elevation"])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Do some soil creep
        self.diffuser.run_one_step(step)

        # Finalize the run_one_step_method
        self.finalize__run_one_step(step)
class BasicVsSa(_ErosionModel):
    """
    A BasicVsSa computes erosion using depth-dependent linear diffusion, basic
    stream power, and Q ~ A exp( -c H S / A); H = soil thickness.

    This "c" parameter has dimensions of length, and is defined as
    c = K dx / R, where K is saturated hydraulic conductivity, dx is grid
    spacing, and R is recharge.
    """

    def __init__(self, input_file=None, params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicVsSa."""

        # Call ErosionModel's init
        super(BasicVsSa, self).__init__(input_file=input_file,
                                        params=params,
                                        BaselevelHandlerClass=BaselevelHandlerClass)

        # Get Parameters and convert units if necessary:
        self.K_sp = self.get_parameter_from_exponent('K_sp')
        linear_diffusivity = (self._length_factor**2.)*self.get_parameter_from_exponent('linear_diffusivity') # has units length^2/time
        try:
            initial_soil_thickness = (self._length_factor)*self.params['initial_soil_thickness'] # has units length
        except KeyError:
            initial_soil_thickness = 1.0  # default value
        soil_transport_decay_depth = (self._length_factor)*self.params['soil_transport_decay_depth']  # has units length
        max_soil_production_rate = (self._length_factor)*self.params['max_soil_production_rate'] # has units length per time
        soil_production_decay_depth = (self._length_factor)*self.params['soil_production_decay_depth']   # has units length

        recharge_rate = (self._length_factor)*self.params['recharge_rate'] # has units length per time
        K_hydraulic_conductivity = (self._length_factor)*self.params['K_hydraulic_conductivity'] # has units length per time

        # Create soil thickness (a.k.a. depth) field
        if 'soil__depth' in self.grid.at_node:
            soil_thickness = self.grid.at_node['soil__depth']
        else:
            soil_thickness = self.grid.add_zeros('node', 'soil__depth')

        # Create bedrock elevation field
        if 'bedrock__elevation' in self.grid.at_node:
            bedrock_elev = self.grid.at_node['bedrock__elevation']
        else:
            bedrock_elev = self.grid.add_zeros('node', 'bedrock__elevation')

        soil_thickness[:] = initial_soil_thickness
        bedrock_elev[:] = self.z - initial_soil_thickness

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(self.grid,
                                           flow_director='D8',
                                           depression_finder = DepressionFinderAndRouter)

        # Add a field for effective drainage area
        if 'effective_drainage_area' in self.grid.at_node:
            self.eff_area = self.grid.at_node['effective_drainage_area']
        else:
            self.eff_area = self.grid.add_zeros('node',
                                                'effective_drainage_area')

        # Get the effective-length parameter
        self.sat_len = (K_hydraulic_conductivity*self.grid.dx)/(recharge_rate)

        # Instantiate a FastscapeEroder component
        self.eroder = StreamPowerEroder(self.grid,
                                        use_Q=self.eff_area,
                                        K_sp=self.K_sp,
                                        m_sp=self.params['m_sp'],
                                        n_sp=self.params['n_sp'])

        # Instantiate a DepthDependentDiffuser component
        self.diffuser = DepthDependentDiffuser(self.grid,
                                               linear_diffusivity=linear_diffusivity,
                                               soil_transport_decay_depth=soil_transport_decay_depth)

        self.weatherer = ExponentialWeatherer(self.grid,
                                              max_soil_production_rate=max_soil_production_rate,
                                              soil_production_decay_depth=soil_production_decay_depth)



    def calc_effective_drainage_area(self):
        """Calculate and store effective drainage area.

        Effective drainage area is defined as:

        $A_{eff} = A \exp ( c H S / A) = A R_r$

        where $H$ is soil thickness, $S$ is downslope-positive steepest
        gradient, $A$ is drainage area, $R_r$ is the runoff ratio, and $c$ is
        the saturation length parameter.
        """

        area = self.grid.at_node['drainage_area']
        slope = self.grid.at_node['topographic__steepest_slope']
        soil = self.grid.at_node['soil__depth']
        cores = self.grid.core_nodes
        self.eff_area[cores] = (area[cores] * (np.exp(-self.sat_len
                                                      * soil[cores]
                                                      * slope[cores]
                                                      / area[cores])))

    def run_one_step(self, dt):
        """
        Advance model for one time-step of duration dt.
        """

        # Route flow
        self.flow_router.run_one_step()

        # Update effective runoff ratio
        self.calc_effective_drainage_area()

        # Zero out effective area in flooded nodes
        self.eff_area[self.flow_router.depression_finder.flood_status==3] = 0.0

        # Do some erosion (but not on the flooded nodes)
        # (if we're varying K through time, update that first)
        if self.opt_var_precip:
            self.eroder.K = (self.K_sp
                             * self.pc.get_erodibility_adjustment_factor(self.model_time))
        self.eroder.run_one_step(dt)

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node['bedrock__elevation']
        b[:] = np.minimum(b, self.grid.at_node['topographic__elevation'])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Do some soil creep
        self.diffuser.run_one_step(dt)

        # calculate model time
        self.model_time += dt

        # Lower outlet
        self.update_outlet(dt)

        # Check walltime
        self.check_walltime()
예제 #16
0
class BasicHySa(ErosionModel):
    """
    A BasicHySa computes erosion using linear diffusion, hybrid alluvium,
    and Q~A.

    It creates soil through weathering and fluvial bedrock erosion,
    and consideres soil thickness in calculating hillslope diffusion.
    """
    def __init__(self,
                 input_file=None,
                 params=None,
                 BaselevelHandlerClass=None):
        """Initialize the BasicSa."""

        # Call ErosionModel's init
        super(BasicHySa,
              self).__init__(input_file=input_file,
                             params=params,
                             BaselevelHandlerClass=BaselevelHandlerClass)

        self.K_br = self.get_parameter_from_exponent('K_rock_sp')
        self.K_sed = self.get_parameter_from_exponent('K_sed_sp')
        linear_diffusivity = (
            self._length_factor**2.) * self.get_parameter_from_exponent(
                'linear_diffusivity')  # has units length^2/time
        v_sc = self.get_parameter_from_exponent(
            'v_sc')  # normalized settling velocity. Unitless.

        linear_diffusivity = (
            self._length_factor**2.) * self.get_parameter_from_exponent(
                'linear_diffusivity')  # has units length^2/time
        try:
            initial_soil_thickness = (self._length_factor) * self.params[
                'initial_soil_thickness']  # has units length
        except KeyError:
            initial_soil_thickness = 1.0  # default value
        soil_transport_decay_depth = (self._length_factor) * self.params[
            'soil_transport_decay_depth']  # has units length
        max_soil_production_rate = (self._length_factor) * self.params[
            'max_soil_production_rate']  # has units length per time
        soil_production_decay_depth = (self._length_factor) * self.params[
            'soil_production_decay_depth']  # has units length

        # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method
        self.flow_router = FlowAccumulator(
            self.grid,
            flow_director='D8',
            depression_finder=DepressionFinderAndRouter)

        #set methods and fields. K's and sp_crits need to be field names
        method = 'simple_stream_power'
        discharge_method = 'discharge_field'
        area_field = None
        discharge_field = 'surface_water__discharge'

        # Instantiate a SPACE component
        self.eroder = Space(self.grid,
                            K_sed=self.K_sed,
                            K_br=self.K_br,
                            F_f=self.params['F_f'],
                            phi=self.params['phi'],
                            H_star=self.params['H_star'],
                            v_s=v_sc,
                            m_sp=self.params['m_sp'],
                            n_sp=self.params['n_sp'],
                            method=method,
                            discharge_method=discharge_method,
                            area_field=area_field,
                            discharge_field=discharge_field,
                            solver=self.params['solver'])

        # Create soil thickness (a.k.a. depth) field
        if 'soil__depth' in self.grid.at_node:
            soil_thickness = self.grid.at_node['soil__depth']
        else:
            soil_thickness = self.grid.add_zeros('node', 'soil__depth')

        # Create bedrock elevation field
        if 'bedrock__elevation' in self.grid.at_node:
            bedrock_elev = self.grid.at_node['bedrock__elevation']
        else:
            bedrock_elev = self.grid.add_zeros('node', 'bedrock__elevation')

        # Set soil thickness and bedrock elevation
        try:
            initial_soil_thickness = self.params['initial_soil_thickness']
        except KeyError:
            initial_soil_thickness = 1.0  # default value
        soil_thickness[:] = initial_soil_thickness
        bedrock_elev[:] = self.z - initial_soil_thickness

        # Instantiate diffusion and weathering components
        self.diffuser = DepthDependentDiffuser(
            self.grid,
            linear_diffusivity=linear_diffusivity,
            soil_transport_decay_depth=soil_transport_decay_depth)

        self.weatherer = ExponentialWeatherer(
            self.grid,
            max_soil_production_rate=max_soil_production_rate,
            soil_production_decay_depth=soil_production_decay_depth)

        self.grid.at_node['soil__depth'][:] = \
            self.grid.at_node['topographic__elevation'] - \
            self.grid.at_node['bedrock__elevation']

    def run_one_step(self, dt):
        """
        Advance model for one time-step of duration dt.
        """

        # Route flow
        self.flow_router.run_one_step()

        # Get IDs of flooded nodes, if any
        flooded = np.where(
            self.flow_router.depression_finder.flood_status == 3)[0]
        #print('There are ' + str(len(flooded)) + ' flooded nodes')

        # Do some erosion (but not on the flooded nodes)
        # (if we're varying K through time, update that first)
        if self.opt_var_precip:
            erode_factor = self.pc.get_erodibility_adjustment_factor(
                self.model_time)
            self.eroder.K_sed = self.K_sed * erode_factor
            self.eroder.K_br = self.K_br * erode_factor

        self.eroder.run_one_step(dt, flooded_nodes=flooded)

        # We must also now erode the bedrock where relevant. If water erosion
        # into bedrock has occurred, the bedrock elevation will be higher than
        # the actual elevation, so we simply re-set bedrock elevation to the
        # lower of itself or the current elevation.
        b = self.grid.at_node['bedrock__elevation']
        b[:] = np.minimum(b, self.grid.at_node['topographic__elevation'])

        # Calculate regolith-production rate
        self.weatherer.calc_soil_prod_rate()

        # Generate and move soil around
        self.diffuser.run_one_step(dt)

        # calculate model time
        self.model_time += dt

        # Lower outlet
        self.update_outlet(dt)

        # Check walltime
        self.check_walltime()

        # Check stability
        self.check_stability()

    def check_stability(self):
        """Check stability and exit if unstable."""
        fields = self.grid.at_node.keys()
        for f in fields:
            if (np.any(np.isnan(self.grid.at_node[f]))
                    or np.any(np.isinf(self.grid.at_node[f]))):

                # model is unstable, write message and exit.
                with open('model_failed.txt', 'w') as f:
                    f.write('This model run became unstable\n')

                exit
예제 #17
0
    #create copy of "old" topography
    z0 = mg.at_node['topographic__elevation'].copy()

    #Call the erosion routines.
    fr.run_one_step()
    lm.map_depressions()
    floodedNodes = np.where(lm.flood_status == 3)[0]
    sp.run_one_step(dt=dt, flooded_nodes=floodedNodes)

    #fetch the nodes where space eroded the bedrock__elevation over topographic__elevation
    #after conversation with charlie shobe:
    b = mg.at_node['bedrock__elevation']
    b[:] = np.minimum(b, mg.at_node['topographic__elevation'])

    #calculate regolith-production rate
    expWeath.calc_soil_prod_rate()

    #Generate and move the soil around.
    DDdiff.run_one_step(dt=dt)

    #run the landform classifier
    lc.run_one_step(elevationStepBin, 300, classtype=classificationType)

    #run lpjguess once at the beginning and then each timestep after the spinup.
    if elapsed_time < spin_up:
        outInt = outIntSpinUp
        if elapsed_time == 0:

            #create all possible landform__ID's in here ONCE before lpjguess is called
            create_all_landforms(upliftRate, totalT, elevationStepBin, mg)
            write_netcdf('./temp_output/current_output.nc',