def test_mass_conserve_all_closed_ErosionDeposition(grid, solver, v_s, dt): z_init = grid.at_node["topographic__elevation"].copy() fa = FlowAccumulator(grid) fa.run_one_step() ed = ErosionDeposition(grid, solver=solver, v_s=v_s) ed.run_one_step(dt) dz = z_init - grid.at_node["topographic__elevation"] # For Erosion Deposition, porosity should not have any effect, because # the component operates in terms of bulk-equivalent sediment flux, # erosion, and deposition. assert_array_almost_equal(dz.sum(), 0.0, decimal=10)
def test_erodep_slope_area_with_threshold(): """Test steady state run with Vs = 1 and wc = 0.00001.""" # Set up a 5x5 grid with open boundaries and low initial elevations. rg = RasterModelGrid((5, 5)) z = rg.add_zeros('node', 'topographic__elevation') z[:] = 0.01 * rg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(rg, flow_director='FlowDirectorD8') # test: Vs = 1 K = 0.002 vs = 1.0 U = 0.001 dt = 10.0 wc = 0.0001 # Create the ErosionDeposition component... ed = ErosionDeposition(rg, K=K, phi=0.0, v_s=vs, m_sp=0.5, n_sp=1.0, sp_crit=wc, method='threshold_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver='adaptive') # ... and run it to steady state. for i in range(1000): fa.run_one_step() ed.run_one_step(dt=dt) z[rg.core_nodes] += U * dt # Test the results s = rg.at_node['topographic__steepest_slope'] sa_factor = ((1.0 + vs) * U + wc) / K # approximate sol'n a11 = 2.0 a12 = 1.0 s11 = sa_factor * (a11**-0.5) s12 = sa_factor * (a12**-0.5) assert_equal(np.round(s[11], 2), np.round(s11, 2)) assert_equal(np.round(s[12], 2), np.round(s12, 2))
def test_can_run_with_hex(): """Test that model can run with hex model grid.""" # Set up a 5x5 grid with open boundaries and low initial elevations. mg = HexModelGrid(7, 7) z = mg.add_zeros('node', 'topographic__elevation') z[:] = 0.01 * mg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(mg, flow_director='FlowDirectorSteepest') # Parameter values for test 1 K = 0.001 vs = 0.0001 U = 0.001 dt = 10.0 # Create the ErosionDeposition component... ed = ErosionDeposition(mg, K=K, phi=0.0, v_s=vs, m_sp=0.5, n_sp=1.0, method='simple_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver='adaptive') # ... and run it to steady state. for i in range(2000): fa.run_one_step() ed.run_one_step(dt=dt) z[mg.core_nodes] += U * dt # Test the results s = mg.at_node['topographic__steepest_slope'] sa_factor = (1.0 + vs) * U / K a18 = mg.at_node['drainage_area'][18] a28 = mg.at_node['drainage_area'][28] s = mg.at_node['topographic__steepest_slope'] s18 = sa_factor * (a18**-0.5) s28 = sa_factor * (a28**-0.5) assert_equal(np.round(s[18], 3), np.round(s18, 3)) assert_equal(np.round(s[28], 3), np.round(s28, 3))
def test_erodep_slope_area_shear_stress_scaling(): """Test steady state run with m_sp = 0.33, n_sp=0.67, Vs = 1.""" # Set up a 5x5 grid with open boundaries and low initial elevations. rg = RasterModelGrid((5, 5)) rg.set_closed_boundaries_at_grid_edges(True, True, True, False) z = rg.add_zeros('node', 'topographic__elevation') z[:] = 0.01 * rg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(rg, flow_director='FlowDirectorD8') # test: Vs = 1 K = 0.002 vs = 1.0 U = 0.001 dt = 10.0 # Create the ErosionDeposition component... ed = ErosionDeposition(rg, K=K, phi=0.0, v_s=vs, m_sp=0.33, n_sp=0.67, method='simple_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver='adaptive') # ... and run it to steady state. for i in range(1500): fa.run_one_step() ed.run_one_step(dt=dt) z[rg.core_nodes] += U * dt # Test the results s = rg.at_node['topographic__steepest_slope'] sa_factor = ((1.0 + vs) * U / K)**(1.0 / 0.67) a6 = 5.0 a8 = 3.0 s6 = sa_factor * (a6**-(0.33 / 0.67)) s8 = sa_factor * (a8**-(0.33 / 0.67)) assert_equal(np.round(s[6], 2), np.round(s6, 2)) assert_equal(np.round(s[8], 2), np.round(s8, 2))
def test_erodep_slope_area_small_vs(): """Test steady state run with Vs << 1.""" # Set up a 5x5 grid with open boundaries and low initial elevations. rg = RasterModelGrid((5, 5)) z = rg.add_zeros('node', 'topographic__elevation') z[:] = 0.01 * rg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(rg, flow_director='FlowDirectorD8') # Parameter values for test 1 K = 0.001 vs = 0.0001 U = 0.001 dt = 10.0 # Create the ErosionDeposition component... ed = ErosionDeposition(rg, K=K, phi=0.0, v_s=vs, m_sp=0.5, n_sp=1.0, method='simple_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver='adaptive') # ... and run it to steady state. for i in range(1000): fa.run_one_step() ed.run_one_step(dt=dt) z[rg.core_nodes] += U * dt # Test the results s = rg.at_node['topographic__steepest_slope'] sa_factor = (1.0 + vs) * U / K a11 = 2.0 a12 = 1.0 s = rg.at_node['topographic__steepest_slope'] s11 = sa_factor * (a11**-0.5) s12 = sa_factor * (a12**-0.5) assert_equal(np.round(s[11], 3), np.round(s11, 3)) assert_equal(np.round(s[12], 3), np.round(s12, 3))
def test_erodep_slope_area_shear_stress_scaling(): """Test steady state run with m_sp = 0.33, n_sp=0.67, Vs = 1.""" # Set up a 5x5 grid with open boundaries and low initial elevations. rg = RasterModelGrid((5, 5)) rg.set_closed_boundaries_at_grid_edges(True, True, True, False) z = rg.add_zeros("node", "topographic__elevation") z[:] = 0.01 * rg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(rg, flow_director="FlowDirectorD8") # test: Vs = 1 K = 0.002 vs = 1.0 U = 0.001 dt = 10.0 m_sp = 0.33 n_sp = 0.67 # Create the ErosionDeposition component... ed = ErosionDeposition(rg, K=K, phi=0.0, v_s=vs, m_sp=m_sp, n_sp=n_sp, solver="adaptive") # ... and run it to steady state. for i in range(1500): fa.run_one_step() ed.run_one_step(dt=dt) z[rg.core_nodes] += U * dt # Test the results s = rg.at_node["topographic__steepest_slope"] sa_factor = ((1.0 + vs) * U / K)**(1.0 / n_sp) a6 = rg.at_node["drainage_area"][6] a8 = rg.at_node["drainage_area"][8] s6 = sa_factor * (a6**-(m_sp / n_sp)) s8 = sa_factor * (a8**-(m_sp / n_sp)) assert_equal(np.round(s[6], 2), np.round(s6, 2)) assert_equal(np.round(s[8], 2), np.round(s8, 2))
def test_erodep_slope_area_with_threshold(): """Test steady state run with Vs = 1 and wc = 0.00001.""" # Set up a 5x5 grid with open boundaries and low initial elevations. rg = RasterModelGrid((5, 5)) z = rg.add_zeros('node', 'topographic__elevation') z[:] = 0.01 * rg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(rg, flow_director='FlowDirectorD8') # test: Vs = 1 K = 0.002 vs = 1.0 U = 0.001 dt = 10.0 wc = 0.0001 # Create the ErosionDeposition component... ed = ErosionDeposition(rg, K=K, phi=0.0, v_s=vs, m_sp=0.5, n_sp=1.0, sp_crit=wc, method='threshold_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver='adaptive') # ... and run it to steady state. for i in range(1000): fa.run_one_step() ed.run_one_step(dt=dt) z[rg.core_nodes] += U * dt # Test the results s = rg.at_node['topographic__steepest_slope'] sa_factor = ((1.0 + vs) * U + wc) / K # approximate sol'n a11 = 2.0 a12 = 1.0 s11 = sa_factor * (a11 ** -0.5) s12 = sa_factor * (a12 ** -0.5) assert_equal(np.round(s[11], 2), np.round(s11, 2)) assert_equal(np.round(s[12], 2), np.round(s12, 2))
def test_erodep_slope_area_shear_stress_scaling(): """Test steady state run with m_sp = 0.33, n_sp=0.67, Vs = 1.""" # Set up a 5x5 grid with open boundaries and low initial elevations. rg = RasterModelGrid((5, 5)) rg.set_closed_boundaries_at_grid_edges(True, True, True, False) z = rg.add_zeros('node', 'topographic__elevation') z[:] = 0.01 * rg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(rg, flow_director='FlowDirectorD8') # test: Vs = 1 K = 0.002 vs = 1.0 U = 0.001 dt = 10.0 m_sp = 0.33 n_sp = 0.67 # Create the ErosionDeposition component... ed = ErosionDeposition(rg, K=K, phi=0.0, v_s=vs, m_sp=m_sp, n_sp=n_sp, method='simple_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver='adaptive') # ... and run it to steady state. for i in range(1500): fa.run_one_step() ed.run_one_step(dt=dt) z[rg.core_nodes] += U * dt # Test the results s = rg.at_node['topographic__steepest_slope'] sa_factor = ((1.0 + vs) * U / K) ** (1.0 / n_sp) a6 = rg.at_node['drainage_area'][6] a8 = rg.at_node['drainage_area'][8] s6 = sa_factor * (a6 ** -(m_sp / n_sp)) s8 = sa_factor * (a8 ** -(m_sp / n_sp)) assert_equal(np.round(s[6], 2), np.round(s6, 2)) assert_equal(np.round(s[8], 2), np.round(s8, 2))
def test_can_run_with_hex(): """Test that model can run with hex model grid.""" # Set up a 5x5 grid with open boundaries and low initial elevations. mg = HexModelGrid((7, 7)) z = mg.add_zeros("topographic__elevation", at="node") z[:] = 0.01 * mg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(mg, flow_director="FlowDirectorSteepest") # Parameter values for test 1 K = 0.001 vs = 0.0001 U = 0.001 dt = 10.0 # Create the ErosionDeposition component... ed = ErosionDeposition(mg, K=K, v_s=vs, m_sp=0.5, n_sp=1.0, solver="adaptive") # ... and run it to steady state. for _ in range(2000): fa.run_one_step() ed.run_one_step(dt=dt) z[mg.core_nodes] += U * dt # Test the results s = mg.at_node["topographic__steepest_slope"] sa_factor = (1.0 + vs) * U / K a18 = mg.at_node["drainage_area"][18] a28 = mg.at_node["drainage_area"][28] s = mg.at_node["topographic__steepest_slope"] s18 = sa_factor * (a18**-0.5) s28 = sa_factor * (a28**-0.5) testing.assert_equal(np.round(s[18], 3), np.round(s18, 3)) testing.assert_equal(np.round(s[28], 3), np.round(s28, 3))
def test_erodep_slope_area_big_vs(): """Test steady state run with Vs >> 1.""" # Set up a 5x5 grid with open boundaries and low initial elevations. rg = RasterModelGrid((5, 5)) z = rg.add_zeros("node", "topographic__elevation") z[:] = 0.01 * rg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(rg, flow_director="FlowDirectorD8") # Next test: big Vs K = 1.0 vs = 1000.0 U = 0.001 dt = 10.0 # Create the ErosionDeposition component... ed = ErosionDeposition(rg, K=K, phi=0.0, v_s=vs, m_sp=0.5, n_sp=1.0, solver="adaptive") # ... and run it to steady state. for i in range(1000): fa.run_one_step() ed.run_one_step(dt=dt) z[rg.core_nodes] += U * dt # Test the results s = rg.at_node["topographic__steepest_slope"] sa_factor = (1.0 + vs) * U / K a11 = 2.0 a12 = 1.0 s11 = sa_factor * (a11**-0.5) s12 = sa_factor * (a12**-0.5) assert_equal(np.round(s[11], 2), np.round(s11, 2)) assert_equal(np.round(s[12], 2), np.round(s12, 2))
def test_erodep_slope_area_small_vs(): """Test steady state run with Vs << 1.""" # Set up a 5x5 grid with open boundaries and low initial elevations. rg = RasterModelGrid((5, 5)) z = rg.add_zeros('node', 'topographic__elevation') z[:] = 0.01 * rg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(rg, flow_director='FlowDirectorD8') # Parameter values for test 1 K = 0.001 vs = 0.0001 U = 0.001 dt = 10.0 # Create the ErosionDeposition component... ed = ErosionDeposition(rg, K=K, phi=0.0, v_s=vs, m_sp=0.5, n_sp=1.0, method='simple_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver='adaptive') # ... and run it to steady state. for i in range(1000): fa.run_one_step() ed.run_one_step(dt=dt) z[rg.core_nodes] += U * dt # Test the results s = rg.at_node['topographic__steepest_slope'] sa_factor = (1.0 + vs) * U / K a11 = 2.0 a12 = 1.0 s = rg.at_node['topographic__steepest_slope'] s11 = sa_factor * (a11 ** -0.5) s12 = sa_factor * (a12 ** -0.5) assert_equal(np.round(s[11], 3), np.round(s11, 3)) assert_equal(np.round(s[12], 3), np.round(s12, 3))
def test_can_run_with_hex(): """Test that model can run with hex model grid.""" # Set up a 5x5 grid with open boundaries and low initial elevations. mg = HexModelGrid(7, 7) z = mg.add_zeros('node', 'topographic__elevation') z[:] = 0.01 * mg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(mg, flow_director='FlowDirectorSteepest') # Parameter values for test 1 K = 0.001 vs = 0.0001 U = 0.001 dt = 10.0 # Create the ErosionDeposition component... ed = ErosionDeposition(mg, K=K, phi=0.0, v_s=vs, m_sp=0.5, n_sp=1.0, method='simple_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver='adaptive') # ... and run it to steady state. for i in range(2000): fa.run_one_step() ed.run_one_step(dt=dt) z[mg.core_nodes] += U * dt # Test the results s = mg.at_node['topographic__steepest_slope'] sa_factor = (1.0 + vs) * U / K a18 = mg.at_node['drainage_area'][18] a28 = mg.at_node['drainage_area'][28] s = mg.at_node['topographic__steepest_slope'] s18 = sa_factor * (a18 ** -0.5) s28 = sa_factor * (a28 ** -0.5) assert_equal(np.round(s[18], 3), np.round(s18, 3)) assert_equal(np.round(s[28], 3), np.round(s28, 3))
def test_erodep_slope_area_with_vs_unity(): """Test steady state run with Vs = 1.""" # Set up a 5x5 grid with open boundaries and low initial elevations. rg = RasterModelGrid((5, 5)) z = rg.add_zeros("node", "topographic__elevation") z[:] = 0.01 * rg.x_of_node # Create a D8 flow handler fa = FlowAccumulator(rg, flow_director="FlowDirectorD8") # test: Vs = 1 K = 0.002 vs = 1.0 U = 0.001 dt = 10.0 # Create the ErosionDeposition component... ed = ErosionDeposition( rg, K=K, phi=0.0, v_s=vs, m_sp=0.5, n_sp=1.0, solver="adaptive" ) # ... and run it to steady state. for i in range(1000): fa.run_one_step() ed.run_one_step(dt=dt) z[rg.core_nodes] += U * dt # Test the results s = rg.at_node["topographic__steepest_slope"] sa_factor = (1.0 + vs) * U / K a11 = 2.0 a12 = 1.0 s11 = sa_factor * (a11 ** -0.5) s12 = sa_factor * (a12 ** -0.5) assert_equal(np.round(s[11], 2), np.round(s11, 2)) assert_equal(np.round(s[12], 2), np.round(s12, 2))
def test_mass_conserve_with_depression_finder_ErosionDeposition( grid2, solver, depression_finder, v_s, dt): assert grid2.status_at_node[1] == grid2.BC_NODE_IS_FIXED_VALUE z_init = grid2.at_node["topographic__elevation"].copy() if depression_finder is None: fa = FlowAccumulator(grid2) else: fa = FlowAccumulator(grid2, depression_finder=depression_finder, routing="D4") fa.run_one_step() ed = ErosionDeposition(grid2, solver=solver, v_s=v_s) ed.run_one_step(dt) dz = grid2.at_node["topographic__elevation"] - z_init # assert that the mass loss over the surface is exported through the one # outlet. net_change = dz[grid2.core_nodes].sum() + (ed.sediment_influx[1] * dt / grid2.cell_area_at_node[11]) assert_array_almost_equal(net_change, 0.0, decimal=10)
threshold_sp=0.0) #k eh erodibilidade, usar 0.0004 #FastscapeEroder(grid, K_sp=0.001, m_sp=0.5, n_sp=1.0, threshold_sp=0.0, discharge_field='drainage_area', erode_flooded_nodes=True) ed = ErosionDeposition( mg, K=0.0004, # Erodibility for substrate (units vary)valor anterior = 0.00001 v_s=0.001, # Effective settling velocity for chosen grain size metric [L/T]. m_sp= 0.5, # Discharge exponent (units vary) usar valores do fast scape (valor anterior = 0.5) n_sp=1.0, #Slope exponent (units vary) usar valores do fast scape sp_crit=0 ) #Critical stream power to erode substrate [E/(TL^2)] usar valores do fast scape lin_diffuse = LinearDiffuser(mg, linear_diffusivity=0.01) uplift_rate = 0.001 time_step = 1000 fr.run_one_step() sp.run_one_step(time_step) for i in range(101): print(i) fr.run_one_step() df.map_depressions() flooded = np.where(df.flood_status == 3)[0] # ver pra que serve ed.run_one_step(time_step) mg.at_node['topographic__elevation'] += time_step * uplift_rate #no artigo ele usa em anos mas esta de acordo com todos os outros parametros em anos tbm #lin_diffuse.run_one_step(time_step) css.run_one_step() if i == 1 or i == 10 or i == 20 or i == 40 or i == 60 or i == 80 or i == 100: files = write_esri_ascii( "../testes_dispersao/" + test_name + "/" + str(i) + ".asc", mg)
class BasicHy(_ErosionModel): """ A BasicHy model computes erosion of sediment and bedrock using dual mass conservation on the bed and in the water column. It applies exponential entrainment rules to account for bed cover. """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the HybridAlluviumModel.""" # Call ErosionModel's init super(BasicHy, self).__init__(input_file=input_file, params=params, BaselevelHandlerClass=BaselevelHandlerClass) # Get Parameters and convert units if necessary: K_sp = self.get_parameter_from_exponent('K_sp', raise_error=False) K_ss = self.get_parameter_from_exponent('K_ss', raise_error=False) # 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_rock_sp and K_rock_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_rock_sp or K_rock_ss must be provided.') # Unit conversion for linear_diffusivity, with units L^2/T linear_diffusivity = ( (self._length_factor**2.) * self.get_parameter_from_exponent('linear_diffusivity')) # Normalized settling velocity (dimensionless) v_sc = self.get_parameter_from_exponent('v_sc') # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method self.flow_router = FlowAccumulator( self.grid, flow_director='D8', depression_finder=DepressionFinderAndRouter) #make area_field and/or discharge_field depending on discharge_method # area_field = self.grid.at_node['drainage_area'] # discharge_field = None # Handle solver option try: solver = self.params['solver'] except: solver = 'original' # Instantiate a Space component self.eroder = ErosionDeposition(self.grid, K=self.K, phi=self.params['phi'], F_f=self.params['F_f'], v_s=v_sc, m_sp=self.params['m_sp'], n_sp=self.params['n_sp'], method='simple_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver=solver) # 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, dynamic_dt=True, flow_director=self.flow_router.flow_director) # 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()
def test_steady_state_with_basic_solver_option(): """ Test that model matches the transport-limited analytical solution for slope/area relationship at steady state: S=((U * v_s) / (K * A^m) + U / (K * A^m))^(1/n). Also test that model matches the analytical solution for steady-state sediment flux: Qs = U * A * (1 - phi). """ #set up a 5x5 grid with one open outlet node and low initial elevations. nr = 5 nc = 5 mg = RasterModelGrid((nr, nc), 10.0) z = mg.add_zeros('node', 'topographic__elevation') mg['node']['topographic__elevation'] += mg.node_y / 100000 \ + mg.node_x / 100000 \ + np.random.rand(len(mg.node_y)) / 10000 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, mg['node']['topographic__elevation'], -9999.) #Instantiate DepressionFinderAndRouter df = DepressionFinderAndRouter(mg) # Create a D8 flow handler fa = FlowAccumulator(mg, flow_director='D8', depression_finder='DepressionFinderAndRouter') # Parameter values for detachment-limited test K = 0.01 U = 0.0001 dt = 1.0 F_f = 0.0 #all sediment is considered coarse bedload m_sp = 0.5 n_sp = 1.0 v_s = 0.5 phi=0.5 # Instantiate the ErosionDeposition component... ed = ErosionDeposition(mg, K=K, F_f=F_f, phi=phi, v_s=v_s, m_sp=m_sp, n_sp=n_sp, sp_crit=0, solver='basic') # ... and run it to steady state (5000x1-year timesteps). for i in range(5000): fa.run_one_step() flooded = np.where(df.flood_status==3)[0] ed.run_one_step(dt=dt, flooded_nodes=flooded) z[mg.core_nodes] += U * dt #m #compare numerical and analytical slope solutions num_slope = mg.at_node['topographic__steepest_slope'][mg.core_nodes] analytical_slope = (np.power(((U * v_s) / (K * np.power(mg.at_node['drainage_area'][mg.core_nodes], m_sp))) + (U / (K * np.power(mg.at_node['drainage_area'][mg.core_nodes], m_sp))), 1./n_sp)) #test for match with analytical slope-area relationship testing.assert_array_almost_equal(num_slope, analytical_slope, decimal=8, err_msg='E/D slope-area test failed', verbose=True) #compare numerical and analytical sediment flux solutions num_sedflux = mg.at_node['sediment__flux'][mg.core_nodes] analytical_sedflux = (U * mg.at_node['drainage_area'][mg.core_nodes] * (1 - phi)) #test for match with anakytical sediment flux testing.assert_array_almost_equal(num_sedflux, analytical_sedflux, decimal=8, err_msg='E/D sediment flux test failed', verbose=True)
class BasicDdHy(_ErosionModel): """ A BasicDdHy computes erosion using 1) the hybrid alluvium component with a threshold that varies with cumulative incision depth, the linear diffusion component. """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """ Initialize the BasicDdHy """ # Call ErosionModel's init super(BasicDdHy, 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) # L2/T * self.get_parameter_from_exponent('linear_diffusivity')) v_s = self.get_parameter_from_exponent('v_sc') # unitless self.sp_crit = ( self._length_factor # L/T * self.get_parameter_from_exponent('erosion__threshold')) # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method self.flow_router = FlowAccumulator( self.grid, flow_director='D8', depression_finder=DepressionFinderAndRouter) # Create a field for the (initial) erosion threshold self.threshold = self.grid.add_zeros('node', 'erosion__threshold') self.threshold[:] = self.sp_crit #starting value # Handle solver option try: solver = self.params['solver'] except: solver = 'original' # Instantiate an ErosionDeposition component self.eroder = ErosionDeposition(self.grid, K=self.K_sp, F_f=self.params['F_f'], phi=self.params['phi'], v_s=v_s, m_sp=self.params['m_sp'], n_sp=self.params['n_sp'], sp_crit='erosion__threshold', method='threshold_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver=solver) # Get the parameter for rate of threshold increase with erosion depth self.thresh_change_per_depth = self.params['thresh_change_per_depth'] # 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] # Calculate cumulative erosion and update threshold cum_ero = self.grid.at_node['cumulative_erosion__depth'] cum_ero[:] = (self.z - self.grid.at_node['initial_topographic__elevation']) self.threshold[:] = (self.sp_crit - (self.thresh_change_per_depth * cum_ero)) self.threshold[self.threshold < self.sp_crit] = self.sp_crit # 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) # calculate model time self.model_time += dt # Lower outlet self.update_outlet(dt) # Check walltime self.check_walltime()
class BasicHyVs(_ErosionModel): """ A BasicHyVs computes erosion using linear diffusion, hybrid alluvium fluvial erosion, and Q ~ A exp( -b S / A). "VSA" stands for "variable source area". """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the BasicHyVs.""" # Call ErosionModel's init super(BasicHyVs, 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 recharge_rate = (self._length_factor * self.params['recharge_rate'] ) # L/T soil_thickness = (self._length_factor * self.params['initial_soil_thickness']) # L K_hydraulic_conductivity = (self._length_factor * self.params['K_hydraulic_conductivity'] ) # has units length per time v_sc = self.get_parameter_from_exponent( 'v_sc') # normalized settling velocity. Unitless. # 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 = 'drainage_area' area_field = 'effective_drainage_area' discharge_field = None # 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-area parameter self.sat_param = ( (K_hydraulic_conductivity * soil_thickness * self.grid.dx) / recharge_rate) # Handle solver option try: solver = self.params['solver'] except KeyError: solver = 'original' # Instantiate a SPACE component self.eroder = ErosionDeposition(self.grid, K=self.K_sp, F_f=self.params['F_f'], phi=self.params['phi'], 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=solver) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser(self.grid, linear_diffusivity=linear_diffusivity) def calc_effective_drainage_area(self): """Calculate and store effective drainage area. Effective drainage area is defined as: $A_{eff} = A \exp ( \alpha S / A) = A R_r$ where $S$ is downslope-positive steepest gradient, $A$ is drainage area, $R_r$ is the runoff ratio, and $\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 self.eff_area[cores] = ( area[cores] * (np.exp(-self.sat_param * 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 # (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) # 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()
def test_phi_affects_transience(): """Test that different porosity values affect the transient case.""" # Set up one 5x5 grid with open boundaries and low initial elevations. mg1 = HexModelGrid((7, 7)) z1 = mg1.add_zeros("topographic__elevation", at="node") z1[:] = 0.01 * mg1.x_of_node # Create a D8 flow handler fa1 = FlowAccumulator(mg1, flow_director="FlowDirectorSteepest") # Parameter values for test 1 K1 = 0.001 vs1 = 0.0001 U1 = 0.001 dt1 = 10.0 phi1 = 0.1 # Create the ErosionDeposition component... ed1 = ErosionDeposition(mg1, K=K1, phi=phi1, v_s=vs1, m_sp=0.5, n_sp=1.0, solver="basic") # ... and run it to steady state. for i in range(200): fa1.run_one_step() ed1.run_one_step(dt=dt1) z1[mg1.core_nodes] += U1 * dt1 # Set up a second 5x5 grid with open boundaries and low initial elevations. mg2 = HexModelGrid((7, 7)) z2 = mg2.add_zeros("topographic__elevation", at="node") z2[:] = 0.01 * mg2.x_of_node # Create a D8 flow handler fa2 = FlowAccumulator(mg2, flow_director="FlowDirectorSteepest") # Parameter values for test 1 K2 = 0.001 vs2 = 0.0001 U2 = 0.001 dt2 = 10.0 phi2 = 0.9 # Create the ErosionDeposition component... ed2 = ErosionDeposition(mg2, K=K2, phi=phi2, v_s=vs2, m_sp=0.5, n_sp=1.0, solver="basic") # ... and run it to steady state. for i in range(200): fa2.run_one_step() ed2.run_one_step(dt=dt2) z2[mg2.core_nodes] += U2 * dt2 # Test the results: higher phi should be lower slope s1 = mg1.at_node["topographic__steepest_slope"][mg1.core_nodes] s2 = mg2.at_node["topographic__steepest_slope"][mg2.core_nodes] testing.assert_array_less(s2, s1)
class BasicHy(ErosionModel): r"""**BasicHy** model program. **BasicHy** is a model program that evolves a topographic surface described by :math:`\eta` with the following governing equation: .. math:: \frac{\partial \eta}{\partial t} = \frac{V Q_s} {Q\left(1 - \phi \right)} - KQ^{m}S^{n} + D\nabla^2 \eta Q_s = \int_0^A \left((1-F_f)KQ(A)^{m}S^{n} - \frac{V Q_s}{Q(A)} \right) dA where :math:`Q` is the local stream discharge, :math:`A` is the local upstream drainage area,: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:`V` is effective sediment settling velocity, :math:`Q_s` is volumetric sediment flux, :math:`r` is a runoff rate, :math:`\phi` is sediment porosity, 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, settling_velocity=0.001, sediment_porosity=0.3, fraction_fines=0.5, solver="basic", **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 Settling velocity of entrained sediment (:math:`V`). 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. solver : str, optional Solver option to pass to the Landlab `ErosionDeposition <https://landlab.readthedocs.io/en/latest/landlab.components.erosion_deposition.html>`__ component. Default is "basic". **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 ------- BasicHy : model object Examples -------- This is a minimal example to demonstrate how to construct an instance of model **BasicHy**. 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, BasicHy >>> clock = Clock(start=0, stop=100, step=1) >>> grid = RasterModelGrid((5,5)) >>> _ = random(grid, "topographic__elevation") Construct the model. >>> model = BasicHy(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(BasicHy, self).__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 # Instantiate a Space component self.eroder = ErosionDeposition( self.grid, K=self.K, phi=sediment_porosity, F_f=fraction_fines, v_s=settling_velocity, m_sp=self.m, n_sp=self.n, discharge_field="surface_water__discharge", solver=solver, ) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser( self.grid, linear_diffusivity=regolith_transport_parameter) def run_one_step(self, step): """Advance model **BasicHy** 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) # 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, dynamic_dt=True, flow_director=self.flow_accumulator.flow_director, ) # Do some soil creep self.diffuser.run_one_step(step) # Finalize the run_one_step_method self.finalize__run_one_step(step)
def test_steady_state_with_basic_solver_option(): """ Test that model matches the transport-limited analytical solution for slope/area relationship at steady state: S=((U * v_s) / (K * A^m) + U / (K * A^m))^(1/n). Also test that model matches the analytical solution for steady-state sediment flux: Qs = U * A * (1 - phi). """ # set up a 5x5 grid with one open outlet node and low initial elevations. nr = 5 nc = 5 mg = RasterModelGrid((nr, nc), xy_spacing=10.0) z = mg.add_zeros("topographic__elevation", at="node") mg["node"]["topographic__elevation"] += ( mg.node_y / 100000 + mg.node_x / 100000 + np.random.rand(len(mg.node_y)) / 10000) 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, mg["node"]["topographic__elevation"], -9999.0) # Create a D8 flow handler fa = FlowAccumulator(mg, flow_director="D8", depression_finder="DepressionFinderAndRouter") # Parameter values for detachment-limited test K = 0.01 U = 0.0001 dt = 1.0 F_f = 0.0 # all sediment is considered coarse bedload m_sp = 0.5 n_sp = 1.0 v_s = 0.5 # Instantiate the ErosionDeposition component... ed = ErosionDeposition( mg, K=K, F_f=F_f, v_s=v_s, m_sp=m_sp, n_sp=n_sp, sp_crit=0, solver="basic", ) # ... and run it to steady state (5000x1-year timesteps). for _ in range(5000): fa.run_one_step() ed.run_one_step(dt=dt) z[mg.core_nodes] += U * dt # m # compare numerical and analytical slope solutions num_slope = mg.at_node["topographic__steepest_slope"][mg.core_nodes] analytical_slope = np.power( ((U * v_s) / (K * np.power(mg.at_node["drainage_area"][mg.core_nodes], m_sp))) + ((U) / (K * np.power(mg.at_node["drainage_area"][mg.core_nodes], m_sp))), 1.0 / n_sp, ) # test for match with analytical slope-area relationship testing.assert_array_almost_equal( num_slope, analytical_slope, decimal=8, err_msg="E/D slope-area test failed", verbose=True, ) # compare numerical and analytical sediment flux solutions num_sedflux = mg.at_node["sediment__flux"][mg.core_nodes] analytical_sedflux = U * mg.at_node["drainage_area"][mg.core_nodes] # test for match with anakytical sediment flux testing.assert_array_almost_equal( num_sedflux, analytical_sedflux, decimal=8, err_msg="E/D sediment flux test failed", verbose=True, )
class BasicHyRt(TwoLithologyErosionModel): r"""**BasicHyRt** model program. This model program combines the :py:class:`BasicRt` and :py:class:`BasicHy` programs by allowing for two lithologies, an "upper" layer and a "lower" layer, stream-power-driven sediment erosion and mass conservation. Given a spatially varying contact zone elevation, :math:`\eta_C(x,y))`, model **BasicHyRt** evolves a topographic surface described by :math:`\eta` with the following governing equations: .. math:: \frac{\partial \eta}{\partial t} = \frac{V Q_s}{Q} - K Q^{m}S^{n} + D\nabla^2 \eta Q_s = \int_0^A \left((1-F_f)KQ(A)^{m}S^{n} - \frac{V Q_s}{Q(A)} \right) dA 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:`A` is the local upstream drainage area, :math:`S` is the local slope, :math:`m` and :math:`n` are the discharge and slope exponen 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:`Q_s` is the volumetric sediment discharge, and :math:`V` is the effective settling velocity of the sediment. :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`` """ _required_fields = [ "topographic__elevation", "lithology_contact__elevation", ] def __init__(self, clock, grid, solver="basic", settling_velocity=0.001, fraction_fines=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. settling_velocity : float, optional Settling velocity of entrained sediment (:math:`V`). Default is 0.001. fraction_fines : float, optional Fraction of fine sediment that is permanently detached (:math:`F_f`). Default is 0.5. solver : str, optional Solver option to pass to the Landlab `ErosionDeposition <https://landlab.readthedocs.io/en/master/reference/components/erosion_deposition.html>`__ component. Default is "basic". **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 ------- BasicHyRt : model object Examples -------- This is a minimal example to demonstrate how to construct an instance of model **BasicHyRt**. 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, BasicHyRt >>> clock = Clock(start=0, stop=100, step=1) >>> grid = RasterModelGrid((5,5)) >>> _ = random(grid, "topographic__elevation") >>> _ = constant(grid, "lithology_contact__elevation", value=-10.) Construct the model. >>> model = BasicHyRt(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 """ # If needed, issue warning on porosity if "sediment_porosity" in kwargs: msg = "sediment_porosity is no longer used by BasicHyRt." raise ValueError(msg) # Call ErosionModel"s init super().__init__(clock, grid, **kwargs) # verify correct fields are present. self._verify_fields(self._required_fields) # Save the threshold values for rock and till self.rock_thresh = 0.0 self.till_thresh = 0.0 # Set up rock-till boundary and associated grid fields. self._setup_rock_and_till_with_threshold() # Instantiate an ErosionDeposition ("hybrid") component self.eroder = ErosionDeposition( self.grid, K="substrate__erodibility", F_f=fraction_fines, v_s=settling_velocity, m_sp=self.m, n_sp=self.n, discharge_field="surface_water__discharge", solver=solver, ) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser( self.grid, linear_diffusivity=self.regolith_transport_parameter) def run_one_step(self, step): """Advance model **BasicHyRt** 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) # Update the erodibility and threshold field self._update_erodibility_and_threshold_fields() # 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 BasicHyVs(ErosionModel): r"""**BasicHyVs** model program. This model program combines :py:class:`BasicHy` and :py:class:`BasicVs` to evolves a topographic surface described by :math:`\eta` with the following governing equations: .. math:: \frac{\partial \eta}{\partial t} = -\left(KQ(A)^{m}S^{n} - \omega_c\left(1-e^{-KQ(A)^{m}S^{n}/\omega_c}\right)\right) + V\frac{Q_s}{Q(A)} + D\nabla^2 \eta Q_s = \int_0^A \left(KQ(A)^{m}S^{n} - \frac{V Q_s}{Q(A)} \right) dA 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, :math:`\omega_c` is the critical stream power needed for erosion to occur, :math:`V` is effective sediment settling velocity, :math:`Q_s` is volumetric sediment flux, and :math:`D` is the regolith transport efficiency. :math:`\alpha` is the saturation area scale used for transforming area into effective area :math:`A_{eff}` (used as discharge). 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, settling_velocity=0.001, fraction_fines=0.5, hydraulic_conductivity=0.1, solver="basic", **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 Settling velocity of entrained sediment (:math:`V`). Default is 0.001. fraction_fines : float, optional Fraction of fine sediment that is permanently detached (:math:`F_f`). Default is 0.5. solver : str, optional Solver option to pass to the Landlab `ErosionDeposition <https://landlab.readthedocs.io/en/master/reference/components/erosion_deposition.html>`__ component. Default is "basic". 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 ------- BasicHyVs : model object Examples -------- This is a minimal example to demonstrate how to construct an instance of model **BasicHy**. 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, BasicHyVs >>> clock = Clock(start=0, stop=100, step=1) >>> grid = RasterModelGrid((5,5)) >>> _ = random(grid, "topographic__elevation") >>> _ = random(grid, "soil__depth") Construct the model. >>> model = BasicHyVs(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 """ # If needed, issue warning on porosity if "sediment_porosity" in kwargs: msg = "sediment_porosity is no longer used by BasicHyVs." raise ValueError(msg) # Call ErosionModel"s init super().__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) self.m = m_sp self.n = n_sp self.K = water_erodibility # Get the effective-area parameter self._Kdx = hydraulic_conductivity * self.grid.dx # Instantiate a SPACE component self.eroder = ErosionDeposition( self.grid, K=self.K, F_f=fraction_fines, v_s=settling_velocity, m_sp=self.m, n_sp=self.n, discharge_field="surface_water__discharge", solver=solver, ) # 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() # Do some erosion # (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)
def test_adaptive_solver_without_depression_handling(): grid = RasterModelGrid((3, 5), xy_spacing=10.0) grid.set_closed_boundaries_at_grid_edges(False, True, False, True) z = grid.add_zeros("node", "topographic__elevation") z[grid.x_of_node < 15.0] = 10.0 fa = FlowAccumulator(grid) ed = ErosionDeposition(grid, solver="adaptive") fa.run_one_step() ed.run_one_step(1.0) assert_array_equal( ed._q, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 200.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ], ) assert_array_equal( ed._erosion_term, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.02, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ], ) assert_array_equal( ed._depo_rate, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.01, 0.01, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ], ) assert_array_equal( ed._qs, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ], ) assert_array_equal( ed._qs_in, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ], )
class BasicHyRt(ErosionModel): """ A BasicHyRt computes erosion using linear diffusion, hybrid alluvium stream erosion, Q~A, and two lithologies: rock and till. """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the BasicHyRt.""" # Call ErosionModel's init super(BasicHyRt, self).__init__(input_file=input_file, params=params, BaselevelHandlerClass=BaselevelHandlerClass) contact_zone__width = (self._length_factor * self.params['contact_zone__width']) # L 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')) v_sc = self.get_parameter_from_exponent( 'v_sc') # normalized settling velocity. Unitless. # Set up rock-till self.setup_rock_and_till(self.params['rock_till_file__name'], rock_erody_br=self.K_rock_sp, till_erody_br=self.K_till_sp, rock_thresh_br=0.0, till_thresh_br=0.0, contact_width=contact_zone__width) # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method self.flow_router = FlowAccumulator( self.grid, flow_director='D8', depression_finder=DepressionFinderAndRouter) # Handle solver option try: solver = self.params['solver'] except: solver = 'original' # Instantiate an ErosionDeposition ("hybrid") component self.eroder = ErosionDeposition(self.grid, K='K_br', F_f=self.params['F_f'], phi=self.params['phi'], v_s=v_sc, m_sp=self.params['m_sp'], n_sp=self.params['n_sp'], method='simple_stream_power', discharge_method='drainage_area', area_field='drainage_area', solver=solver) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser(self.grid, linear_diffusivity=linear_diffusivity) def setup_rock_and_till(self, file_name='file', rock_erody_br=1, till_erody_br=1, rock_thresh_br=0, till_thresh_br=0, contact_width=1): """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) rock_erody : float Water erosion coefficient for bedrock till_erody : float Water erosion coefficient for till rock_thresh : float Water erosion threshold for bedrock till_thresh : float Water erosion threshold for till contact_width : float [L] Characteristic width of the interface zone between rock and till 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. """ 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 rock erodability if 'K_br' in self.grid.at_node: self.erody_br = self.grid.at_node['K_br'] else: self.erody_br = self.grid.add_ones('node', 'K_br') self.erody_br[:] = rock_erody_br # field for rock threshold values if 'sp_crit_br' in self.grid.at_node: self.threshold_br = self.grid.at_node['sp_crit_br'] else: self.threshold_br = self.grid.add_ones('node', 'sp_crit_br') self.threshold_br[:] = rock_thresh_br # Create array for erodibility weighting function for BEDROCK self.erody_wt_br = np.zeros(self.grid.number_of_nodes) # Read the erodibility value of rock and till self.rock_erody_br = rock_erody_br self.till_erody_br = till_erody_br # Read the threshold values for rock and till self.rock_thresh_br = rock_thresh_br self.till_thresh_br = till_thresh_br # Read and remember the contact zone characteristic width self.contact_width = contact_width def update_erodibility_and_threshold_fields(self): """Update erodibility and threshold 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. 4. Threshold values are set similarly. 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_br[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_br = self.K_till_sp * erode_factor self.rock_erody_br = self.K_rock_sp * erode_factor # Calculate the effective BEDROCK erodibilities using weighted averaging self.erody_br[:] = (self.erody_wt_br * self.till_erody_br + (1.0 - self.erody_wt_br) * self.rock_erody_br) # Calculate the effective BEDROCK thresholds using weighted averaging self.threshold_br[:] = (self.erody_wt_br * self.till_thresh_br + (1.0 - self.erody_wt_br) * self.rock_thresh_br) 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 and threshold field self.update_erodibility_and_threshold_fields() # 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()
def test_with_depression_handling(): grid = RasterModelGrid((3, 5), xy_spacing=10.0) grid.set_closed_boundaries_at_grid_edges(False, True, False, True) z = grid.add_zeros("node", "topographic__elevation") z[grid.x_of_node < 15.0] = 10.0 fa = FlowAccumulator(grid, routing="D4", depression_finder="DepressionFinderAndRouter") ed = ErosionDeposition(grid) fa.run_one_step() ed.run_one_step(1.0) assert_array_equal( ed._q, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 200.0, 300.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0, ], ) assert_array_equal( ed._erosion_term, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.02, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ], ) assert_array_almost_equal( ed._depo_rate, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.01, 0.00333333, 0.00166667, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ], ) assert_array_almost_equal( ed._qs, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.66666667, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ], ) assert_array_almost_equal( ed._qs_in, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.66666667, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, ], )