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 __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 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_bad_solver_name(): """ Test that any solver name besides 'basic' and 'adaptive' raises an error. """ #set up a 5x5 grid with one open outlet node and low initial elevations. nr = 5 nc = 5 mg = RasterModelGrid((nr, nc), 10.0) mg.add_zeros('node', 'topographic__elevation') mg['node']['topographic__elevation'] += mg.node_y / 10000 \ + mg.node_x / 10000 \ + 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.) # Create a D8 flow handler fa = FlowAccumulator(mg, flow_director='D8') #try to instantiate ErodionDeposition using a wrong solver name with pytest.raises(ValueError): ErosionDeposition(mg, K=0.01, phi=0.0, v_s=0.001, m_sp=0.5, n_sp=1.0, sp_crit=0, F_f=0.0, solver='something_else')
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 # 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_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_extra_kwd_error_raised(): mg = RasterModelGrid((10, 10)) z = mg.add_zeros("topographic__elevation", at="node") z += mg.x_of_node + mg.y_of_node fa = FlowAccumulator(mg) fa.run_one_step() with pytest.raises(ValueError): ErosionDeposition(mg, spam=0)
def test_route_to_multiple_error_raised(): mg = RasterModelGrid((10, 10)) z = mg.add_zeros("topographic__elevation", at="node") z += mg.x_of_node + mg.y_of_node fa = FlowAccumulator(mg, flow_director="MFD") fa.run_one_step() with pytest.raises(NotImplementedError): ErosionDeposition(mg, K=0.01, v_s=0.001, m_sp=0.5, n_sp=1.0, sp_crit=0)
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_q_as_array(): """ Test that passing in water discharge as an array results in self.q holding correct values """ # 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) mg.add_zeros("topographic__elevation", at="node") q = np.zeros(mg.number_of_nodes) q[:] += 1.0 # add 1.0 m3/yr of water 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 FlowAccumulator( mg, flow_director="D8", depression_finder="DepressionFinderAndRouter" ) # Instantiate the ErosionDeposition component... ed = ErosionDeposition( mg, K=0.01, F_f=0.0, phi=0.0, v_s=0.001, m_sp=0.5, n_sp=1.0, sp_crit=0.0, discharge_field=q, solver="basic", ) # ensure that ed._q is everywhere equal to 1.0 m3/yr. testing.assert_array_equal( np.ones(mg.number_of_nodes), ed._q, err_msg="E/D discharge array test failed", verbose=True, )
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_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("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_sediment__flux_already_created(): """ Test that an existing sediment flux grid field is not changed by instantiating ErosionDeposition. """ # 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) mg.add_zeros("topographic__elevation", at="node") qs = mg.add_zeros("sediment__flux", at="node") qs[:] += 1.0 # add 1.0 m3/yr of flux 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 FlowAccumulator( mg, flow_director="D8", depression_finder="DepressionFinderAndRouter" ) # Instantiate the ErosionDeposition component... ed = ErosionDeposition( mg, K=0.01, F_f=0.0, phi=0.0, v_s=0.001, m_sp=0.5, n_sp=1.0, sp_crit=0.0, solver="basic", ) # ensure that 'sediment__flux' field is everywhere equal to 1.0 m3/yr. testing.assert_array_equal( np.ones(mg.number_of_nodes), ed._qs, err_msg="E/D sediment flux field test failed", verbose=True, )
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)
def test_q_as_field(): """ Test that passing in water discharge as a grid field results in self.q holding correct values """ #set up a 5x5 grid with one open outlet node and low initial elevations. nr = 5 nc = 5 mg = RasterModelGrid((nr, nc), 10.0) mg.add_zeros('node', 'topographic__elevation') q = mg.add_zeros('node', 'user_imposed_discharge') q[:] += 1.0 #add 1.0 m3/yr of water 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.) # Create a D8 flow handler fa = FlowAccumulator(mg, flow_director='D8', depression_finder='DepressionFinderAndRouter') # Instantiate the ErosionDeposition component... ed = ErosionDeposition(mg, K=0.01, F_f=0.0, phi=0.0, v_s=0.001, m_sp=0.5, n_sp=1.0, sp_crit=0.0, discharge_field='user_imposed_discharge', solver='basic') #ensure that ed.q is everywhere equal to 1.0 m3/yr. testing.assert_array_equal(np.ones(mg.number_of_nodes), ed.q, err_msg='E/D discharge field test failed', verbose=True)
def test_Ff_too_high_vals(): """ Test that instantiating ErosionDeposition with a F_f value > 1 throws a ValueError. """ # 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) 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 FlowAccumulator( mg, flow_director="D8", depression_finder="DepressionFinderAndRouter" ) # Instantiate the ErosionDeposition component... with pytest.raises(ValueError): ErosionDeposition( mg, K=0.01, F_f=2.0, phi=0.5, v_s=0.001, m_sp=0.5, n_sp=1.0, sp_crit=0.0, solver="basic", )
def test_Ff_bad_vals(): """ Test that instantiating ErosionDeposition with a F_f value > 1 throws a ValueError. """ #set up a 5x5 grid with one open outlet node and low initial elevations. nr = 5 nc = 5 mg = RasterModelGrid((nr, nc), 10.0) 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.) # Create a D8 flow handler fa = FlowAccumulator(mg, flow_director='D8', depression_finder='DepressionFinderAndRouter') # Instantiate the ErosionDeposition component... with pytest.raises(ValueError): ErosionDeposition(mg, K=0.01, F_f=2.0, phi=0.5, v_s=0.001, m_sp=0.5, n_sp=1.0, sp_crit=0.0, solver='basic')
def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the BasicHySt.""" # Call ErosionModel's init super(BasicHySt, self).__init__(input_file=input_file, params=params, BaselevelHandlerClass=BaselevelHandlerClass) # Get Parameters: K = ((self._length_factor**0.5) # K_stochastic [=] L^(1/2) T^-(1/2) * self.get_parameter_from_exponent('K_stochastic_sp')) linear_diffusivity = ( (self._length_factor**2) * self.get_parameter_from_exponent('linear_diffusivity')) # L^2/T v_s = (self._length_factor) * self.get_parameter_from_exponent( 'v_s') # has units length per time # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method self.flow_router = FlowAccumulator( self.grid, flow_director='D8', depression_finder=DepressionFinderAndRouter) #set methods and fields. method = 'simple_stream_power' discharge_method = 'discharge_field' area_field = None discharge_field = 'surface_water__discharge' # instantiate rain generator self.instantiate_rain_generator() # Add a field for discharge if 'surface_water__discharge' not in self.grid.at_node: self.grid.add_zeros('node', 'surface_water__discharge') self.discharge = self.grid.at_node['surface_water__discharge'] # Get the infiltration-capacity parameter infiltration_capacity = (self._length_factor * self.params['infiltration_capacity']) # L/T self.infilt = infiltration_capacity # Run flow routing and lake filler self.flow_router.run_one_step() # Keep a reference to drainage area self.area = self.grid.at_node['drainage_area'] # Handle solver option try: solver = self.params['solver'] except: solver = 'original' # Instantiate an ErosionDeposition component self.eroder = ErosionDeposition(self.grid, K=K, 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'], 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 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, ], )
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)
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)
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)
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()
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)
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 __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)