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 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() 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]), )
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)
def test_raise_kwargs_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 with pytest.raises(TypeError): DepthDependentTaylorDiffuser(mg, diffusivity=1)
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')
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, )
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)
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()