def run_one_step_adaptive(self, grid, dt=None, Klr=None, inlet_area_ts=None, qsinlet_ts=None, **kwds): if Klr is None: # Added10/9 to allow changing rainrate (indirectly this way.) Klr = self.Klr UC = self._UC TB = self._TB inlet_on = self.inlet_on # this is a true/false flag Kv = self.Kv frac = self.frac qs_in = self.qs_in dzdt = self.dzdt alph = self.alph self.dt = dt vol_lat = self.grid.at_node['volume__lateral_erosion'] kw = 10. F = 0.02 runoffms = (Klr * F / kw)**2 Kl = Kv * Klr z = grid.at_node['topographic__elevation'] # clear qsin for next loop qs_in = grid.add_zeros('node', 'qs_in', noclobber=False) lat_nodes = np.zeros(grid.number_of_nodes, dtype=int) dzlat = np.zeros(grid.number_of_nodes) dzver = np.zeros(grid.number_of_nodes) vol_lat_dt = np.zeros(grid.number_of_nodes) if inlet_on is True: # define inlet_node inlet_node = self.inlet_node # if a value is passed with qsinlet_ts, qsinlet has changed with this timestep, # so reset qsinlet to qsinlet_ts if qsinlet_ts is not None: qsinlet = qsinlet_ts qs_in[inlet_node] = qsinlet # if nothing is passed with qsinlet_ts, qsinlet remains the same from # initialized parameters else: # qsinlet_ts==None: qsinlet = self.qsinlet qs_in[inlet_node] = qsinlet if inlet_area_ts is not None: inlet_area = inlet_area_ts runoffinlet = np.ones(grid.number_of_nodes) * grid.dx**2 # Change the runoff at the inlet node to node area + inlet node runoffinlet[inlet_node] += inlet_area _ = grid.add_field('node', 'water__unit_flux_in', runoffinlet, noclobber=False) # if inlet area has changed with time (so we have a new inlet area here) fa = FlowAccumulator(grid, surface='topographic__elevation', flow_director='FlowDirectorD8', runoff_rate=None, depression_finder=None) # "DepressionFinderAndRouter", router="D8") (da, q) = fa.accumulate_flow() q = grid.at_node['surface_water__discharge'] # this is the drainage area that I need for code below with an inlet set # by spatially varible runoff. da = q / grid.dx**2 else: q = grid.at_node['surface_water__discharge'] # this is the drainage area that I need for code below with an inlet set # by spatially varible runoff. da = q / grid.dx**2 # if inlet flag is not on, proceed as normal. else: # renamed this drainage area set by flow router da = grid.at_node['drainage_area'] s = grid.at_node['flow__upstream_node_order'] max_slopes = grid.at_node['topographic__steepest_slope'] flowdirs = grid.at_node['flow__receiver_node'] l = s[np.where((grid.status_at_node[s] == 0))[0]] dwnst_nodes = l # reverse list so we go from upstream to down stream dwnst_nodes = dwnst_nodes[::-1] # local time time = 0 globdt = dt while time < globdt: max_slopes[:] = max_slopes.clip(0) # here calculate dzdt for each node, with initial time step for i in dwnst_nodes: dep = alph * qs_in[i] / da[i] ero = -Kv[i] * da[i]**(0.5) * max_slopes[i] dzver[i] = dep + ero petlat = 0. # water depth in meters, needed for lateral erosion calc wd = 0.4 * (da[i] * runoffms)**0.35 if i in flowdirs: # Node_finder picks the lateral node to erode based on angle # between segments between three nodes [lat_node, inv_rad_curv] = Node_Finder2(grid, i, flowdirs, da) # node_finder returns the lateral node ID and the radius of # curvature lat_nodes[i] = lat_node # if the lateral node is not 0 or -1 continue. if lat_node > 0: # if the elevation of the lateral node is higher than primary node, # calculate a new potential lateral erosion (L/T), which is # negative if z[lat_node] > z[i]: petlat = -Kl[i] * da[i] * max_slopes[i] * inv_rad_curv # the calculated potential lateral erosion is mutiplied by the length of the node # and the bank height, then added to an array, vol_lat_dt, for volume eroded # laterally *per timestep* at each node. This vol_lat_dt is reset to zero for # each timestep loop. vol_lat_dt is added to itself more than one primary nodes are # laterally eroding this lat_node # volume of lateral erosion per timestep vol_lat_dt[lat_node] += abs(petlat) * grid.dx * wd # send sediment downstream. sediment eroded from vertical incision # and lateral erosion is sent downstream qs_in[flowdirs[i]] += qs_in[i] - \ (dzver[i] * grid.dx**2) - \ (petlat * grid.dx * wd) # qsin to next node dzdt[:] = dzver # Do a time-step check # If the downstream node is eroding at a slower rate than the # upstream node, there is a possibility of flow direction reversal, # or at least a flattening of the landscape. # Limit dt so that this flattening or reversal doesn't happen. # How close you allow these two points to get to eachother is # determined by the variable frac. # dtn is an arbitrarily large number to begin with, but will be adapted as we step through # the nodes dtn = dt * 50 # starting minimum timestep for this round for i in dwnst_nodes: # are points converging? ie, downstream eroding slower than upstream dzdtdif = dzdt[flowdirs[i]] - dzdt[i] # if points converging, find time to zero slope if dzdtdif > 1.e-5 and max_slopes[i] > 1e-5: # time to flat between points dtflat = (z[i] - z[flowdirs[i]]) / dzdtdif # if time to flat is smaller than dt, take the lower value if dtflat < dtn: dtn = dtflat # assert dtn>0, "dtn <0 at dtflat" # if dzdtdif*dtflat will make upstream lower than downstream, find # time to flat if dzdtdif * dtflat > (z[i] - z[flowdirs[i]]): dtn = (z[i] - z[flowdirs[i]]) / dzdtdif dtn *= frac # new minimum timestep for this round of nodes dt = min(abs(dtn), dt) assert dt > 0., "timesteps less than 0." # vol_lat is the total volume eroded from the lateral nodes through # the entire model run. So vol_lat is itself plus vol_lat_dt (for current loop) # times stable timestep size vol_lat[:] += vol_lat_dt * dt # this loop determines if enough lateral erosion has happened to change # the height of the neighbor node. for i in dwnst_nodes: lat_node = lat_nodes[i] wd = 0.4 * (da[i] * runoffms)**0.35 if lat_node > 0: # greater than zero now bc inactive neighbors are value -1 if z[lat_node] > z[i]: # vol_diff is the volume that must be eroded from lat_node so that its # elevation is the same as node downstream of primary node # UC model: this would represent undercutting (the water height # at node i), slumping, and instant removal. if UC == 1: voldiff = (z[i] + wd - z[flowdirs[i]]) * grid.dx**2 # TB model: entire lat node must be eroded before lateral # erosion occurs if TB == 1: voldiff = (z[lat_node] - z[flowdirs[i]]) * grid.dx**2 # if the total volume eroded from lat_node is greater than the volume # needed to be removed to make node equal elevation, # then instantaneously remove this height from lat node. already # has timestep in it if vol_lat[lat_node] >= voldiff: dzlat[lat_node] = z[flowdirs[i]] - z[lat_node] # -0.001 # after the lateral node is eroded, reset its volume eroded # to zero vol_lat[lat_node] = 0.0 # multiply dzver(changed to dzdt above) by timestep size and combine with lateral erosion # dzlat, which is already a length for the chosen time step dz = dzdt * dt + dzlat # change height of landscape z[:] = dz + z grid['node']['topographic__elevation'][grid.core_nodes] = z[grid.core_nodes] # update elapsed time time = dt + time # check to see that you are within 0.01% of the storm duration, if so # done, if not continue if time > 0.9999 * globdt: time = globdt else: dt = globdt - time qs_in = grid.zeros(centering='node') # recalculate flow directions fa = FlowAccumulator(grid, surface='topographic__elevation', flow_director='FlowDirectorD8', runoff_rate=None, depression_finder=None) # "DepressionFinderAndRouter", router="D8") (da, q) = fa.accumulate_flow() if inlet_on: # #if inlet on, reset drainage area and qsin to reflect inlet conditions # this is the drainage area that I need for code below with an inlet # set by spatially varible runoff. da = q / grid.dx**2 qs_in[inlet_node] = qsinlet else: # otherwise, drainage area is just drainage area. *** could remove the # below line to speed up model. It's not really necessary. # renamed this drainage area set by flow router da = grid.at_node['drainage_area'] s = grid.at_node['flow__upstream_node_order'] max_slopes = grid.at_node['topographic__steepest_slope'] q = grid.at_node['surface_water__discharge'] flowdirs = grid.at_node['flow__receiver_node'] l = s[np.where((grid.status_at_node[s] == 0))[0]] dwnst_nodes = l dwnst_nodes = dwnst_nodes[::-1] lat_nodes = np.zeros(grid.number_of_nodes, dtype=int) dzlat = np.zeros(grid.number_of_nodes) vol_lat_dt = np.zeros(grid.number_of_nodes) dzver = np.zeros(grid.number_of_nodes) return grid, dzlat
def run_one_step_basic(self, grid, dt=None, Klr=None, inlet_area_ts=None, qsinlet_ts=None, **kwds): if Klr is None: # Added10/9 to allow changing rainrate (indirectly this way.) Klr = self.Klr UC = self._UC TB = self._TB inlet_on = self.inlet_on # this is a true/false flag Kv = self.Kv qs_in = self.qs_in dzdt = self.dzdt alph = self.alph self.dt = dt vol_lat = self.grid.at_node['volume__lateral_erosion'] kw = 10. F = 0.02 # May 2, runoff calculated below (in m/s) is important for calculating # discharge and water depth correctly. renamed runoffms to prevent # confusion with other uses of runoff runoffms = (Klr * F / kw)**2 # Kl is calculated from ratio of lateral to vertical K parameters Kl = Kv * Klr z = grid.at_node['topographic__elevation'] # clear qsin for next loop qs_in = grid.add_zeros('node', 'qs_in', noclobber=False) qs = grid.add_zeros('node', 'qs', noclobber=False) lat_nodes = np.zeros(grid.number_of_nodes, dtype=int) dzlat = np.zeros(grid.number_of_nodes) dzver = np.zeros(grid.number_of_nodes) vol_lat_dt = np.zeros(grid.number_of_nodes) if inlet_on is True: inlet_node = self.inlet_node # if a value is passed with qsinlet_ts, qsinlet has changed with this timestep, # so reset qsinlet to qsinlet_ts if qsinlet_ts is not None: qsinlet = qsinlet_ts qs_in[inlet_node] = qsinlet # if nothing is passed with qsinlet_ts, qsinlet remains the same from # initialized parameters else: # qsinlet_ts==None: qsinlet = self.qsinlet qs_in[inlet_node] = qsinlet if inlet_area_ts is not None: inlet_area = inlet_area_ts runoffinlet = np.ones(grid.number_of_nodes) * grid.dx**2 # Change the runoff at the inlet node to node area + inlet node runoffinlet[inlet_node] += inlet_area _ = grid.add_field('node', 'water__unit_flux_in', runoffinlet, noclobber=False) # if inlet area has changed with time (so we have a new inlet area here) fa = FlowAccumulator(grid, surface='topographic__elevation', flow_director='FlowDirectorD8', runoff_rate=None, depression_finder=None) # "DepressionFinderAndRouter", router="D8") (da, q) = fa.accumulate_flow() q = grid.at_node['surface_water__discharge'] # this is the drainage area that I need for code below with an inlet set # by spatially varible runoff. da = q / grid.dx**2 else: # inletarea not changing with time q = grid.at_node['surface_water__discharge'] # this is the drainage area that I need for code below with an inlet set # by spatially varible runoff. da = q / grid.dx**2 # if inlet flag is not on, proceed as normal. else: # renamed this drainage area set by flow router da = grid.at_node['drainage_area'] # flow__upstream_node_order is node array contianing downstream to # upstream order list of node ids s = grid.at_node['flow__upstream_node_order'] max_slopes = grid.at_node['topographic__steepest_slope'] flowdirs = grid.at_node['flow__receiver_node'] # make a list l, where node status is interior (signified by label 0) in s l = s[np.where((grid.status_at_node[s] == 0))[0]] dwnst_nodes = l.copy() # reverse list so we go from upstream to down stream dwnst_nodes = dwnst_nodes[::-1] max_slopes[:] = max_slopes.clip(0) for i in dwnst_nodes: # calc deposition and erosion dep = alph * qs_in[i] / da[i] ero = -Kv[i] * da[i]**(0.5) * max_slopes[i] dzver[i] = dep + ero # potential lateral erosion initially set to 0 petlat = 0. # water depth in meters, needed for lateral erosion calc wd = 0.4 * (da[i] * runoffms)**0.35 # Choose lateral node for node i. If node i flows downstream, continue. # if node i is the first cell at the top of the drainage network, don't go # into this loop because in this case, node i won't have a "donor" node if i in flowdirs: # Node_finder picks the lateral node to erode based on angle # between segments between three nodes [lat_node, inv_rad_curv] = Node_Finder2(grid, i, flowdirs, da) # node_finder returns the lateral node ID and the radius of curvature lat_nodes[i] = lat_node # if the lateral node is not 0 or -1 continue. lateral node may be # 0 or -1 if a boundary node was chosen as a lateral node. then # radius of curavature is also 0 so there is no lateral erosion if lat_node > 0: # if the elevation of the lateral node is higher than primary node, # calculate a new potential lateral erosion (L/T), which is negative if z[lat_node] > z[i]: petlat = -Kl[i] * da[i] * max_slopes[i] * inv_rad_curv # the calculated potential lateral erosion is mutiplied by the length of the node # and the bank height, then added to an array, vol_lat_dt, for volume eroded # laterally *per timestep* at each node. This vol_lat_dt is reset to zero for # each timestep loop. vol_lat_dt is added to itself in case more than one primary # nodes are laterally eroding this lat_node # volume of lateral erosion per timestep vol_lat_dt[lat_node] += abs(petlat) * grid.dx * wd # send sediment downstream. sediment eroded from vertical incision # and lateral erosion is sent downstream qs_in[flowdirs[i]] += qs_in[i] - \ (dzver[i] * grid.dx**2) - (petlat * grid.dx * wd) # qsin to next node qs[:] = qs_in - (dzver * grid.dx**2) dzdt[:] = dzver * dt vol_lat[:] += vol_lat_dt * dt # this loop determines if enough lateral erosion has happened to change # the height of the neighbor node. for i in dwnst_nodes: lat_node = lat_nodes[i] wd = 0.4 * (da[i] * runoffms)**0.35 if lat_node > 0: # greater than zero now bc inactive neighbors are value -1 if z[lat_node] > z[i]: # vol_diff is the volume that must be eroded from lat_node so that its # elevation is the same as node downstream of primary node # UC model: this would represent undercutting (the water height at # node i), slumping, and instant removal. if UC == 1: voldiff = (z[i] + wd - z[flowdirs[i]]) * grid.dx**2 # TB model: entire lat node must be eroded before lateral erosion # occurs if TB == 1: voldiff = (z[lat_node] - z[flowdirs[i]]) * grid.dx**2 # if the total volume eroded from lat_node is greater than the volume # needed to be removed to make node equal elevation, # then instantaneously remove this height from lat node. already has # timestep in it if vol_lat[lat_node] >= voldiff: dzlat[lat_node] = z[flowdirs[i]] - z[lat_node] # -0.001 # after the lateral node is eroded, reset its volume eroded to # zero vol_lat[lat_node] = 0.0 # combine vertical and lateral erosion dz = dzdt + dzlat # change height of landscape z[:] = dz + z grid['node']['topographic__elevation'][grid.core_nodes] = z[grid.core_nodes] return grid, dzlat