def test_sp_discharges_new(): input_str = os.path.join(_THIS_DIR, 'test_sp_params_discharge_new.txt') inputs = ModelParameterDictionary(input_str, auto_type=True) nrows = 5 ncols = 5 dx = inputs.read_float('dx') dt = inputs.read_float('dt') mg = RasterModelGrid(nrows, ncols, dx) mg.add_zeros('topographic__elevation', at='node') z = np.array([5., 5., 0., 5., 5., 5., 2., 1., 2., 5., 5., 3., 2., 3., 5., 5., 4., 4., 4., 5., 5., 5., 5., 5., 5.]) mg['node']['topographic__elevation'] = z fr = FlowAccumulator(mg, flow_director='D8') sp = StreamPowerEroder(mg, **inputs) # perform the loop (once!) for i in range(1): fr.run_one_step() sp.run_one_step(dt) z_tg = np.array([5. , 5. , 0. , 5. , 5. , 5. , 1.47759225, 0.43050087, 1.47759225, 5. , 5. , 2.32883687, 1.21525044, 2.32883687, 5. , 5. , 3.27261262, 3.07175015, 3.27261262, 5. , 5. , 5. , 5. , 5. , 5. ]) assert_array_almost_equal(mg.at_node['topographic__elevation'], z_tg)
def test_sp_discharges_new(): input_str = os.path.join(_THIS_DIR, 'test_sp_params_discharge_new.txt') inputs = ModelParameterDictionary(input_str, auto_type=True) nrows = 5 ncols = 5 dx = inputs.read_float('dx') dt = inputs.read_float('dt') mg = RasterModelGrid(nrows, ncols, dx) mg.add_zeros('topographic__elevation', at='node') z = np.array([ 5., 5., 0., 5., 5., 5., 2., 1., 2., 5., 5., 3., 2., 3., 5., 5., 4., 4., 4., 5., 5., 5., 5., 5., 5. ]) mg['node']['topographic__elevation'] = z fr = FlowAccumulator(mg, flow_director='D8') sp = StreamPowerEroder(mg, **inputs) # perform the loop (once!) for i in range(1): fr.run_one_step() sp.run_one_step(dt) z_tg = np.array([ 5., 5., 0., 5., 5., 5., 1.47759225, 0.43050087, 1.47759225, 5., 5., 2.32883687, 1.21525044, 2.32883687, 5., 5., 3.27261262, 3.07175015, 3.27261262, 5., 5., 5., 5., 5., 5. ]) assert_array_almost_equal(mg.at_node['topographic__elevation'], z_tg)
def test_route_to_multiple_error_raised_run_StreamPowerEroder(): mg = RasterModelGrid((10, 10)) z = mg.add_zeros("node", "topographic__elevation") z += mg.x_of_node + mg.y_of_node sp = StreamPowerEroder(mg) fa = FlowAccumulator(mg, flow_director="MFD") fa.run_one_step() with pytest.raises(NotImplementedError): sp.run_one_step(10)
def test_route_to_multiple_error_raised_run_StreamPowerEroder(): mg = RasterModelGrid((10, 10)) z = mg.add_zeros('node', 'topographic__elevation') z += mg.x_of_node + mg.y_of_node sp = StreamPowerEroder(mg) fa = FlowAccumulator(mg, flow_director='MFD') fa.run_one_step() with pytest.raises(NotImplementedError): sp.run_one_step(10)
def test_sp_new(): """ Tests new style component instantiation and run. """ input_str = os.path.join(_THIS_DIR, 'drive_sp_params.txt') inputs = ModelParameterDictionary(input_str, auto_type=True) nrows = inputs.read_int('nrows') ncols = inputs.read_int('ncols') dx = inputs.read_float('dx') dt = inputs.read_float('dt') time_to_run = inputs.read_float('run_time') uplift = inputs.read_float('uplift_rate') init_elev = inputs.read_float('init_elev') mg = RasterModelGrid((nrows, ncols), spacing=(dx, dx)) mg.set_closed_boundaries_at_grid_edges(False, False, True, True) mg.add_zeros('topographic__elevation', at='node') z = mg.zeros(at='node') + init_elev numpy.random.seed(0) mg['node']['topographic__elevation'] = z + \ numpy.random.rand(len(z)) / 1000. fr = FlowAccumulator(mg, flow_director='D8') sp = StreamPowerEroder(mg, **inputs) elapsed_time = 0. while elapsed_time < time_to_run: if elapsed_time + dt > time_to_run: dt = time_to_run - elapsed_time fr.run_one_step() sp.run_one_step(dt) mg.at_node['topographic__elevation'][mg.core_nodes] += uplift * dt elapsed_time += dt z_trg = numpy.array([ 5.48813504e-04, 7.15189366e-04, 6.02763376e-04, 5.44883183e-04, 4.23654799e-04, 6.45894113e-04, 1.01830760e-02, 9.58036770e-03, 6.55865452e-03, 3.83441519e-04, 7.91725038e-04, 1.00142749e-02, 8.80798884e-03, 5.78387585e-03, 7.10360582e-05, 8.71292997e-05, 9.81911417e-03, 9.52243406e-03, 7.55093226e-03, 8.70012148e-04, 9.78618342e-04, 1.00629755e-02, 8.49253798e-03, 5.33216680e-03, 1.18274426e-04, 6.39921021e-04, 9.88956320e-03, 9.47119567e-03, 6.43790696e-03, 4.14661940e-04, 2.64555612e-04, 1.00450743e-02, 8.37262908e-03, 5.21540904e-03, 1.87898004e-05, 6.17635497e-04, 9.21286940e-03, 9.34022513e-03, 7.51114450e-03, 6.81820299e-04, 3.59507901e-04, 6.19166921e-03, 7.10456176e-03, 6.62585507e-03, 6.66766715e-04, 6.70637870e-04, 2.10382561e-04, 1.28926298e-04, 3.15428351e-04, 3.63710771e-04 ]) assert_array_almost_equal(mg.at_node['topographic__elevation'], z_trg)
def test_storms(): dt = 500.0 uplift = 0.0001 mean_duration = 100.0 mean_interstorm = 400.0 mean_depth = 5.0 storm_run_time = 3000000.0 delta_t = 500.0 mg = RasterModelGrid((10, 10), xy_spacing=1000.0) mg.add_zeros("topographic__elevation", at="node") z = mg.zeros(at="node") mg["node"]["topographic__elevation"] = z + np.random.rand(len(z)) / 1000.0 mg.add_zeros("water__unit_flux_in", at="node") precip = PrecipitationDistribution( mean_storm_duration=mean_duration, mean_interstorm_duration=mean_interstorm, mean_storm_depth=mean_depth, total_t=storm_run_time, delta_t=delta_t, ) fr = FlowAccumulator(mg, flow_director="D8") sp = StreamPowerEroder( mg, K_sp=0.0001, m_sp=0.5, n_sp=1.0, threshold_sp=1.0, discharge_field="surface_water__discharge", ) for ( interval_duration, rainfall_rate, ) in precip.yield_storm_interstorm_duration_intensity(): if rainfall_rate != 0.0: mg.at_node["water__unit_flux_in"].fill(rainfall_rate) fr.run_one_step() sp.run_one_step(dt) mg.at_node["topographic__elevation"][mg.core_nodes] += ( uplift * interval_duration)
def test_storms(): input_file_string = os.path.join(_THIS_DIR, "drive_sp_params_storms.txt") inputs = ModelParameterDictionary(input_file_string, auto_type=True) nrows = inputs.read_int("nrows") ncols = inputs.read_int("ncols") dx = inputs.read_float("dx") dt = inputs.read_float("dt") uplift = inputs.read_float("uplift_rate") mean_duration = inputs.read_float("mean_storm") mean_interstorm = inputs.read_float("mean_interstorm") mean_depth = inputs.read_float("mean_depth") storm_run_time = inputs.read_float("storm_run_time") delta_t = inputs.read_float("delta_t") mg = RasterModelGrid(nrows, ncols, xy_spacing=dx) mg.add_zeros("topographic__elevation", at="node") z = mg.zeros(at="node") mg["node"]["topographic__elevation"] = z + np.random.rand(len(z)) / 1000. mg.add_zeros("water__unit_flux_in", at="node") precip = PrecipitationDistribution( mean_storm_duration=mean_duration, mean_interstorm_duration=mean_interstorm, mean_storm_depth=mean_depth, total_t=storm_run_time, delta_t=delta_t, ) fr = FlowAccumulator(mg, flow_director="D8") sp = StreamPowerEroder(mg, **inputs) for ( interval_duration, rainfall_rate, ) in precip.yield_storm_interstorm_duration_intensity(): if rainfall_rate != 0.: mg.at_node["water__unit_flux_in"].fill(rainfall_rate) fr.run_one_step() sp.run_one_step(dt) mg.at_node["topographic__elevation"][mg.core_nodes] += ( uplift * interval_duration )
def test_storms(): input_file_string = os.path.join(_THIS_DIR, "drive_sp_params_storms.txt") inputs = ModelParameterDictionary(input_file_string, auto_type=True) nrows = inputs.read_int("nrows") ncols = inputs.read_int("ncols") dx = inputs.read_float("dx") dt = inputs.read_float("dt") uplift = inputs.read_float("uplift_rate") mean_duration = inputs.read_float("mean_storm") mean_interstorm = inputs.read_float("mean_interstorm") mean_depth = inputs.read_float("mean_depth") storm_run_time = inputs.read_float("storm_run_time") delta_t = inputs.read_float("delta_t") mg = RasterModelGrid((nrows, ncols), xy_spacing=dx) mg.add_zeros("topographic__elevation", at="node") z = mg.zeros(at="node") mg["node"]["topographic__elevation"] = z + np.random.rand(len(z)) / 1000.0 mg.add_zeros("water__unit_flux_in", at="node") precip = PrecipitationDistribution( mean_storm_duration=mean_duration, mean_interstorm_duration=mean_interstorm, mean_storm_depth=mean_depth, total_t=storm_run_time, delta_t=delta_t, ) fr = FlowAccumulator(mg, flow_director="D8") sp = StreamPowerEroder(mg, **inputs) for ( interval_duration, rainfall_rate, ) in precip.yield_storm_interstorm_duration_intensity(): if rainfall_rate != 0.0: mg.at_node["water__unit_flux_in"].fill(rainfall_rate) fr.run_one_step() sp.run_one_step(dt) mg.at_node["topographic__elevation"][mg.core_nodes] += ( uplift * interval_duration)
def test_sp_widths(): input_str = os.path.join(_THIS_DIR, 'test_sp_params_widths.txt') inputs = ModelParameterDictionary(input_str, auto_type=True) nrows = 5 ncols = 5 dx = inputs.read_float('dx') dt = inputs.read_float('dt') mg = RasterModelGrid(nrows, ncols, dx) widths = np.ones(mg.number_of_nodes, dtype=float) mg.add_zeros('topographic__elevation', at='node') z = np.array([5., 5., 0., 5., 5., 5., 2., 1., 2., 5., 5., 3., 2., 3., 5., 5., 4., 4., 4., 5., 5., 5., 5., 5., 5.]) mg['node']['topographic__elevation'] = z fr = FlowAccumulator(mg, flow_director='D8') sp = StreamPowerEroder(mg, use_W=widths, **inputs) # perform the loop (once!) for i in range(1): fr.run_one_step() sqrt_A = mg.at_node['drainage_area']**0.5 widths[mg.core_nodes] = sqrt_A[mg.core_nodes]/sqrt_A[ mg.core_nodes].mean() # so widths has mean=1. # note the issue with drainage_area not defined at perimeter => nans # if not careful... sp.run_one_step(dt) z_tg = np.array([ 5. , 5. , 0. , 5. , 5. , 5. , 1.37222369, 0.36876358, 1.37222369, 5. , 5. , 2.17408606, 1.07986038, 2.17408606, 5. , 5. , 3.08340277, 2.85288049, 3.08340277, 5. , 5. , 5. , 5. , 5. , 5. ]) assert_array_almost_equal(mg.at_node['topographic__elevation'], z_tg)
def test_storms(): input_file_string = os.path.join(_THIS_DIR, 'drive_sp_params_storms.txt') inputs = ModelParameterDictionary(input_file_string, auto_type=True) nrows = inputs.read_int('nrows') ncols = inputs.read_int('ncols') dx = inputs.read_float('dx') dt = inputs.read_float('dt') time_to_run = inputs.read_float('run_time') uplift = inputs.read_float('uplift_rate') mean_duration = inputs.read_float('mean_storm') mean_interstorm = inputs.read_float('mean_interstorm') mean_depth = inputs.read_float('mean_depth') storm_run_time = inputs.read_float('storm_run_time') delta_t = inputs.read_float('delta_t') mg = RasterModelGrid(nrows, ncols, dx) mg.add_zeros('topographic__elevation', at='node') z = mg.zeros(at='node') mg['node']['topographic__elevation'] = z + np.random.rand(len(z)) / 1000. mg.add_zeros('water__unit_flux_in', at='node') precip = PrecipitationDistribution( mean_storm_duration=mean_duration, mean_interstorm_duration=mean_interstorm, mean_storm_depth=mean_depth, total_t=storm_run_time, delta_t=delta_t) fr = FlowAccumulator(mg, flow_director='D8') sp = StreamPowerEroder(mg, **inputs) for (interval_duration, rainfall_rate) in \ precip.yield_storm_interstorm_duration_intensity(): if rainfall_rate != 0.: mg.at_node['water__unit_flux_in'].fill(rainfall_rate) fr.run_one_step() sp.run_one_step(dt) mg.at_node['topographic__elevation'][ mg.core_nodes] += uplift * interval_duration
def test_storms(): input_file_string = os.path.join(_THIS_DIR, 'drive_sp_params_storms.txt') inputs = ModelParameterDictionary(input_file_string, auto_type=True) nrows = inputs.read_int('nrows') ncols = inputs.read_int('ncols') dx = inputs.read_float('dx') dt = inputs.read_float('dt') time_to_run = inputs.read_float('run_time') uplift = inputs.read_float('uplift_rate') mean_duration = inputs.read_float('mean_storm') mean_interstorm = inputs.read_float('mean_interstorm') mean_depth = inputs.read_float('mean_depth') storm_run_time = inputs.read_float('storm_run_time') delta_t = inputs.read_float('delta_t') mg = RasterModelGrid(nrows, ncols, dx) mg.add_zeros('topographic__elevation', at='node') z = mg.zeros(at='node') mg['node']['topographic__elevation'] = z + np.random.rand(len(z)) / 1000. mg.add_zeros('water__unit_flux_in', at='node') precip = PrecipitationDistribution(mean_storm_duration = mean_duration, mean_interstorm_duration = mean_interstorm, mean_storm_depth = mean_depth, total_t = storm_run_time, delta_t = delta_t) fr = FlowAccumulator(mg, flow_director='D8') sp = StreamPowerEroder(mg, **inputs) for (interval_duration, rainfall_rate) in \ precip.yield_storm_interstorm_duration_intensity(): if rainfall_rate != 0.: mg.at_node['water__unit_flux_in'].fill(rainfall_rate) fr.run_one_step() sp.run_one_step(dt) mg.at_node['topographic__elevation'][ mg.core_nodes] += uplift * interval_duration
def test_sp_widths(): input_str = os.path.join(_THIS_DIR, 'test_sp_params_widths.txt') inputs = ModelParameterDictionary(input_str, auto_type=True) nrows = 5 ncols = 5 dx = inputs.read_float('dx') dt = inputs.read_float('dt') mg = RasterModelGrid(nrows, ncols, dx) widths = np.ones(mg.number_of_nodes, dtype=float) mg.add_zeros('topographic__elevation', at='node') z = np.array([ 5., 5., 0., 5., 5., 5., 2., 1., 2., 5., 5., 3., 2., 3., 5., 5., 4., 4., 4., 5., 5., 5., 5., 5., 5. ]) mg['node']['topographic__elevation'] = z fr = FlowAccumulator(mg, flow_director='D8') sp = StreamPowerEroder(mg, use_W=widths, **inputs) # perform the loop (once!) for i in range(1): fr.run_one_step() sqrt_A = mg.at_node['drainage_area']**0.5 widths[mg.core_nodes] = sqrt_A[mg.core_nodes] / sqrt_A[ mg.core_nodes].mean() # so widths has mean=1. # note the issue with drainage_area not defined at perimeter => nans # if not careful... sp.run_one_step(dt) z_tg = np.array([ 5., 5., 0., 5., 5., 5., 1.37222369, 0.36876358, 1.37222369, 5., 5., 2.17408606, 1.07986038, 2.17408606, 5., 5., 3.08340277, 2.85288049, 3.08340277, 5., 5., 5., 5., 5., 5. ]) assert_array_almost_equal(mg.at_node['topographic__elevation'], z_tg)
class BasicRtVs(ErosionModel): """ A BasicVsRt computes erosion using linear diffusion, basic stream power with 2 lithologies, and Q ~ A exp( -b S / A). """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the BasicVsRt.""" # Call ErosionModel's init super(BasicVsRt, self).__init__(input_file=input_file, params=params, BaselevelHandlerClass=BaselevelHandlerClass) contact_zone__width = (self._length_factor) * self.params[ 'contact_zone__width'] # has units length self.K_rock_sp = self.get_parameter_from_exponent('K_rock_sp') self.K_till_sp = self.get_parameter_from_exponent('K_till_sp') linear_diffusivity = ( self._length_factor** 2.) * self.get_parameter_from_exponent('linear_diffusivity') recharge_rate = (self._length_factor) * self.params[ 'recharge_rate'] # has units length per time soil_thickness = (self._length_factor) * self.params[ 'initial_soil_thickness'] # has units length K_hydraulic_conductivity = (self._length_factor) * self.params[ 'K_hydraulic_conductivity'] # has units length per time # Set up rock-till self.setup_rock_and_till(self.params['rock_till_file__name'], self.K_rock_sp, self.K_till_sp, contact_zone__width) # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method self.flow_router = FlowAccumulator( self.grid, flow_director='D8', depression_finder=DepressionFinderAndRouter) # 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) # Instantiate a FastscapeEroder component self.eroder = StreamPowerEroder(self.grid, K_sp=self.erody, m_sp=self.params['m_sp'], n_sp=self.params['n_sp'], use_Q=self.eff_area) # 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 setup_rock_and_till(self, file_name, rock_erody, till_erody, contact_width): """Set up lithology handling for two layers with different erodibility. Parameters ---------- file_name : string Name of arc-ascii format file containing elevation of contact position at each grid node (or NODATA) Read elevation of rock-till contact from an esri-ascii format file containing the basal elevation value at each node, create a field for erodibility. Some considerations here: 1. We could represent the contact between two layers either as a depth below present land surface, or as an altitude. Using a depth would allow for vertical motion, because for a fixed surface, the depth remains constant while the altitude changes. But the depth must be updated every time the surface is eroded or aggrades. Using an altitude avoids having to update the contact position every time the surface erodes or aggrades, but any tectonic motion would need to be applied to the contact position as well. Here we'll use the altitude approach because this model was originally written for an application with lots of erosion expected but no tectonics. """ from landlab.io import read_esri_ascii # Read input data on rock-till contact elevation read_esri_ascii(file_name, grid=self.grid, name='rock_till_contact__elevation', halo=1) # Get a reference to the rock-till field self.rock_till_contact = self.grid.at_node[ 'rock_till_contact__elevation'] # Create field for erodibility if 'substrate__erodibility' in self.grid.at_node: self.erody = self.grid.at_node['substrate__erodibility'] else: self.erody = self.grid.add_zeros('node', 'substrate__erodibility') # Create array for erodibility weighting function self.erody_wt = np.zeros(self.grid.number_of_nodes) # Read the erodibility value of rock and till self.rock_erody = rock_erody self.till_erody = till_erody # Read and remember the contact zone characteristic width self.contact_width = contact_width def update_erodibility_field(self): """Update erodibility at each node based on elevation relative to contact elevation. To promote smoothness in the solution, the erodibility at a given point (x,y) is set as follows: 1. Take the difference between elevation, z(x,y), and contact elevation, b(x,y): D(x,y) = z(x,y) - b(x,y). This number could be positive (if land surface is above the contact), negative (if we're well within the rock), or zero (meaning the rock-till contact is right at the surface). 2. Define a smoothing function as: $F(D) = 1 / (1 + exp(-D/D*))$ This sigmoidal function has the property that F(0) = 0.5, F(D >> D*) = 1, and F(-D << -D*) = 0. Here, D* describes the characteristic width of the "contact zone", where the effective erodibility is a mixture of the two. If the surface is well above this contact zone, then F = 1. If it's well below the contact zone, then F = 0. 3. Set the erodibility using F: $K = F K_till + (1-F) K_rock$ So, as F => 1, K => K_till, and as F => 0, K => K_rock. In between, we have a weighted average. Translating these symbols into variable names: z = self.elev b = self.rock_till_contact D* = self.contact_width F = self.erody_wt K_till = self.till_erody K_rock = self.rock_erody """ # Update the erodibility weighting function (this is "F") core = self.grid.core_nodes if self.contact_width > 0.0: self.erody_wt[core] = ( 1.0 / (1.0 + np.exp(-(self.z[core] - self.rock_till_contact[core]) / self.contact_width))) else: self.erody_wt[core] = 0.0 self.erody_wt[np.where(self.z > self.rock_till_contact)[0]] = 1.0 # (if we're varying K through time, update that first) if self.opt_var_precip: erode_factor = self.pc.get_erodibility_adjustment_factor( self.model_time) self.till_erody = self.K_till_sp * erode_factor self.rock_erody = self.K_rock_sp * erode_factor # Calculate the effective erodibilities using weighted averaging self.erody[:] = (self.erody_wt * self.till_erody + (1.0 - self.erody_wt) * self.rock_erody) def run_one_step(self, dt): """ Advance model for one time-step of duration dt. """ # Route flow self.flow_router.run_one_step() # 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 # Update the erodibility field self.update_erodibility_field() # Do some erosion (but not on the flooded nodes) 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_sp_discharges_new(): dt = 1.0 mg = RasterModelGrid((5, 5)) mg.add_zeros("topographic__elevation", at="node") z = np.array([ 5.0, 5.0, 0.0, 5.0, 5.0, 5.0, 2.0, 1.0, 2.0, 5.0, 5.0, 3.0, 2.0, 3.0, 5.0, 5.0, 4.0, 4.0, 4.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, ]) mg["node"]["topographic__elevation"] = z fr = FlowAccumulator(mg, flow_director="D8") sp = StreamPowerEroder(mg, K_sp=0.5, m_sp=0.5, n_sp=1.0, threshold_sp=0.0) # perform the loop (once!) for i in range(1): fr.run_one_step() sp.run_one_step(dt) z_tg = np.array([ 5.0, 5.0, 0.0, 5.0, 5.0, 5.0, 1.47759225, 0.43050087, 1.47759225, 5.0, 5.0, 2.32883687, 1.21525044, 2.32883687, 5.0, 5.0, 3.27261262, 3.07175015, 3.27261262, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, ]) assert_array_almost_equal(mg.at_node["topographic__elevation"], z_tg)
def test_sp_discharges_new(): input_str = os.path.join(_THIS_DIR, "test_sp_params_discharge_new.txt") inputs = ModelParameterDictionary(input_str, auto_type=True) nrows = 5 ncols = 5 dx = inputs.read_float("dx") dt = inputs.read_float("dt") mg = RasterModelGrid(nrows, ncols, xy_spacing=dx) mg.add_zeros("topographic__elevation", at="node") z = np.array([ 5.0, 5.0, 0.0, 5.0, 5.0, 5.0, 2.0, 1.0, 2.0, 5.0, 5.0, 3.0, 2.0, 3.0, 5.0, 5.0, 4.0, 4.0, 4.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, ]) mg["node"]["topographic__elevation"] = z fr = FlowAccumulator(mg, flow_director="D8") sp = StreamPowerEroder(mg, **inputs) # perform the loop (once!) for i in range(1): fr.run_one_step() sp.run_one_step(dt) z_tg = np.array([ 5.0, 5.0, 0.0, 5.0, 5.0, 5.0, 1.47759225, 0.43050087, 1.47759225, 5.0, 5.0, 2.32883687, 1.21525044, 2.32883687, 5.0, 5.0, 3.27261262, 3.07175015, 3.27261262, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, ]) assert_array_almost_equal(mg.at_node["topographic__elevation"], z_tg)
def test_sp_discharges_old(): input_str = os.path.join(_THIS_DIR, "test_sp_params_discharge.txt") inputs = ModelParameterDictionary(input_str) nrows = 5 ncols = 5 dx = inputs.read_float("dx") dt = inputs.read_float("dt") mg = RasterModelGrid(nrows, ncols, dx) mg.add_zeros("topographic__elevation", at="node") z = np.array([ 5., 5., 0., 5., 5., 5., 2., 1., 2., 5., 5., 3., 2., 3., 5., 5., 4., 4., 4., 5., 5., 5., 5., 5., 5., ]) mg["node"]["topographic__elevation"] = z fr = FlowAccumulator(mg, flow_director="D8") my_Q = mg.at_node["surface_water__discharge"] sp = StreamPowerEroder(mg, input_str, use_Q=my_Q) # perform the loop (once!) for i in range(1): fr.run_one_step() my_Q[:] = mg.at_node["surface_water__discharge"] * 1. sp.run_one_step(dt) z_tg = np.array([ 5., 5., 0., 5., 5., 5., 1.47759225, 0.43050087, 1.47759225, 5., 5., 2.32883687, 1.21525044, 2.32883687, 5., 5., 3.27261262, 3.07175015, 3.27261262, 5., 5., 5., 5., 5., 5., ]) assert_array_almost_equal(mg.at_node["topographic__elevation"], z_tg)
class BasicVsSa(_ErosionModel): """ A BasicVsSa computes erosion using depth-dependent linear diffusion, basic stream power, and Q ~ A exp( -c H S / A); H = soil thickness. This "c" parameter has dimensions of length, and is defined as c = K dx / R, where K is saturated hydraulic conductivity, dx is grid spacing, and R is recharge. """ def __init__(self, input_file=None, params=None, BaselevelHandlerClass=None): """Initialize the BasicVsSa.""" # Call ErosionModel's init super(BasicVsSa, self).__init__(input_file=input_file, params=params, BaselevelHandlerClass=BaselevelHandlerClass) # Get Parameters and convert units if necessary: self.K_sp = self.get_parameter_from_exponent('K_sp') linear_diffusivity = (self._length_factor**2.)*self.get_parameter_from_exponent('linear_diffusivity') # has units length^2/time try: initial_soil_thickness = (self._length_factor)*self.params['initial_soil_thickness'] # has units length except KeyError: initial_soil_thickness = 1.0 # default value soil_transport_decay_depth = (self._length_factor)*self.params['soil_transport_decay_depth'] # has units length max_soil_production_rate = (self._length_factor)*self.params['max_soil_production_rate'] # has units length per time soil_production_decay_depth = (self._length_factor)*self.params['soil_production_decay_depth'] # has units length recharge_rate = (self._length_factor)*self.params['recharge_rate'] # has units length per time K_hydraulic_conductivity = (self._length_factor)*self.params['K_hydraulic_conductivity'] # has units length per time # Create soil thickness (a.k.a. depth) field if 'soil__depth' in self.grid.at_node: soil_thickness = self.grid.at_node['soil__depth'] else: soil_thickness = self.grid.add_zeros('node', 'soil__depth') # Create bedrock elevation field if 'bedrock__elevation' in self.grid.at_node: bedrock_elev = self.grid.at_node['bedrock__elevation'] else: bedrock_elev = self.grid.add_zeros('node', 'bedrock__elevation') soil_thickness[:] = initial_soil_thickness bedrock_elev[:] = self.z - initial_soil_thickness # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method self.flow_router = FlowAccumulator(self.grid, flow_director='D8', depression_finder = DepressionFinderAndRouter) # Add a field for effective drainage area if 'effective_drainage_area' in self.grid.at_node: self.eff_area = self.grid.at_node['effective_drainage_area'] else: self.eff_area = self.grid.add_zeros('node', 'effective_drainage_area') # Get the effective-length parameter self.sat_len = (K_hydraulic_conductivity*self.grid.dx)/(recharge_rate) # Instantiate a FastscapeEroder component self.eroder = StreamPowerEroder(self.grid, use_Q=self.eff_area, K_sp=self.K_sp, m_sp=self.params['m_sp'], n_sp=self.params['n_sp']) # Instantiate a DepthDependentDiffuser component self.diffuser = DepthDependentDiffuser(self.grid, linear_diffusivity=linear_diffusivity, soil_transport_decay_depth=soil_transport_decay_depth) self.weatherer = ExponentialWeatherer(self.grid, max_soil_production_rate=max_soil_production_rate, soil_production_decay_depth=soil_production_decay_depth) def calc_effective_drainage_area(self): """Calculate and store effective drainage area. Effective drainage area is defined as: $A_{eff} = A \exp ( c H S / A) = A R_r$ where $H$ is soil thickness, $S$ is downslope-positive steepest gradient, $A$ is drainage area, $R_r$ is the runoff ratio, and $c$ is the saturation length parameter. """ area = self.grid.at_node['drainage_area'] slope = self.grid.at_node['topographic__steepest_slope'] soil = self.grid.at_node['soil__depth'] cores = self.grid.core_nodes self.eff_area[cores] = (area[cores] * (np.exp(-self.sat_len * soil[cores] * slope[cores] / area[cores]))) def run_one_step(self, dt): """ Advance model for one time-step of duration dt. """ # Route flow self.flow_router.run_one_step() # Update effective runoff ratio self.calc_effective_drainage_area() # Zero out effective area in flooded nodes self.eff_area[self.flow_router.depression_finder.flood_status==3] = 0.0 # Do some erosion (but not on the flooded nodes) # (if we're varying K through time, update that first) if self.opt_var_precip: self.eroder.K = (self.K_sp * self.pc.get_erodibility_adjustment_factor(self.model_time)) self.eroder.run_one_step(dt) # We must also now erode the bedrock where relevant. If water erosion # into bedrock has occurred, the bedrock elevation will be higher than # the actual elevation, so we simply re-set bedrock elevation to the # lower of itself or the current elevation. b = self.grid.at_node['bedrock__elevation'] b[:] = np.minimum(b, self.grid.at_node['topographic__elevation']) # Calculate regolith-production rate self.weatherer.calc_soil_prod_rate() # Do some soil creep self.diffuser.run_one_step(dt) # calculate model time self.model_time += dt # Lower outlet self.update_outlet(dt) # Check walltime self.check_walltime()
def test_sp_discharges_old(): input_str = os.path.join(_THIS_DIR, "test_sp_params_discharge.txt") inputs = ModelParameterDictionary(input_str) nrows = 5 ncols = 5 dx = inputs.read_float("dx") dt = inputs.read_float("dt") mg = RasterModelGrid(nrows, ncols, xy_spacing=dx) mg.add_zeros("topographic__elevation", at="node") z = np.array( [ 5., 5., 0., 5., 5., 5., 2., 1., 2., 5., 5., 3., 2., 3., 5., 5., 4., 4., 4., 5., 5., 5., 5., 5., 5., ] ) mg["node"]["topographic__elevation"] = z fr = FlowAccumulator(mg, flow_director="D8") my_Q = mg.at_node["surface_water__discharge"] sp = StreamPowerEroder(mg, input_str, use_Q=my_Q) # perform the loop (once!) for i in range(1): fr.run_one_step() my_Q[:] = mg.at_node["surface_water__discharge"] * 1. sp.run_one_step(dt) z_tg = np.array( [ 5., 5., 0., 5., 5., 5., 1.47759225, 0.43050087, 1.47759225, 5., 5., 2.32883687, 1.21525044, 2.32883687, 5., 5., 3.27261262, 3.07175015, 3.27261262, 5., 5., 5., 5., 5., 5., ] ) assert_array_almost_equal(mg.at_node["topographic__elevation"], z_tg)
use_Q=None) lake = DepressionFinderAndRouter(mg) # Hillslopes dfn = LinearDiffuser(mg, linear_diffusivity=K_hs) zr_last = -9999 keep_running = np.mean(np.abs(zr - zr_last)) >= end_thresh ti = 0 while keep_running: zr_last = zr.copy() zr[mg.core_nodes] += uplift_rate*dt dfn.run_one_step(dt) # hillslopes always diffusive, even when dry frr.run_one_step() lake.map_depressions() spr.run_one_step(dt, flooded_nodes=lake.lake_at_node) keep_running = np.mean(np.abs(zr - zr_last)) >= end_thresh ti += dt print ti/1000., 'kyr elapsed; ', np.mean(zr-zr_last) / dt * 1E6, \ 'um/yr surface uplift' print "Convergence reached! Landscape is at steady state." A = mg.at_node['drainage_area']#[not_edge] A = A.reshape(ncells_side, ncells_side) S = mg.at_node['topographic__steepest_slope'] S = S.reshape(ncells_side, ncells_side) np.savetxt('Synthetic_data/z.txt', zr, fmt='%.2f') np.savetxt('Synthetic_data/A.txt', A, fmt='%d') np.savetxt('Synthetic_data/S.txt', S, fmt='%.5f')
def test_sp_new(): """ Tests new style component instantiation and run. """ input_str = os.path.join(_THIS_DIR, "drive_sp_params.txt") inputs = ModelParameterDictionary(input_str, auto_type=True) nrows = inputs.read_int("nrows") ncols = inputs.read_int("ncols") dx = inputs.read_float("dx") dt = inputs.read_float("dt") time_to_run = inputs.read_float("run_time") uplift = inputs.read_float("uplift_rate") init_elev = inputs.read_float("init_elev") mg = RasterModelGrid((nrows, ncols), xy_spacing=(dx, dx)) mg.set_closed_boundaries_at_grid_edges(False, False, True, True) mg.add_zeros("topographic__elevation", at="node") z = mg.zeros(at="node") + init_elev numpy.random.seed(0) mg["node"]["topographic__elevation"] = z + numpy.random.rand(len(z)) / 1000.0 fr = FlowAccumulator(mg, flow_director="D8") sp = StreamPowerEroder(mg, **inputs) elapsed_time = 0.0 while elapsed_time < time_to_run: if elapsed_time + dt > time_to_run: dt = time_to_run - elapsed_time fr.run_one_step() sp.run_one_step(dt) mg.at_node["topographic__elevation"][mg.core_nodes] += uplift * dt elapsed_time += dt z_trg = numpy.array( [ 5.48813504e-04, 7.15189366e-04, 6.02763376e-04, 5.44883183e-04, 4.23654799e-04, 6.45894113e-04, 1.01830760e-02, 9.58036770e-03, 6.55865452e-03, 3.83441519e-04, 7.91725038e-04, 1.00142749e-02, 8.80798884e-03, 5.78387585e-03, 7.10360582e-05, 8.71292997e-05, 9.81911417e-03, 9.52243406e-03, 7.55093226e-03, 8.70012148e-04, 9.78618342e-04, 1.00629755e-02, 8.49253798e-03, 5.33216680e-03, 1.18274426e-04, 6.39921021e-04, 9.88956320e-03, 9.47119567e-03, 6.43790696e-03, 4.14661940e-04, 2.64555612e-04, 1.00450743e-02, 8.37262908e-03, 5.21540904e-03, 1.87898004e-05, 6.17635497e-04, 9.21286940e-03, 9.34022513e-03, 7.51114450e-03, 6.81820299e-04, 3.59507901e-04, 6.19166921e-03, 7.10456176e-03, 6.62585507e-03, 6.66766715e-04, 6.70637870e-04, 2.10382561e-04, 1.28926298e-04, 3.15428351e-04, 3.63710771e-04, ] ) assert_array_almost_equal(mg.at_node["topographic__elevation"], z_trg)
class BasicVs(ErosionModel): """ A BasicVs computes erosion using linear diffusion, basic stream power, 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 BasicVs.""" # Call ErosionModel's init super(BasicVs, self).__init__(input_file=input_file, params=params, BaselevelHandlerClass=BaselevelHandlerClass) # Get Parameters: K_sp = self.get_parameter_from_exponent('K_sp', raise_error=False) K_ss = self.get_parameter_from_exponent('K_ss', raise_error=False) linear_diffusivity = (self._length_factor**2.)*self.get_parameter_from_exponent('linear_diffusivity') # has units length^2/time recharge_rate = (self._length_factor)*self.params['recharge_rate'] # has units length per time soil_thickness = (self._length_factor)*self.params['initial_soil_thickness'] # has units length K_hydraulic_conductivity = (self._length_factor)*self.params['K_hydraulic_conductivity'] # has units length per time # check that a stream power and a shear stress parameter have not both been given if K_sp != None and K_ss != None: raise ValueError('A parameter for both K_sp and K_ss has been' 'provided. Only one of these may be provided') elif K_sp != None or K_ss != None: if K_sp != None: self.K = K_sp else: self.K = (self._length_factor**(1./3.))*K_ss # K_ss has units Lengtg^(1/3) per Time else: raise ValueError('A value for K_sp or K_ss must be provided.') # Instantiate a FlowAccumulator with DepressionFinderAndRouter using D8 method self.flow_router = FlowAccumulator(self.grid, flow_director='D8', depression_finder = DepressionFinderAndRouter) # Add a field for effective drainage area if 'effective_drainage_area' in self.grid.at_node: self.eff_area = self.grid.at_node['effective_drainage_area'] else: self.eff_area = self.grid.add_zeros('node', 'effective_drainage_area') # Get the effective-area parameter self.sat_param = (K_hydraulic_conductivity*soil_thickness*self.grid.dx)/(recharge_rate) # Instantiate a FastscapeEroder component self.eroder = StreamPowerEroder(self.grid, use_Q=self.eff_area, K_sp=self.K, m_sp=self.params['m_sp'], n_sp=self.params['n_sp']) # Instantiate a LinearDiffuser component self.diffuser = LinearDiffuser(self.grid, linear_diffusivity = linear_diffusivity) def 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 (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) # 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_sp_old(): uplift = 0.001 dt = 1.0 time_to_run = 10.0 mg = RasterModelGrid((10, 5)) mg.set_closed_boundaries_at_grid_edges(False, False, True, True) mg.add_zeros("topographic__elevation", at="node") z = mg.zeros(at="node") numpy.random.seed(0) mg["node"]["topographic__elevation"] = z + numpy.random.rand(len(z)) / 1000.0 fr = FlowAccumulator(mg, flow_director="D8") sp = StreamPowerEroder(mg, K_sp=0.1, m_sp=0.5, n_sp=1.0, threshold_sp=0.0) elapsed_time = 0.0 while elapsed_time < time_to_run: if elapsed_time + dt > time_to_run: dt = time_to_run - elapsed_time fr.run_one_step() sp.run_one_step(dt) mg.at_node["topographic__elevation"][mg.core_nodes] += uplift * dt elapsed_time += dt z_trg = numpy.array( [ 5.48813504e-04, 7.15189366e-04, 6.02763376e-04, 5.44883183e-04, 4.23654799e-04, 6.45894113e-04, 1.01830760e-02, 9.58036770e-03, 6.55865452e-03, 3.83441519e-04, 7.91725038e-04, 1.00142749e-02, 8.80798884e-03, 5.78387585e-03, 7.10360582e-05, 8.71292997e-05, 9.81911417e-03, 9.52243406e-03, 7.55093226e-03, 8.70012148e-04, 9.78618342e-04, 1.00629755e-02, 8.49253798e-03, 5.33216680e-03, 1.18274426e-04, 6.39921021e-04, 9.88956320e-03, 9.47119567e-03, 6.43790696e-03, 4.14661940e-04, 2.64555612e-04, 1.00450743e-02, 8.37262908e-03, 5.21540904e-03, 1.87898004e-05, 6.17635497e-04, 9.21286940e-03, 9.34022513e-03, 7.51114450e-03, 6.81820299e-04, 3.59507901e-04, 6.19166921e-03, 7.10456176e-03, 6.62585507e-03, 6.66766715e-04, 6.70637870e-04, 2.10382561e-04, 1.28926298e-04, 3.15428351e-04, 3.63710771e-04, ] ) assert_array_almost_equal(mg.at_node["topographic__elevation"], z_trg)
def test_sp_voronoi(): nnodes = 100 np.random.seed(0) x = np.random.rand(nnodes) np.random.seed(1) y = np.random.rand(nnodes) mg = VoronoiDelaunayGrid(x, y) np.random.seed(2) z = mg.add_field( "topographic__elevation", np.random.rand(nnodes) / 10000.0, at="node", copy=False, ) fr = FlowAccumulator(mg) spe = StreamPowerEroder(mg, K_sp=0.15, m_sp=0.5, n_sp=1.0, threshold_sp=0.0) for i in range(10): z[mg.core_nodes] += 0.01 fr.run_one_step() spe.run_one_step(1.0) z_tg = np.array([ 4.35994902e-05, 2.59262318e-06, 5.49662478e-05, 6.56738615e-03, 4.20367802e-05, 1.21371424e-02, 2.16596169e-02, 4.73320898e-02, 6.00389761e-02, 5.22007356e-02, 5.37507115e-02, 5.95794752e-02, 5.29862904e-02, 6.76465914e-02, 7.31720024e-02, 6.18730861e-02, 8.53975293e-05, 5.32189275e-02, 7.34302556e-02, 8.07385044e-02, 5.05246090e-05, 4.08940657e-02, 7.39971005e-02, 3.31915602e-02, 6.72650419e-02, 5.96745309e-05, 4.72752445e-02, 3.60359567e-02, 7.59432065e-02, 7.24461985e-02, 7.80305760e-02, 4.93866869e-02, 8.69642467e-02, 7.21627626e-02, 8.96368291e-02, 4.65142080e-02, 6.07720217e-02, 8.83372939e-02, 2.35887558e-02, 7.97616193e-02, 8.35615355e-02, 4.61809032e-02, 6.34634214e-02, 9.25711770e-02, 4.11717225e-03, 7.24493623e-02, 7.97908053e-02, 9.10375623e-02, 9.13155023e-02, 7.10567915e-02, 7.35271752e-02, 6.13091341e-02, 9.45498463e-02, 8.48532386e-02, 8.82702021e-02, 7.14969941e-02, 2.22640943e-02, 8.53311932e-02, 7.49161159e-02, 3.48837223e-02, 9.30132692e-02, 6.01817121e-05, 3.87455443e-02, 8.44673586e-02, 9.35213577e-02, 6.76075824e-02, 1.58614508e-02, 8.51346837e-02, 8.83645680e-02, 8.69944117e-02, 5.04000439e-05, 5.02319084e-02, 8.63882765e-02, 5.00991880e-02, 7.65156630e-02, 5.07591983e-02, 6.54909962e-02, 6.91505342e-02, 7.33358371e-02, 5.30109890e-02, 2.99074601e-02, 2.55509418e-06, 8.21523907e-02, 8.09368483e-02, 4.35073025e-02, 3.04096109e-02, 3.26298627e-02, 4.92259177e-02, 5.48690358e-02, 6.44732130e-02, 6.28133567e-02, 4.17977098e-06, 5.37149677e-02, 4.32828136e-02, 1.30559903e-02, 2.62405261e-02, 2.86079272e-02, 6.61481327e-05, 1.70477133e-05, 8.81652236e-05, ]) assert_array_almost_equal(mg.at_node["topographic__elevation"], z_tg)
def test_sp_widths(): nrows = 5 ncols = 5 dt = 1 mg = RasterModelGrid((nrows, ncols)) widths = np.ones(mg.number_of_nodes, dtype=float) mg.add_zeros("topographic__elevation", at="node") z = np.array([ 5.0, 5.0, 0.0, 5.0, 5.0, 5.0, 2.0, 1.0, 2.0, 5.0, 5.0, 3.0, 2.0, 3.0, 5.0, 5.0, 4.0, 4.0, 4.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, ]) mg["node"]["topographic__elevation"] = z fr = FlowAccumulator(mg, flow_director="D8") sp = StreamPowerEroder(mg, channel_width_field=widths, K_sp=0.5, m_sp=1.0, n_sp=1.0, threshold_sp=0.0) # perform the loop (once!) for i in range(1): fr.run_one_step() sqrt_A = mg.at_node["drainage_area"]**0.5 widths[mg.core_nodes] = sqrt_A[mg.core_nodes] / sqrt_A[ mg.core_nodes].mean() # so widths has mean=1. # note the issue with drainage_area not defined at perimeter => nans # if not careful... sp.run_one_step(dt) z_tg = np.array([ 5.0, 5.0, 0.0, 5.0, 5.0, 5.0, 1.37222369, 0.36876358, 1.37222369, 5.0, 5.0, 2.17408606, 1.07986038, 2.17408606, 5.0, 5.0, 3.08340277, 2.85288049, 3.08340277, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, ]) assert_array_almost_equal(mg.at_node["topographic__elevation"], z_tg)
def test_sp_discharges_new(): input_str = os.path.join(_THIS_DIR, "test_sp_params_discharge_new.txt") inputs = ModelParameterDictionary(input_str, auto_type=True) nrows = 5 ncols = 5 dx = inputs.read_float("dx") dt = inputs.read_float("dt") mg = RasterModelGrid((nrows, ncols), xy_spacing=dx) mg.add_zeros("topographic__elevation", at="node") z = np.array( [ 5.0, 5.0, 0.0, 5.0, 5.0, 5.0, 2.0, 1.0, 2.0, 5.0, 5.0, 3.0, 2.0, 3.0, 5.0, 5.0, 4.0, 4.0, 4.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, ] ) mg["node"]["topographic__elevation"] = z fr = FlowAccumulator(mg, flow_director="D8") sp = StreamPowerEroder(mg, **inputs) # perform the loop (once!) for i in range(1): fr.run_one_step() sp.run_one_step(dt) z_tg = np.array( [ 5.0, 5.0, 0.0, 5.0, 5.0, 5.0, 1.47759225, 0.43050087, 1.47759225, 5.0, 5.0, 2.32883687, 1.21525044, 2.32883687, 5.0, 5.0, 3.27261262, 3.07175015, 3.27261262, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, ] ) assert_array_almost_equal(mg.at_node["topographic__elevation"], z_tg)