def simulate_water_quality(tree, cell_res, fn, parent_cell=None, current_cell=None): """ Perform a water quality simulation by doing simulations on each type of cells (leaves), then adding them together going upward (summing the values of a node's subtrees and storing them at that node). `parent_cell` is the cell type (a string with a soil type and land use separated by a colon) of the parent of the present node in tree. `current_cell` is the cell type for the present node. `cell_res` is the size of each cell (used for turning inches of water into volumes of water). `tree` is the (sub)tree of cell distributions that is currently under consideration. `fn` is a function that takes a cell type and a number of cells and returns a dictionary containing runoff, et, and inf as volumes. This typically just calls `simulate_cell_year`, but can be set to something else if e.g. a simulation over a different time-scale is desired. """ # Internal node. if 'cell_count' in tree and 'distribution' in tree: # simulate subtrees tally = {} for cell, subtree in tree['distribution'].items(): simulate_water_quality(subtree, cell_res, fn, current_cell, cell) subtree_ex_dist = subtree.copy() subtree_ex_dist.pop('distribution', None) tally = dict_plus(tally, subtree_ex_dist) # update this node tree.update(tally) # Leaf node. elif 'cell_count' in tree and 'distribution' not in tree: # runoff, et, inf n = tree['cell_count'] result = fn(current_cell, n) tree.update(result) # water quality if n != 0: runoff = result['runoff-vol'] / n liters = get_volume_of_runoff(runoff, n, cell_res) land_use = current_cell.split(':')[1] if is_bmp(land_use) or land_use == 'no_till' or \ land_use == 'cluster_housing': land_use = parent_cell.split(':')[1] for pol in get_pollutants(): tree[pol] = get_pollutant_load(land_use, pol, liters)
def simulate_cell_day(precip, evaptrans, cell, cell_count): """ Simulate a bunch of cells of the same type during a one-day event. `precip` is the amount of precipitation in inches. `evaptrans` is evapotranspiration. `cell` is a string which contains a soil type and land use separated by a colon. `cell_count` is the number of cells to simulate. The return value is a dictionary of runoff, evapotranspiration, and infiltration as volumes of water. """ def clamp(runoff, et, inf, precip): """ This function ensures that runoff + et + inf <= precip. NOTE: Infiltration is normally independent of the precipitation level, but this function introduces a slight dependency (that is, at very low levels of precipitation, this function can cause infiltration to be smaller than it ordinarily would be. """ total = runoff + et + inf if (total > precip): scale = precip / total runoff *= scale et *= scale inf *= scale return (runoff, et, inf) precip = max(0.0, precip) soil_type, land_use, bmp = cell.lower().split(':') # If there is no precipitation, then there is no runoff or # infiltration; however, there is evapotranspiration. (It is # understood that over a period of time, this can lead to the sum # of the three values exceeding the total precipitation.) if precip == 0.0: return { 'runoff-vol': 0.0, 'et-vol': 0.0, 'inf-vol': 0.0, } # If the BMP is cluster_housing or no_till, then make it the # land-use. This is done because those two types of BMPs behave # more like land-uses than they do BMPs. if bmp and not is_bmp(bmp): land_use = bmp or land_use # When the land-use is a built-type and the level of precipitation # is two inches or less, use the Pitt Small Storm Hydrology Model. # When the land-use is a built-type but the level of precipitation # is higher, the runoff is the larger of that predicted by the # Pitt model and NRCS model. Otherwise, return the NRCS amount. if is_built_type(land_use) and precip <= 2.0: runoff = runoff_pitt(precip, land_use) elif is_built_type(land_use): pitt_runoff = runoff_pitt(2.0, land_use) nrcs_runoff = runoff_nrcs(precip, evaptrans, soil_type, land_use) runoff = max(pitt_runoff, nrcs_runoff) else: runoff = runoff_nrcs(precip, evaptrans, soil_type, land_use) inf = max(0.0, precip - (evaptrans + runoff)) (runoff, evaptrans, inf) = clamp(runoff, evaptrans, inf, precip) return { 'runoff-vol': cell_count * runoff, 'et-vol': cell_count * evaptrans, 'inf-vol': cell_count * inf, }
def simulate_cell_day(precip, evaptrans, cell, cell_count): """ Simulate a bunch of cells of the same type during a one-day event. `precip` is the amount of precipitation in inches. `evaptrans` is evapotranspiration. `cell` is a string which contains a soil type and land use separated by a colon. `cell_count` is the number of cells to simulate. The return value is a dictionary of runoff, evapotranspiration, and infiltration as volumes of water. """ soil_type, land_use, bmp = cell.lower().split(':') # If there is no precipitation, then there is no runoff or # infiltration. There is evapotranspiration, however (is # understood that over a period of time, this can lead to the sum # of the three values exceeding the total precipitation). if precip == 0.0: return { 'runoff-vol': 0.0, 'et-vol': cell_count * evaptrans, 'inf-vol': 0.0 } # Deal with the Best Management Practices (BMPs). For most BMPs, # the infiltration is read from the table and the runoff is what # is left over after infiltration and evapotranspiration. Rain # gardens are treated differently. if bmp and is_bmp(bmp) and bmp != 'rain_garden': inf = lookup_bmp_infiltration(soil_type, bmp) # infiltration runoff = precip - (evaptrans + inf) # runoff return { 'runoff-vol': cell_count * runoff, 'et-vol': cell_count * evaptrans, 'inf-vol': cell_count * inf } elif bmp and bmp == 'rain_garden': # Here, return a mixture of 20% ideal rain garden and 80% high # intensity residential. inf = lookup_bmp_infiltration(soil_type, bmp) runoff = precip - (evaptrans + inf) hi_res_cell = soil_type + ':hi_residential:' hi_res = simulate_cell_day(precip, evaptrans, hi_res_cell, 1) hir_run = hi_res['runoff-vol'] hir_et = hi_res['et-vol'] hir_inf = hi_res['inf-vol'] return { 'runoff-vol': cell_count * (0.2 * runoff + 0.8 * hir_run), 'et-vol': cell_count * (0.2 * evaptrans + 0.8 * hir_et), 'inf-vol': cell_count * (0.2 * inf + 0.8 * hir_inf) } # At this point, if the `bmp` string has non-zero length, it is # equal to either 'no_till' or 'cluster_housing'. land_use = bmp or land_use # When the land use is a built-type and the level of precipitation # is two inches or less, use the Pitt Small Storm Hydrology Model. # When the land use is a built-type but the level of precipitation # is higher, the runoff is the larger of that predicted by the # Pitt model and NRCS model. Otherwise, return the NRCS amount. if is_built_type(land_use) and precip <= 2.0: runoff = runoff_pitt(precip, land_use) elif is_built_type(land_use): pitt_runoff = runoff_pitt(2.0, land_use) nrcs_runoff = runoff_nrcs(precip, evaptrans, soil_type, land_use) runoff = max(pitt_runoff, nrcs_runoff) else: runoff = runoff_nrcs(precip, evaptrans, soil_type, land_use) inf = precip - (evaptrans + runoff) return { 'runoff-vol': cell_count * runoff, 'et-vol': cell_count * evaptrans, 'inf-vol': cell_count * max(inf, 0.0), }