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')
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')
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]), )
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
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()
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" )
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)
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()
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()
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
#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',