def __init__(self, input_file=None, params=None): """Initialize the LinearDiffusionModel.""" # Call ErosionModel's init super(KinwaveModel, self).__init__(input_file=input_file, params=params) # Get input parameters: duration for a runoff (i.e., storm or snowmelt) # event, interval of time from the end of one event to the next, and # rate of runoff generation (in mm/hr) during an event. self.runoff_duration = self.params['runoff__duration'] self.interstorm_duration = self.params['interstorm__duration'] self.runoff_rate = self.params['runoff__rate'] / 3.6e6 # A "cycle" is a runoff event plus the interlude before the next event. self.cycle_duration = self.runoff_duration + self.interstorm_duration # Runoff continues for some time after runoff generation ceases. Here # we set the amount of time we actively calculate flow. After this # time period, we ignore flow for the remainder of the cycle. But if # the user-specified hydrograph duration is longer than our cycle, we # truncate at the cycle duration, because we'll start computing flow # again at the start of the next cycle. self.hydrograph_duration = min(self.cycle_duration, self.params['hydrograph__maximum_duration']) # This variable keeps track of how far we are in one cycle. self.current_time_in_cycle = 0.0 # Instantiate a KinwaveImplicitOverlandFlow self.water_router = KinwaveImplicitOverlandFlow(self.grid)
def test_initialization(): """Test initialization with various parameters.""" rg = RasterModelGrid((3, 4), xy_spacing=2.0) rg.add_zeros("topographic__elevation", at="node") kw = KinwaveImplicitOverlandFlow(rg) # Make sure fields have been created for field_name in kw._info: if kw._info[field_name]["mapping"] == "node": assert field_name in kw.grid.at_node elif kw._info[field_name]["mapping"] == "link": assert field_name in kw.grid.at_link # Re-initialize, this time with fields already existing in the grid # (this triggers the "if" instead of "else" in the field setup in init) kw = KinwaveImplicitOverlandFlow(rg)
def test_first_iteration(): """Test stuff that happens only on first iteration""" # Create a basic ramp rg = RasterModelGrid((10, 10), spacing=(2, 2)) rg.add_field("topographic__elevation", 0.1 * rg.node_y, at="node") # Create component and run it kw = KinwaveImplicitOverlandFlow(rg) kw.run_one_step(1.0) # Max gradient should be 0.1, and min should be zero assert round(np.amax(kw.grid.at_link["topographic__gradient"]), 2) == 0.1 assert round(np.amin(kw.grid.at_link["topographic__gradient"]), 2) == 0.0 assert round(np.amax(kw.sqrt_slope), 3) == 0.316 assert round(np.amax(kw.grad_width_sum), 3) == 0.632 assert round(np.amax(kw.alpha), 3) == 15.811
def test_first_iteration(): """Test stuff that happens only on first iteration""" # Create a basic ramp rg = RasterModelGrid((10,10), spacing=(2, 2)) rg.add_field('topographic__elevation', 0.1 * rg.node_y, at='node') # Create component and run it kw = KinwaveImplicitOverlandFlow(rg) kw.run_one_step(1.0) # Max gradient should be 0.1, and min should be zero assert round(np.amax(kw.grid.at_link['topographic__gradient']), 2) == 0.1 assert round(np.amin(kw.grid.at_link['topographic__gradient']), 2) == 0.0 assert round(np.amax(kw.sqrt_slope), 3) == 0.316 assert round(np.amax(kw.grad_width_sum), 3) == 0.632 assert round(np.amax(kw.alpha), 3) == 15.811
def test_initialization(): """Test initialization with various parameters. """ rg = RasterModelGrid((3, 4), 2.0) rg.add_zeros('node', 'topographic__elevation') kw = KinwaveImplicitOverlandFlow(rg) # Make sure fields have been created for field_name in kw._var_mapping: if kw._var_mapping[field_name] == 'node': assert field_name in kw.grid.at_node elif kw._var_mapping[field_name] == 'link': assert field_name in kw.grid.at_link # Re-initialize, this time with fields already existing in the grid # (this triggers the "if" instead of "else" in the field setup in init) kw = KinwaveImplicitOverlandFlow(rg)
def test_curved_surface(): """Test flow across a curved surface.""" # Create a grid rg = RasterModelGrid((10,10), spacing=(2, 2)) rg.add_field('topographic__elevation', 3.*rg.node_x**2 + rg.node_y**2, at='node') # Create component and run it kw = KinwaveImplicitOverlandFlow(rg) for i in range(8): kw.run_one_step(1.0, runoff_rate=0.001) # The inflow discharge to each cell at steady state should equal the # runoff rate times the "inflow" drainage area, which is the total drainage # area minus the area of the cell itself. Here we'll test a column of core # nodes across the middle of the domain. area = rg.at_node['drainage_area'] runoff_rate = 0.001 unit_area = 4.0 for i in range(15, 95, 10): assert round(kw.disch_in[i], 6) == round(runoff_rate * (area[i] - unit_area), 6)
def test_curved_surface(): """Test flow across a curved surface.""" # Create a grid rg = RasterModelGrid((10, 10), spacing=(2, 2)) rg.add_field("topographic__elevation", 3. * rg.node_x**2 + rg.node_y**2, at="node") # Create component and run it kw = KinwaveImplicitOverlandFlow(rg) for i in range(8): kw.run_one_step(1.0, runoff_rate=0.001) # The inflow discharge to each cell at steady state should equal the # runoff rate times the "inflow" drainage area, which is the total drainage # area minus the area of the cell itself. Here we'll test a column of core # nodes across the middle of the domain. area = rg.at_node["drainage_area"] runoff_rate = 0.001 unit_area = 4.0 for i in range(15, 95, 10): assert round(kw.disch_in[i], 6) == round(runoff_rate * (area[i] - unit_area), 6)
def test_steady_basic_ramp(): """Run to steady state with basic ramp""" # Create a basic ramp rg = RasterModelGrid((10,10), spacing=(2, 2)) rg.add_field('topographic__elevation', 0.1 * rg.node_y, at='node') # Create component and run it kw = KinwaveImplicitOverlandFlow(rg) for i in range(12): kw.run_one_step(1.0, runoff_rate=0.001) # Look at a column of nodes down the middle. The inflow from uphill should # be, from top to bottom: 0, 0.004, 0.008, 0.012, 0.016, 0.02, 0.024, 0.028 assert_equal(kw.disch_in[85], 0.0) assert_equal(round(kw.disch_in[75], 3), 0.004) assert_equal(round(kw.disch_in[65], 3), 0.008) assert_equal(round(kw.disch_in[55], 3), 0.012) assert_equal(round(kw.disch_in[45], 3), 0.016) assert_equal(round(kw.disch_in[35], 3), 0.020) assert_equal(round(kw.disch_in[25], 3), 0.024) assert_equal(round(kw.disch_in[15], 3), 0.028) # Try with passing in runoff kw = KinwaveImplicitOverlandFlow(rg, runoff_rate=360.0) kw.depth[:] = 0.0 for i in range(22): kw.run_one_step(1.0) # Again, look at a column of nodes down the middle. The inflow from uphill # should now be 1/10 of the prior example. assert_equal(round(kw.disch_in[75], 4), 0.0004) assert_equal(round(kw.disch_in[65], 4), 0.0008) assert_equal(round(kw.disch_in[55], 4), 0.0012) assert_equal(round(kw.disch_in[45], 4), 0.0016) assert_equal(round(kw.disch_in[35], 4), 0.0020) assert_equal(round(kw.disch_in[25], 4), 0.0024) assert_equal(round(kw.disch_in[15], 4), 0.0028) # Try with default runoff rate of 1 mm/hr = 2.78e-7 m/s kw = KinwaveImplicitOverlandFlow(rg) assert_equal(round(kw.runoff_rate * 1.0e7, 2), 2.78) kw.depth[:] = 0.0 for i in range(18): kw.run_one_step(10.0) # Look at a column of nodes down the middle. The inflow from uphill should # be, from top to bottom: 0, 0.004, 0.008, 0.012, 0.016, 0.02, 0.024, 0.028 assert_equal(kw.disch_in[85], 0.0) assert_equal(round(kw.disch_in[75], 7), 0.0000011) assert_equal(round(kw.disch_in[65], 7), 0.0000022) assert_equal(round(kw.disch_in[55], 7), 0.0000033) assert_equal(round(kw.disch_in[45], 7), 0.0000044) assert_equal(round(kw.disch_in[35], 7), 0.0000055) assert_equal(round(kw.disch_in[25], 7), 0.0000066) assert_equal(round(kw.disch_in[15], 7), 0.0000077)
def test_steady_basic_ramp(): """Run to steady state with basic ramp""" # Create a basic ramp rg = RasterModelGrid((10, 10), spacing=(2, 2)) rg.add_field("topographic__elevation", 0.1 * rg.node_y, at="node") # Create component and run it kw = KinwaveImplicitOverlandFlow(rg) for i in range(12): kw.run_one_step(1.0, runoff_rate=0.001) # Look at a column of nodes down the middle. The inflow from uphill should # be, from top to bottom: 0, 0.004, 0.008, 0.012, 0.016, 0.02, 0.024, 0.028 assert kw.disch_in[85] == 0.0 assert round(kw.disch_in[75], 3) == 0.004 assert round(kw.disch_in[65], 3) == 0.008 assert round(kw.disch_in[55], 3) == 0.012 assert round(kw.disch_in[45], 3) == 0.016 assert round(kw.disch_in[35], 3) == 0.020 assert round(kw.disch_in[25], 3) == 0.024 assert round(kw.disch_in[15], 3) == 0.028 # Try with passing in runoff kw = KinwaveImplicitOverlandFlow(rg, runoff_rate=360.0) kw.depth[:] = 0.0 for i in range(22): kw.run_one_step(1.0) # Again, look at a column of nodes down the middle. The inflow from uphill # should now be 1/10 of the prior example. assert round(kw.disch_in[75], 4) == 0.0004 assert round(kw.disch_in[65], 4) == 0.0008 assert round(kw.disch_in[55], 4) == 0.0012 assert round(kw.disch_in[45], 4) == 0.0016 assert round(kw.disch_in[35], 4) == 0.0020 assert round(kw.disch_in[25], 4) == 0.0024 assert round(kw.disch_in[15], 4) == 0.0028 # Try with default runoff rate of 1 mm/hr = 2.78e-7 m/s kw = KinwaveImplicitOverlandFlow(rg) assert round(kw.runoff_rate * 1.0e7, 2) == 2.78 kw.depth[:] = 0.0 for i in range(18): kw.run_one_step(10.0) # Look at a column of nodes down the middle. The inflow from uphill should # be, from top to bottom: 0, 0.004, 0.008, 0.012, 0.016, 0.02, 0.024, 0.028 assert kw.disch_in[85] == 0.0 assert round(kw.disch_in[75], 7) == 0.0000011 assert round(kw.disch_in[65], 7) == 0.0000022 assert round(kw.disch_in[55], 7) == 0.0000033 assert round(kw.disch_in[45], 7) == 0.0000044 assert round(kw.disch_in[35], 7) == 0.0000055 assert round(kw.disch_in[25], 7) == 0.0000066 assert round(kw.disch_in[15], 7) == 0.0000077
# + plt.figure(1) plt.plot(hydrograph_time, discharge_at_outlet, "r-", label="outlet") plt.ylabel("Discharge (cms)") plt.xlabel("Time (hour)") plt.legend(loc="upper right") title_text = f"Hydrograph ({dem_path.split('/')[-1].split('.')[0]})" plt.title(title_text) plt.show() # - # ### Run/Loop Kinematic Wave Overland Flow Component kw = KinwaveImplicitOverlandFlow(rmg, runoff_rate=0.0, roughness=n, depth_exp=5 / 3) # + elapsed_time = 1 while elapsed_time < model_run_time_sec: if elapsed_time < storm_duration_sec: kw.runoff_rate = starting_precip_mmhr #This needs to be in mm/hr because the source code automatically converts to m/s else: kw.runoff_rate = 1e-20 #This needs to be > 0 because of an assertion in the source code... kw.run_one_step(dt) # add elapsed time to the continuos time
class KinwaveModel(_ErosionModel): """ A DrainageAreaModel simply computes drainage area on a raster-grid DEM. """ def __init__(self, input_file=None, params=None): """Initialize the LinearDiffusionModel.""" # Call ErosionModel's init super(KinwaveModel, self).__init__(input_file=input_file, params=params) # Get input parameters: duration for a runoff (i.e., storm or snowmelt) # event, interval of time from the end of one event to the next, and # rate of runoff generation (in mm/hr) during an event. self.runoff_duration = self.params['runoff__duration'] self.interstorm_duration = self.params['interstorm__duration'] self.runoff_rate = self.params['runoff__rate'] / 3.6e6 # A "cycle" is a runoff event plus the interlude before the next event. self.cycle_duration = self.runoff_duration + self.interstorm_duration # Runoff continues for some time after runoff generation ceases. Here # we set the amount of time we actively calculate flow. After this # time period, we ignore flow for the remainder of the cycle. But if # the user-specified hydrograph duration is longer than our cycle, we # truncate at the cycle duration, because we'll start computing flow # again at the start of the next cycle. self.hydrograph_duration = min(self.cycle_duration, self.params['hydrograph__maximum_duration']) # This variable keeps track of how far we are in one cycle. self.current_time_in_cycle = 0.0 # Instantiate a KinwaveImplicitOverlandFlow self.water_router = KinwaveImplicitOverlandFlow(self.grid) def run_one_step(self, dt): """ Advance model for one time-step of duration dt. """ self.water_router.run_one_step(dt) def run_for(self, dt, runtime): """ Run model without interruption for a specified time period. This involves """ remaining_time = runtime remaining_time_in_cycle = (self.cycle_duration - self.current_time_in_cycle) while remaining_time > 0.0: # If we're in the hydrograph part of the cycle, run a hydrograph. if self.current_time_in_cycle < self.hydrograph_duration: # Set the component's runoff rate according to whether it's # still raining or not. if self.current_time_in_cycle < self.runoff_duration: self.water_router.runoff_rate = self.runoff_rate else: self.water_router.runoff_rate = 0.0 # Calculate time-step size: we adjust downward from dt if # needed to make sure we don't exceed the runoff duration or # the runtime. delt = min(dt, remaining_time) delt = min(delt, self.runoff_duration) # Run some water flow self.run_one_step(delt) # Advance time remaining_time -= delt remaining_time_in_cycle -= delt self.current_time_in_cycle += delt # If we're in the "dry" part of a cycle, simply advance to either # the beginning of the next cycle or the end of the "runtime" # period, whichever is shorter. else: dry_time = min(remaining_time, remaining_time_in_cycle) remaining_time -= dry_time remaining_time_in_cycle -= dry_time if remaining_time_in_cycle > 0.0: self.current_time_in_cycle += dry_time else: self.current_time_in_cycle = 0.0 # start a new cycle remaining_time_in_cycle = self.cycle_duration