def test_assertion_error(): """Test that the correct assertion error will be raised.""" mg = RasterModelGrid(10, 10) z = mg.add_zeros('topographic__elevation', at='node') z += 200 + mg.x_of_node + mg.y_of_node + np.random.randn(mg.size('node')) mg.set_closed_boundaries_at_grid_edges(bottom_is_closed=True, left_is_closed=True, right_is_closed=True, top_is_closed=True) mg.set_watershed_boundary_condition_outlet_id(0, z, -9999) fa = FlowAccumulator(mg, flow_director='D8', depression_finder=DepressionFinderAndRouter) sp = FastscapeEroder(mg, K_sp=.0001, m_sp=.5, n_sp=1) ld = LinearDiffuser(mg, linear_diffusivity=0.0001) dt = 100 for i in range(200): fa.run_one_step() flooded = np.where(fa.depression_finder.flood_status==3)[0] sp.run_one_step(dt=dt, flooded_nodes=flooded) ld.run_one_step(dt=dt) mg.at_node['topographic__elevation'][0] -= 0.001 # Uplift assert_raises(AssertionError, analyze_channel_network_and_plot, mg, threshold = 100, starting_nodes = [0], number_of_channels=2)
def test_asking_for_too_many_watersheds(): mg = RasterModelGrid((10, 10)) z = mg.add_zeros("topographic__elevation", at="node") z += 200 + mg.x_of_node + mg.y_of_node mg.set_closed_boundaries_at_grid_edges( bottom_is_closed=True, left_is_closed=True, right_is_closed=True, top_is_closed=True, ) mg.set_watershed_boundary_condition_outlet_id(0, z, -9999) fa = FlowAccumulator(mg, flow_director="D8") sp = FastscapeEroder(mg, K_sp=0.0001, m_sp=0.5, n_sp=1) dt = 100 for i in range(200): fa.run_one_step() sp.run_one_step(dt=dt) mg.at_node["topographic__elevation"][0] -= 0.001 with pytest.raises(ValueError): ChannelProfiler(mg, number_of_watersheds=3) with pytest.raises(ValueError): ChannelProfiler(mg, number_of_watersheds=None, minimum_outlet_threshold=200)
def test_assertion_error(): """Test that the correct assertion error will be raised.""" mg = RasterModelGrid((10, 10)) z = mg.add_zeros("topographic__elevation", at="node") z += 200 + mg.x_of_node + mg.y_of_node + np.random.randn(mg.size("node")) mg.set_closed_boundaries_at_grid_edges( bottom_is_closed=True, left_is_closed=True, right_is_closed=True, top_is_closed=True, ) mg.set_watershed_boundary_condition_outlet_id(0, z, -9999) fa = FlowAccumulator(mg, flow_director="D8", depression_finder=DepressionFinderAndRouter) sp = FastscapeEroder(mg, K_sp=0.0001, m_sp=0.5, n_sp=1, erode_flooded_nodes=True) ld = LinearDiffuser(mg, linear_diffusivity=0.0001) dt = 100 for i in range(200): fa.run_one_step() sp.run_one_step(dt=dt) ld.run_one_step(dt=dt) mg.at_node["topographic__elevation"][0] -= 0.001 # Uplift with pytest.raises(ValueError): ChannelProfiler(mg, outlet_nodes=[0], number_of_watersheds=2)
def test_mask_is_stable(): mg = RasterModelGrid((10, 10)) mg.add_zeros("node", "topographic__elevation") np.random.seed(3542) noise = np.random.rand(mg.size("node")) mg.at_node["topographic__elevation"] += noise fr = FlowAccumulator(mg, flow_director="D8") fsc = FastscapeEroder(mg, K_sp=0.01, m_sp=0.5, n_sp=1) for x in range(2): fr.run_one_step() fsc.run_one_step(dt=10.0) mg.at_node["topographic__elevation"][mg.core_nodes] += 0.01 mask = np.zeros(len(mg.at_node["topographic__elevation"]), dtype=np.uint8) mask[np.where(mg.at_node["drainage_area"] > 0)] = 1 mask0 = mask.copy() dd = DrainageDensity(mg, channel__mask=mask) mask1 = mask.copy() dd.calc_drainage_density() mask2 = mask.copy() assert_array_equal(mask0, mask1) assert_array_equal(mask0[mg.core_nodes], mask2[mg.core_nodes])
def test_assertion_error(): """Test that the correct assertion error will be raised.""" mg = RasterModelGrid(10, 10) z = mg.add_zeros('topographic__elevation', at='node') z += 200 + mg.x_of_node + mg.y_of_node + np.random.randn(mg.size('node')) mg.set_closed_boundaries_at_grid_edges(bottom_is_closed=True, left_is_closed=True, right_is_closed=True, top_is_closed=True) mg.set_watershed_boundary_condition_outlet_id(0, z, -9999) fa = FlowAccumulator(mg, depression_finder=DepressionFinderAndRouter) sp = FastscapeEroder(mg, K_sp=.0001, m_sp=.5, n_sp=1) ld = LinearDiffuser(mg, linear_diffusivity=0.0001) dt = 100 for i in range(200): fa.run_one_step() flooded = np.where(fa.depression_finder.flood_status == 3)[0] sp.run_one_step(dt=dt, flooded_nodes=flooded) ld.run_one_step(dt=dt) mg.at_node['topographic__elevation'][0] -= 0.001 # Uplift assert_raises(AssertionError, analyze_channel_network_and_plot, mg, threshold=100, starting_nodes=[0], number_of_channels=2)
def test_re_calculating_nodes_and_distance(): mg = RasterModelGrid((20, 20), xy_spacing=100) z = mg.add_zeros("topographic__elevation", at="node") z += np.random.rand(z.size) mg.set_closed_boundaries_at_grid_edges( bottom_is_closed=False, left_is_closed=True, right_is_closed=True, top_is_closed=True, ) fa = FlowAccumulator(mg, flow_director="D8") sp = FastscapeEroder(mg, K_sp=0.0001, m_sp=0.5, n_sp=1) dt = 1000 uplift_per_step = 0.001 * dt for i in range(10): z[mg.core_nodes] += uplift_per_step fa.run_one_step() sp.run_one_step(dt=dt) profiler = ChannelProfiler(mg) profiler.run_one_step() assert len(profiler.distance_along_profile) == 1 # result: 1 profiler.run_one_step() # here nathan originally found result: 2, a bug! assert len(profiler.distance_along_profile) == 1 # make the most complicated profile structure profiler = ChannelProfiler(mg, main_channel_only=False, number_of_watersheds=2) profiler.run_one_step() p1 = list(profiler.nodes) d1 = list(profiler.distance_along_profile) profiler.run_one_step() p2 = list(profiler.nodes) d2 = list(profiler.distance_along_profile) # assert that these are copies, not pointers to same thing assert p1 is not p2 assert d1 is not d2 # test that structures are the same. for idx_watershed in range(len(p1)): p1_w = p1[idx_watershed] p2_w = p2[idx_watershed] d1_w = d1[idx_watershed] d2_w = d2[idx_watershed] for idx_segment in range(len(p1_w)): np.testing.assert_array_equal(p1_w[idx_segment], p2_w[idx_segment]) np.testing.assert_array_equal(d1_w[idx_segment], d2_w[idx_segment])
def test_route_to_multiple_error_raised_run_FastscapeEroder(): mg = RasterModelGrid((10, 10)) z = mg.add_zeros('node', 'topographic__elevation') z += mg.x_of_node + mg.y_of_node sp = FastscapeEroder(mg, K_sp=0.1) fa = FlowAccumulator(mg, flow_director='MFD') fa.run_one_step() with pytest.raises(NotImplementedError): sp.run_one_step(10)
def test_route_to_multiple_error_raised_run_FastscapeEroder(): mg = RasterModelGrid((10, 10)) z = mg.add_zeros("node", "topographic__elevation") z += mg.x_of_node + mg.y_of_node sp = FastscapeEroder(mg, K_sp=0.1) fa = FlowAccumulator(mg, flow_director="MFD") fa.run_one_step() with pytest.raises(NotImplementedError): sp.run_one_step(10)
def profile_example_grid(): mg = RasterModelGrid((40, 60)) z = mg.add_zeros("topographic__elevation", at="node") z += 200 + mg.x_of_node + mg.y_of_node mg.set_closed_boundaries_at_grid_edges( bottom_is_closed=True, left_is_closed=True, right_is_closed=True, top_is_closed=True, ) mg.set_watershed_boundary_condition_outlet_id(0, z, -9999) fa = FlowAccumulator(mg, flow_director="D8") sp = FastscapeEroder(mg, K_sp=0.0001, m_sp=0.5, n_sp=1) dt = 100 for i in range(200): fa.run_one_step() sp.run_one_step(dt=dt) mg.at_node["topographic__elevation"][0] -= 0.001 return mg
def test_getting_all_the_way_to_the_divide(main, nshed): np.random.seed(42) mg = RasterModelGrid((10, 12)) z = mg.add_zeros("topographic__elevation", at="node") z += np.random.rand(z.size) fa = FlowAccumulator(mg, flow_director="D8") sp = FastscapeEroder(mg, K_sp=0.0001, m_sp=0.5, n_sp=1) dt = 1000 uplift_per_step = 0.001 * dt for i in range(100): z[mg.core_nodes] += uplift_per_step fa.run_one_step() sp.run_one_step(dt=dt) profiler = ChannelProfiler( mg, number_of_watersheds=nshed, minimum_outlet_threshold=0, main_channel_only=main, minimum_channel_threshold=0, ) profiler.run_one_step() # assert that with minimum_channel_threshold set to zero, we get all the way to the top of the divide. for outlet_id in profiler._data_struct: seg_tuples = profiler._data_struct[outlet_id].keys() wshd_ids = [ profiler._data_struct[outlet_id][seg]["ids"] for seg in seg_tuples ] nodes = np.concatenate(wshd_ids).ravel() da = mg.at_node["drainage_area"][nodes] # if "profile" is just bits of the edge, then da is 0. assert (mg.area_of_cell.min() in da) or (0.0 in da)
class BasicStreamPowerErosionModel(_ErosionModel): """ A BasicStreamPowerErosionModel computes erosion using the simplest form of the unit stream power model. """ def __init__(self, input_file=None, params=None): """Initialize the BasicStreamPowerErosionModel.""" # Call ErosionModel's init super(BasicStreamPowerErosionModel, self).__init__(input_file=input_file, params=params) # Instantiate a FlowRouter and DepressionFinderAndRouter components self.flow_router = FlowRouter(self.grid, **self.params) self.lake_filler = DepressionFinderAndRouter(self.grid, **self.params) # Instantiate a FastscapeEroder component self.eroder = FastscapeEroder(self.grid, K_sp=self.params['K_sp'], m_sp=self.params['m_sp'], n_sp=self.params['n_sp']) def run_one_step(self, dt): """ Advance model for one time-step of duration dt. """ # Route flow self.flow_router.run_one_step() self.lake_filler.map_depressions() # Get IDs of flooded nodes, if any flooded = np.where(self.lake_filler.flood_status == 3)[0] # Do some erosion (but not on the flooded nodes) self.eroder.run_one_step(dt, flooded_nodes=flooded)
class BasicCv(ErosionModel): """ A BasicCV computes erosion using linear diffusion, basic stream power, and Q~A. It also has basic climate change """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the BasicCv model.""" # Call ErosionModel's init super(BasicCv, self).__init__(input_file=input_file, params=params, BaselevelHandlerClass=BaselevelHandlerClass) K_sp = self.get_parameter_from_exponent('K_sp') linear_diffusivity = ( self._length_factor** 2.) * self.get_parameter_from_exponent('linear_diffusivity') self.climate_factor = self.params['climate_factor'] self.climate_constant_date = self.params['climate_constant_date'] time = [0, self.climate_constant_date, self.params['run_duration']] K = [K_sp * self.climate_factor, K_sp, K_sp] self.K_through_time = interp1d(time, K) # 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=K[0], m_sp=self.params['m_sp'], n_sp=self.params['n_sp']) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser(self.grid, linear_diffusivity=linear_diffusivity) 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 erosion based on climate self.eroder.K = float(self.K_through_time(self.model_time)) # Do some erosion (but not on the flooded nodes) self.eroder.run_one_step(dt, flooded_nodes=flooded) # 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 BasicVs(ErosionModel): r"""**BasicVs** model program. This model program evolves a topographic surface, :math:`\eta`, with the following governing equations: .. math:: \frac{\partial \eta}{\partial t} = - K Q^{m}S^{n} + D\nabla^2 \eta Q = 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, and :math:`D` is the regolith transport parameter. :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, 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. 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 ------- BasicVs : model object Examples -------- This is a minimal example to demonstrate how to construct an instance of model **BasicVs**. 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, BasicVs >>> clock = Clock(start=0, stop=100, step=1) >>> grid = RasterModelGrid((5,5)) >>> _ = random(grid, "topographic__elevation") >>> _ = random(grid, "soil__depth") Construct the model. >>> model = BasicVs(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(BasicVs, 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: self.m = m_sp self.n = n_sp self.K = water_erodibility # Add a field for effective drainage area self.grid.at_node["surface_water__discharge"] = self.grid.add_zeros( "node", "effective_drainage_area") # 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 LinearDiffuser component self.diffuser = LinearDiffuser( self.grid, linear_diffusivity=regolith_transport_parameter) 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 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) # Do some soil creep self.diffuser.run_one_step(step) # Finalize the run_one_step_method self.finalize__run_one_step(step)
dt = 0.1 # Timestep in Ma cell_area = mg.dx * mg.dy ###################################### ### run erosion model for one step ### ###################################### print("Running flow router") print(datetime.datetime.now().time()) frr.run_one_step() # flow routing zrold = zr[mg.nodes] # pre-incision elevations print("Running fastscaper") print(datetime.datetime.now().time()) spr.run_one_step(dt) # erosion: stream power zrnew = zr[mg.nodes] # post-incision elevations incise = zrold - zrnew # incision per cell qs = incise * cell_area # Volume sediment produced per cell qsflat = qs.ravel() # flatten qs for flow routing calculation # extract cumulative flux (q) as function of flow length. a, q = find_drainage_area_and_discharge( mg.at_node['flow__upstream_node_order'], mg.at_node['flow__receiver_node'], runoff=qsflat) # a is number of nodes area = mg.at_node['drainage_area'] mg.add_field('node', 'flux', q, noclobber=False) area_threshold = 25 #float(sys.argv[1]) #km2 is_drainage = area > (area_threshold * 1000000) #km2 to m2
class Basic(ErosionModel): r"""**Basic** model program. This model program evolves a topographic surface, :math:`\eta`, with the following governing equation: .. math:: \frac{\partial \eta}{\partial t} = -K Q^{m}S^{n} + D\nabla^2 \eta 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, and :math:`D` is the regolith transport efficiency. 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`` """ _required_fields = ["topographic__elevation"] def __init__( self, clock, grid, m_sp=0.5, n_sp=1.0, water_erodibility=0.0001, regolith_transport_parameter=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. **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 ------- Basic : model object Examples -------- This is a minimal example to demonstrate how to construct an instance of model **Basic**. 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, Basic >>> clock = Clock(start=0, stop=100, step=1) >>> grid = RasterModelGrid((5,5)) >>> _ = random(grid, "topographic__elevation") Construct the model. >>> model = Basic(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) # Get Parameters: self.m = m_sp self.n = n_sp self.K = water_erodibility self.regolith_transport_parameter = regolith_transport_parameter # Instantiate a FastscapeEroder component self.eroder = FastscapeEroder( self.grid, K_sp=self.K, m_sp=self.m, n_sp=self.n, discharge_field="surface_water__discharge", erode_flooded_nodes=self._erode_flooded_nodes, ) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser( self.grid, linear_diffusivity=self.regolith_transport_parameter ) def run_one_step(self, step): """Advance model **Basic** 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. 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 """ # create and move water self.create_and_move_water(step) # If a PrecipChanger is being used, update the eroder"s K value. if "PrecipChanger" in self.boundary_handlers: self.eroder.K = ( self.K * self.boundary_handlers[ "PrecipChanger" ].get_erodibility_adjustment_factor() ) # Do some water erosion (but not on the flooded nodes) self.eroder.run_one_step(step) # Do some soil creep self.diffuser.run_one_step(step) # Finalize the run_one_step_method self.finalize__run_one_step(step)
fr = FlowRouter(mg, method='D8') sp = FastscapeEroder(mg, K_sp=0.00005, m_sp=0.5, n_sp=1.0, threshold_sp=0., rainfall_intensity=1.) k_d = 0.5 lin_diffuse = LinearDiffuser(mg, linear_diffusivity=k_d) # * The calculations are all done in the time loop below. for i in range(nt): fr.run_one_step() # route flow sp.run_one_step(dt) # fluvial incision lin_diffuse.run_one_step(dt) # linear diffusion mg.at_node['topographic__elevation'][ mg.core_nodes] += uplift_per_step # add the uplift if i % 20 == 0: print("Completed loop ", i, " out of ", nt) # ### Visualize the results. # * First we plot the topography after the time loop. # * Second we plot the slope-area relationship, which is often used to # identify hillslopes, channels, and quantify drainage density. plt.figure(1) imshow_grid(mg, 'topographic__elevation',
DHDTLowLim = upliftRate - (upliftRate * 1) DHDTHighLim = upliftRate + (upliftRate * 1) while elapsed_time < totalT: #create copy of "old" topography z0 = mg.at_node['topographic__elevation'].copy() #Call the erosion routines. #expw.run_one_step(dt=dt) #dld.run_one_step(dt=dt) ld.run_one_step(dt=dt) fr.run_one_step() lm.map_depressions() floodedNodes = np.where(lm.flood_status==3)[0] fc.run_one_step(dt=dt, flooded_nodes = floodedNodes) mg.at_node['topographic__elevation'][mg.core_nodes] += uplift_per_step #add uplift mg.at_node['bedrock__elevation'][mg.core_nodes] += uplift_per_step #add uplift #look for nodes where river incises below current soil thickness bad_nodes = mg.at_node['topographic__elevation'] < mg.at_node['bedrock__elevation'] #redefine bedrock to current channel elevation mg.at_node['bedrock__elevation'][bad_nodes] = mg.at_node['topographic__elevation'][bad_nodes] #calculate drainage_density channel_mask = mg.at_node['drainage_area'] > critArea dd = drainage_density.DrainageDensity(mg, channel__mask = channel_mask) mean_dd.append(dd.calc_drainage_density()) #Calculate dhdt and E dh = (mg.at_node['topographic__elevation'] - z0)
# Now that we have performed the tectonic deformation, lets apply our # landscape evolution and watch the landscape change as a result. # Uplift the landscape rmg['node']['topographic__elevation'][rmg.core_nodes] += uplift_rate * dt # set the lower boundary as fixed elevation rmg['node']['topographic__elevation'][rmg.node_y == 0] = 0 # Diffuse the landscape simulating hillslope sediment transport lin_diffuse.run_one_step(dt) # Accumulate and route flow, fill any lakes, and erode under the rivers fr.run_one_step() # route flow DepressionFinderAndRouter.map_depressions(fill) # fill lakes sp.run_one_step(dt) # fastscape stream power eroder ## Calculate the geomorphic metric ## # In the paper, we use a geomorphic metric, BR, to quantify the # reorientation of the channels as time goes on. The code to calculate this # value is below but turned off as it can slow the model. Set the # 'calculate_BR' variable to 'True' if you want to calculate it. if calculate_BR: aspects = rmg.calc_aspect_at_node() # measure pixel aspect # classify and count the number of pixels with certain directions asp_0_45 = float(np.logical_and(aspects >= 0, aspects <= 45).sum()) asp_45_135 = float(np.logical_and(aspects > 45, aspects <= 135).sum())
class BasicCh(_ErosionModel): """ A BasicCh computes erosion using cubic diffusion, basic stream power, and Q~A. """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the BasicCh.""" # Call ErosionModel's init super(BasicCh, 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 # 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 LinearDiffuser component self.diffuser = TaylorNonLinearDiffuser(self.grid, linear_diffusivity=linear_diffusivity, slope_crit=self.params['slope_crit'], 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) # 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()
#Create boundary conditions of the model grid (either closed or fixed-head) for edge in (mg.nodes_at_left_edge,mg.nodes_at_right_edge, mg.nodes_at_top_edge): mg.status_at_node[edge] = CLOSED_BOUNDARY for edge in (mg.nodes_at_bottom_edge): mg.status_at_node[edge] = FIXED_VALUE_BOUNDARY #Initialize Fastscape fc = FastscapeEroder(mg, K_sp = ksp , m_sp = msp, n_sp = nsp, rainfall_intensity = 1) fr = FlowRouter(mg) lm = DepressionFinderAndRouter(mg) for i in range(nSteps): fr.run_one_step(dt=1) lm.map_depressions() fc.run_one_step(dt=1) mg.at_node['topographic__elevation'][mg.core_nodes] += 0.0002 z = mg.at_node['topographic__elevation'] plt.figure() imshow_grid(mg,z) plt.savefig('test.png') plt.close() np.save('iniTopo',z)
surface="topographic__elevation", fill_surface="topographic__elevation", redirect_flow_steepest_descent=False, reaccumulate_flow=False, track_lakes=False, ignore_overfill=True, ) dfr = DepressionFinderAndRouter(grid) ld = LinearDiffuser(grid, D) sp = FastscapeEroder(grid, K_sp=Ksp, m_sp=0.5, n_sp=1.0, threshold_sp=E0) for i in range(N): dfr._find_pits() if dfr._number_of_pits > 0: lmb.run_one_step() z[grid.core_nodes] += U * dt ld.run_one_step(dt) fa.run_one_step() sp.run_one_step(dt) print('completed loop %d' % i) if i % output_interval == 0: print('finished iteration %d' % i) filename = base_path + '%d_grid_%d.nc' % (ID, i) to_netcdf(grid, filename, include="at_node:topographic__elevation")
x, y, z = gaussian_hill_elevation(n) z = z * 100 mg = RasterModelGrid((n, n), node_spacing) gh_org = mg.add_field('node', 'topographic__elevation', z, units='meters', copy=True, clobber=False) fr = FlowAccumulator(mg, flow_director='D8') fse = FastscapeEroder(mg, K_sp=1e-3, m_sp=0.5, n_sp=1.) fr.run_one_step() fse.run_one_step(dt=1000.) fg7 = pl.figure() imshow_grid( mg, 'topographic__elevation', plot_name= 'Gaussian Hill after one time step with K_sp=1e-5, m_sp=0.5, n_sp=1 (FastScape)', allow_colorbar=True) pl.figure() pl.imshow(np.log10(np.reshape(fr.node_drainage_area, (n, n)))) cb = pl.colorbar() cb.set_label('Log10 Flowaccumulation D8') gh_fse = fse.grid.node_vector_to_raster(gh_org, flip_vertically=True)
class BasicCh(ErosionModel): r"""**BasicCh** model program. This model program evolves a topographic surface, :math:`\eta`, with the following governing equation: .. math:: \frac{\partial \eta}{\partial t} = -KQ^{m}S^{n} + \nabla^2 q_h q_h = -DS \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 efficiency, and :math:`S_c` is the critical slope. :math:`q_h` represents the hillslope sediment flux per unit width. :math:`N` is the number of terms in the Taylor Series expansion. 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`` """ _required_fields = ["topographic__elevation"] 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, **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. **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 ------- BasicCh : model object Examples -------- This is a minimal example to demonstrate how to construct an instance of model **BasicCh**. 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, BasicCh >>> clock = Clock(start=0, stop=100, step=1) >>> grid = RasterModelGrid((5,5)) >>> _ = random(grid, "topographic__elevation") Construct the model. >>> model = BasicCh(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(BasicCh, self).__init__(clock, grid, **kwargs) # 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 regolith_transport_parameter = regolith_transport_parameter # 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 NonLinearDiffuser component self.diffuser = TaylorNonLinearDiffuser( self.grid, linear_diffusivity=regolith_transport_parameter, slope_crit=critical_slope, nterms=number_of_taylor_terms, ) def run_one_step(self, step): """Advance model **BasicCh** 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. Calculates topographic change by nonlinear 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) # 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) # 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 BasicCv(ErosionModel): r"""**BasicCv** model program. This model program evolves a topographic surface, :math:`\eta`, with the following governing equation: .. math:: \frac{\partial \eta}{\partial t} = -KQ^{m}S^{n} + D\nabla^2 \eta where :math:`K` is the fluviel erodibility coefficient, :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, and :math:`D` is the regolith transport parameter. This model also has a basic parameterization of climate change such that :math:`K` varies through time. Between model run onset and a time at which the climate becomes constant, the value of :math:`K` linearly changes from :math:`fK` to :math:`K`, at which point it remains at :math:`K` for the remainder of the modeling time period. 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`` """ _required_fields = ["topographic__elevation"] def __init__(self, clock, grid, m_sp=0.5, n_sp=1.0, water_erodibility=0.0001, regolith_transport_parameter=0.1, climate_factor=0.5, climate_constant_date=0.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. climate_factor : float, optional. Default is 0.5.(:math:`f` ) climate_constant_date : float, optional. Model time at which climate becomes constant (:math:`T_s`) and water erodibility stabilizes at a value of :math:`K`. Default is 0.0. **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 ------- Basic : model object Examples -------- This is a minimal example to demonstrate how to construct an instance of model ``Basic``. 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, BasicCv >>> clock = Clock(start=0, stop=100, step=1) >>> grid = RasterModelGrid((5,5)) >>> _ = random(grid, "topographic__elevation") Construct the model. >>> model = BasicCv(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) self.m = m_sp self.n = n_sp self.climate_factor = climate_factor self.climate_constant_date = climate_constant_date time = [ 0, self.climate_constant_date, self.clock.stop + self.clock.step, ] K = [ water_erodibility * self.climate_factor, water_erodibility, water_erodibility, ] self.K_through_time = interp1d(time, K) # Instantiate a FastscapeEroder component self.eroder = FastscapeEroder( self.grid, K_sp=K[0], m_sp=self.m, n_sp=self.n, discharge_field="surface_water__discharge", erode_flooded_nodes=self._erode_flooded_nodes, ) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser( self.grid, linear_diffusivity=regolith_transport_parameter) def run_one_step(self, step): """Advance model ``Basic`` 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. Updates detachment-limited erosion based on climate. 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 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 erosion based on climate self.eroder.K = float(self.K_through_time(self.model_time)) # Do some erosion (but not on the flooded nodes) self.eroder.run_one_step(step) # Do some soil creep self.diffuser.run_one_step(step) # Finalize the run_one_step_method self.finalize__run_one_step(step)
class Basic(_ErosionModel): """ A Basic computes erosion using linear diffusion, basic stream power, and Q~A. """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the Basic model.""" # Call ErosionModel's init super(Basic, self).__init__(input_file=input_file, params=params, BaselevelHandlerClass=BaselevelHandlerClass) # Get Parameters: K_sp = self.get_parameter_from_exponent('K_sp', raise_error=False) K_ss = self.get_parameter_from_exponent('K_ss', raise_error=False) linear_diffusivity = ( self._length_factor**2.) * self.get_parameter_from_exponent( 'linear_diffusivity') # has units length^2/time # check that a stream power and a shear stress parameter have not both been given if K_sp != None and K_ss != None: raise ValueError('A parameter for both K_sp and K_ss has been' 'provided. Only one of these may be provided') elif K_sp != None or K_ss != None: if K_sp != None: self.K = K_sp else: self.K = (self._length_factor**( 1. / 3.)) * K_ss # K_ss has units Lengtg^(1/3) per Time else: raise ValueError('A value for K_sp or K_ss must be provided.') # run the sink filler, only on initiation. sink_filler = SinkFiller(self.grid, apply_slope=True, fill_slope=1e-3) sink_filler.run_one_step() # 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, m_sp=self.params['m_sp'], n_sp=self.params['n_sp']) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser(self.grid, linear_diffusivity=linear_diffusivity) 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 * self.pc.get_erodibility_adjustment_factor(self.model_time)) self.eroder.run_one_step(dt, flooded_nodes=flooded) # 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()
from matplotlib import pyplot as plt ## Make a grid that is 100 by 100 with dx=dy=100. m rmg1 = RasterModelGrid((100, 100), 100.) ## Add elevation field to the grid. z1 = rmg1.add_ones('node', 'topographic__elevation') ## Instantiate process components ld1 = LinearDiffuser(rmg1, linear_diffusivity=0.1) fr1 = FlowRouter(rmg1, method='D8') fse1 = FastscapeEroder(rmg1, K_sp=1e-5, m_sp=0.5, n_sp=1.) ## Set some variables rock_up_rate = 1e-3 #m/yr dt = 1000 # yr rock_up_len = dt * rock_up_rate # m ## Time loop where evolution happens for i in range(500): z1[rmg1.core_nodes] += rock_up_len #uplift only the core nodes ld1.run_one_step(dt) #linear diffusion happens. fr1.run_one_step() #flow routing happens, time step not needed fse1.run_one_step(dt) #fluvial incision happens ## optional print statement print('i', i) ## Plotting the topography plt.figure(1) imshow_grid(rmg1, 'topographic__elevation') #need to run for about 4000 time steps, or 4,000,000 years to reach SS
class FloodWorld(ActiveCellWorld): """ FloodWorld class. This ActiveCellWorld implementation uses Landlab to simulate floods. :param world_size: :param grid_size: :param torus: :param agent_dynamic: """ def __init__(self, world_size, grid_size, torus, agent_dynamic): super(FloodWorld, self).__init__(world_size, grid_size, torus, agent_dynamic) # World generation parameters self.vertical_scale = 200 # (m) self.inputs = {'nrows': 100, 'ncols': 100, 'dx': 0.02, 'dt': 0.5, 'total_time': 50.0, 'uplift_rate': 0.001, 'K_sp': 0.3, 'm_sp': 0.5, 'n_sp': 1.0, 'rock_density': 2.7, 'sed_density': 2.7, 'linear_diffusivity': 0.0001} # Flood parameters self.h_init = 5 # initial thin layer of water (m) self.n = 0.01 # roughness coefficient, (s/m^(1/3)) self.alpha = 0.7 # time-step factor (nondimensional; from Bates et al., 2010) self.u = 0.4 # constant velocity (m/s, de Almeida et al., 2012) self.cell_update_period = 10 # (s) self.mg = None self.fr = None self.sp = None self.of = None self.swd = None self.z = None self.flood_threshold = 0.05 # minimum water depth detected as flood (m) def reset(self): """ Reset function. Resets the world to its initial state. """ super(FloodWorld, self).reset() # Terrain generation shape = (self.grid_size,) * 2 size = self.grid_size * self.grid_size # Set initial simple topography z = self.vertical_scale * self.simple_topography(shape) # Create raster model if it does not exist if self.mg is None: self.mg = RasterModelGrid(shape, int(round(self.world_size/self.grid_size))) self.mg.set_closed_boundaries_at_grid_edges(True, True, True, True) self.z = self.mg.add_field('node', 'topographic__elevation', z) self.swd = self.mg.add_zeros('node', 'surface_water__depth') else: self.mg.at_node['topographic__elevation'] = z self.swd[:] = np.zeros(size) import matplotlib.pyplot as plt plt.figure() from landlab.plot import imshow_grid imshow_grid(self.mg, 'topographic__elevation', colorbar_label='m') plt.draw() # Set evolution parameters uplift_rate = self.inputs['uplift_rate'] total_t = self.inputs['total_time'] dt = self.inputs['dt'] nt = int(total_t // dt) # Loops uplift_per_step = uplift_rate * dt self.fr = FlowAccumulator(self.mg, **self.inputs) self.sp = FastscapeEroder(self.mg, **self.inputs) # Erode terrain for i in range(nt): self.fr.run_one_step() # Not time sensitive self.sp.run_one_step(dt) self.mg.at_node['topographic__elevation'][self.mg.core_nodes] += uplift_per_step # add the uplift if i % 10 == 0: print('Erode: Completed loop %d' % i) plt.figure() imshow_grid(self.mg, 'topographic__elevation', colorbar_label='m') plt.draw() plt.show() # Setup surface water flow self.of = OverlandFlow(self.mg, steep_slopes=True, mannings_n=0.01) # Setup initial flood self.swd[[5050, 5051, 5150, 5151]] += self.h_init self.active = np.greater(np.flip(np.reshape(self.swd, shape), axis=0), self.flood_threshold) def update_cells(self): """ Update cells function. Updates water level and cell status. """ self.of.overland_flow(dt=self.cell_update_period) shape = (self.grid_size,) * 2 self.active = np.greater(np.flip(np.reshape(self.swd, shape), axis=0), self.flood_threshold) @staticmethod def noise_octave(shape, f): """ Noise octave function. Generates a noise map. :param shape: (array) shape of the map. :param f: (float) frequency bounds parameter. """ return te3w_util.fbm(shape, -1, lower=f, upper=(2 * f)) def simple_topography(self, shape): """ Simple topography function. Generates an elevation map. :param shape: (array) shape of the map. """ values = np.zeros(shape) for p in range(1, 10): a = 2 ** p values += np.abs(self.noise_octave(shape, a) - 0.5) / a result = (1.0 - te3w_util.normalize(values)) ** 2 return result
class BasicRtVs(TwoLithologyErosionModel): r"""**BasicRtVs** model program. This model program combines the :py:class:`BasicRt` and :py:class:`BasicVs` programs by allowing for two lithologies, an "upper" layer and a "lower" layer, and using discharge proportional to effective drainage area based on variable source area hydrology. Given a spatially varying contact zone elevation, :math:`\eta_C(x,y))`, model **BasicRtVs** evolves a topographic surface described by :math:`\eta` with the following governing equations: .. math:: \frac{\partial \eta}{\partial t} = - K(\eta,\eta_C) A_{eff}^{m}S^{n} + D\nabla^2 \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)} A_{eff} = A \exp \left( -\frac{-\alpha S}{A}\right) \alpha = \frac{K_{sat} 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:`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:`\alpha` is the saturation area scale used for transforming area into effective area and 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`. :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. 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, 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_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. hydraulic_conductivity : float, optional Hydraulic conductivity (:math:`K_{sat}`). Default is 0.1. **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 ------- BasicRtVs : model object Examples -------- This is a minimal example to demonstrate how to construct an instance of model **BasicRtVs**. 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, BasicRtVs >>> 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 = BasicRtVs(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(BasicRtVs, self).__init__(clock, grid, **kwargs) # ensure Precipitator and RunoffGenerator are vanilla self._ensure_precip_runoff_are_vanilla() # 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() # Get the effective-area parameter self._Kdx = hydraulic_conductivity * self.grid.dx # 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", ) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser( self.grid, linear_diffusivity=self.regolith_transport_parameter ) def _calc_effective_drainage_area(self): r"""Calculate and store effective drainage area. Effective drainage area is defined as: .. math:: A_{eff} = A \exp ( \alpha S / A) = A R_r where :math:`S` is downslope-positive steepest gradient, :math:`A` is drainage area, :math:`R_r` is the runoff ratio, and :math:`\alpha` is the saturation parameter. """ 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 **BasicRtVs** 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. 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) # 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 # Update the erodibility field self._update_erodibility_field() # Do some erosion (but not on the flooded nodes) self.eroder.run_one_step(step) # Do some soil creep self.diffuser.run_one_step(step) # 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 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)
y_uplift = (-1)*np.power(x_uplift-50, 2.) y_uplift = y_uplift - np.min(y_uplift) y_uplift = y_uplift / np.max(y_uplift) y_uplift = y_uplift / 1000 y_uplift2d = np.empty((100,100)) y_uplift2d = np.reshape(np.repeat(y_uplift,100), (100, 100) ) pl.figure() pl.imshow(y_uplift2d) pl.colorbar() fr = FlowAccumulator(mg, flow_director='D8') fse = FastscapeEroder(mg, K_sp = 1e-5, m_sp=0.5, n_sp=1.) dt = 10000. time_steps = 50 for i in range(time_steps): z += np.reshape(y_uplift2d, n*n) * dt #uplift the block nodes fr.run_one_step() fse.run_one_step(dt) pl.figure() imshow_grid(mg, 'topographic__elevation', plot_name='Uplifted block after ts=50,dt=100000 eroded by Stream Power Erosion law with K_sp=1e-4, m_sp=0.5, n_sp=1 (FastScape)', allow_colorbar=True, vmin=np.percentile(z,10), vmax=np.percentile(z,90)) pl.figure() pl.imshow(np.flipud(np.log10(np.reshape(fr.node_drainage_area, (n,n))))) cb=pl.colorbar() cb.set_label('Log10 Flowaccumulation D8 at final step')
class BasicChRt(ErosionModel): """ A BasicChRt model computes erosion using cubic diffusion, basic stream power with two rock units, and Q~A. """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the BasicChRt model.""" # Call ErosionModel's init super(BasicChRt, 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, m_sp=self.params['m_sp'], n_sp=self.params['n_sp'], K_sp=self.erody) # Instantiate a LinearDiffuser component self.diffuser = TaylorNonLinearDiffuser(self.grid, linear_diffusivity=linear_diffusivity, slope_crit=self.params['slope_crit'], nterms=7) 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") D_over_D_star = ((self.z[self.data_nodes] - self.rock_till_contact[self.data_nodes]) / self.contact_width) # truncate D_over_D star to remove potential for overflow in exponent D_over_D_star[D_over_D_star < -100.0] = -100.0 D_over_D_star[D_over_D_star > 100.0] = 100.0 self.erody_wt[self.data_nodes] = (1.0 / (1.0 + np.exp(-D_over_D_star))) # (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) # 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()
DHDTLowLim = upliftRate - (upliftRate * 1) DHDTHighLim = upliftRate + (upliftRate * 1) while elapsed_time < totalT: #create copy of "old" topography z0 = mg.at_node['topographic__elevation'].copy() #Call the erosion routines. #expw.run_one_step(dt=dt) #dld.run_one_step(dt=dt) ld.run_one_step(dt=dt) fr.run_one_step() lm.map_depressions() floodedNodes = np.where(lm.flood_status == 3)[0] fc.run_one_step(dt=dt, flooded_nodes=floodedNodes) mg.at_node['topographic__elevation'][ mg.core_nodes] += uplift_per_step #add uplift mg.at_node['bedrock__elevation'][ mg.core_nodes] += uplift_per_step #add uplift #look for nodes where river incises below current soil thickness bad_nodes = mg.at_node['topographic__elevation'] < mg.at_node[ 'bedrock__elevation'] #redefine bedrock to current channel elevation mg.at_node['bedrock__elevation'][bad_nodes] = mg.at_node[ 'topographic__elevation'][bad_nodes] #calculate drainage_density channel_mask = mg.at_node['drainage_area'] > critArea dd = drainage_density.DrainageDensity(mg, channel__mask=channel_mask)