SpatialPrecipitationDistribution
(grid, number_of_years=1, orographic_scenario=None, max_numstorms=5000)[source]¶Bases: landlab.core.model_component.Component
Generate spatially resolved precipitation events.
A component to generate a sequence of spatially resolved storms over a grid, following a lightly modified version (see below) of the stochastic methods of Singer & Michaelides, Env Res Lett 12, 104011, 2017, & Singer et al., Geosci. Model Dev., accepted, 10.5194/gmd201886.
The method is heavily stochastic, and at the present time is intimately calibrated against the conditions at Walnut Gulch, described in those papers. In particular, assumptions around intensityduration calibration and orographic rainfall are “burned in” for now, and are not accessible to the user. The various probability distributions supplied to the various run methods default to WG values, but are easily modified. This calibration reflects a US desert southwest “monsoonal” climate, and the component distinguishes (optionally) between two seasons, “monsoonal” and “winter”. The intensityduration relationship is shared between the seasons, and so may prove useful in a variety of stormdominated contexts.
The default is to disable the orographic rainfall functionality of the component. However, if orographic_scenario == ‘Singer’, the component requires a ‘topographic__elevation’ field to already exist on the grid at the time of instantiation.
The component has two ways of simulating a “year”. This choice is controlled by the ‘limit’ parameter of the yield methods. If limit== ‘total_rainfall’, the component will continue to run until the total rainfall for the season and/or year exceeds a stochastically generated value. This method is directly comparable to the Singer & Michaelides method, but will almost always result in years which are not one calendar year long, unless the input distributions are very carefully recalibrated for each use case. If limit==’total_time’, the component will terminate a season and/or year once the elapsed time exceeds one year. In this case, the total rainfall will not correspond to the stochastically generated total. You can access the actual total for the last season using the property (median_)total_rainfall_last_season.
Note that this component cannot simulate the occurrence of more than one storm at the same time. Storms that should be synchronous will instead occur sequentially, with no interstorm time. This limitation means that if enough storms occur in a year that numstorms*mean_storm_duration exceeds one year, the number of simulated storms will saturate. This limitation may be relaxed in the future.
The component offers the option to modify the maximum number of storms simulated per year. If you find simulations encountering this limit too often, you may need to raise this limit. Conversely, it could be lowered to reduce memory usage over small grids. However, in increasing the value, beware  the component maintains two limit*nnodes arrays, which will chew through memory if the limit gets too high. The default will happily simulate grids up to around 50 km * 50 km using the default probability distributions.
Key methods are:
A large number of properties are available to access storm properties during generation:
 current_year
 current_season
 storm_depth_last_storm
 storm_recession_value_last_storm
 storm_duration_last_storm
 storm_area_last_storm
 storm_intensity_last_storm
 total_rainfall_this_season
 total_rainfall_this_year
 total_rainfall_last_season
 total_rainfall_last_year
 median_total_rainfall_this_season
 median_total_rainfall_this_year
 median_total_rainfall_last_season
 median_total_rainfall_last_year
 number_of_nodes_under_storm
 nodes_under_storm
 target_median_total_rainfall_this_season
Note that becuase these are medians not means, median_total_rainfall_last_season + median_total_rainfall_this_season != median_total_rainfall_this_year.
Significant differences between this component and the Singer code are:
 The component does not model evapotranspiration. Use a separate
 Landlab component for this.
 The component runs only over a LL grid; there is no such thing as a
 validation or simulation run.
 It produces “fuzz” around intensity values using a continuous
 distribution; Singer does this with integer steps.
 Step changes midrun cannot be explicitly modelled. Instead, run the
 component for a fixed duration, make the change to the distribution input parameter, then run it again.
 Storms can be centred at any spatial coordinate, not just over nodes.
 Edge buffering is now dynamic; i.e., big storms have a bigger edge
 buffer than smaller storms. Storms can be centered off the grid edges.
 Storms are never discarded  once a storm is drawn, it must hit the
 catchment, and positions are repeatedly selected until this can happen. Singer’s method would discard such a storm and draw a new one.
 Durations are not rescaled to ensure both total duration and total
 precip are both satisfied at the same time, as in Singer’s method. Instead, the component either matches a year’s duration, or exactly a year’s worth of rain. This choice is dictated by the limit parameter in the yield methods.
Examples
>>> import numpy as np
>>> from landlab import RasterModelGrid, VoronoiDelaunayGrid
>>> mg = RasterModelGrid((10, 10), xy_spacing=1000.)
>>> rain = SpatialPrecipitationDistribution(mg)
Calling yield_storms will produce storminterstorm duration (hr) pairs until the model runtime has elapsed.
>>> np.random.seed(1)
>>> total_t_each_step = [
... (storm+interstorm) for (storm, interstorm) in rain.yield_storms()]
>>> len(total_t_each_step)
41
>>> np.isclose(sum(total_t_each_step)/24., 365.)
True
The actual rainfall intensities during that interval are accessible in the ‘rainfall__flux’ field (mm/hr). The storm centre does not have to be over the grid, but in this case, it was for the last simulated storm:
>>> mg.at_node['rainfall__flux'].argmax()
80
We can also run the component for only one season (i.e., only using one of the pdf sets describing the storm properties):
>>> for field in ('rainfall__flux', 'rainfall__total_depth_per_year'):
... _ = mg.at_node.pop(field) # clear out the existing fields
>>> rain = SpatialPrecipitationDistribution(mg, number_of_years=2)
>>> np.random.seed(5)
>>> total_t_each_step = [
... (storm+interstorm) for (storm, interstorm) in rain.yield_storms(
... style='monsoonal', monsoon_fraction_of_year=0.35)]
>>> np.isclose(sum(total_t_each_step)/24./365./2., 0.35)
True
Note this behaviour can be stopped by upping monsoon_fraction_of_year:
>>> np.random.seed(5)
>>> total_t_each_step = [
... (storm+interstorm) for (storm, interstorm) in rain.yield_storms(
... style='monsoonal', monsoon_fraction_of_year=1.)]
>>> np.isclose(round(sum(total_t_each_step)/24./365./2., 2), 1.)
True
yield_years yields the number of storms in the last whole year. Use ‘rainfall__total_depth_per_year’ to access the rainfall map for the last fully elapsed year, or equivalently, the total_rainfall_last_year property. Note the component seamlessly handles nonraster grid types:
>>> vdg = VoronoiDelaunayGrid(np.random.rand(100)*1000.,
... np.random.rand(100)*1000.)
>>> np.random.seed(3)
>>> rain = SpatialPrecipitationDistribution(vdg, number_of_years=3)
>>> storms_each_year = []
>>> for total_storms in rain.yield_years(style='monsoonal',
... total_rf_trend=0.05,
... storminess_trend=0.02):
... storms_each_year.append(total_storms)
... assert(np.all(np.equal(
... vdg.at_node['rainfall__total_depth_per_year'],
... rain.total_rainfall_last_year)))
>>> sum(storms_each_year)
11
yield_seasons yields rainfall statistics for individual seasons. Access these using the various provided component properties. Note that we can get the component to yield a total rainfall that is calibrated to the supplied total_rf_gaussians if we set limit to ‘total__rainfall’ rather than ‘total_time’ (at the cost of exactly matching the season length):
>>> for field in ('rainfall__flux', 'rainfall__total_depth_per_year'):
... _ = mg.at_node.pop(field) # clear out the existing fields
>>> rain = SpatialPrecipitationDistribution(mg, number_of_years=2)
>>> np.random.seed(5)
>>> season_list = []
>>> theoretical_median_rf_season = []
>>> median_rf_season = []
>>> median_rf_last_year = []
>>> mean_rf_season = []
>>> mean_rf_last_year = []
>>> for storm_number in rain.yield_seasons(limit='total_rainfall'):
... season_list.append(rain.current_season)
... theoretical_median_rf_season.append(
... rain.target_median_total_rainfall_this_season)
... median_rf_season.append(rain.median_total_rainfall_this_season)
... median_rf_last_year.append(rain.median_total_rainfall_last_year)
... mean_rf_season.append(rain.total_rainfall_this_season.mean())
... mean_rf_last_year.append(rain.total_rainfall_last_year.mean())
>>> season_list == ['M', 'W', 'M', 'W']
True
>>> [meas > sim for (meas, sim) in zip(
... median_rf_season, theoretical_median_rf_season)] # must exceed
[True, True, True, True]
>>> np.isclose(median_rf_last_year[0], 0.)
True
>>> for season in (0, 2): # this property must be the same in both seasons
... np.isclose(median_rf_last_year[season],
... median_rf_last_year[season + 1])
True
True
Note that because we work here with medians, the seasonal medians don’t sum to the year median, but the means do:
>>> np.isclose(median_rf_last_year[2],
... median_rf_season[0] + median_rf_season[1])
False
>>> np.isclose(mean_rf_last_year[2],
... mean_rf_season[0] + mean_rf_season[1])
True
References
Required Software Citation(s) Specific to this Component
Singer, M., Michaelides, K., Hobley, D. (2018). STORM 1.0: a simple, flexible, and parsimonious stochastic rainfall generator for simulating climate and climate change. Geoscientific Model Development 11(9), 37133726. https://dx.doi.org/10.5194/gmd1137132018
Additional References
None Listed
Create the SpatialPrecipitationDistribution generator component.
Parameters: 


__init__
(grid, number_of_years=1, orographic_scenario=None, max_numstorms=5000)[source]¶Create the SpatialPrecipitationDistribution generator component.
Parameters: 


calc_annual_rainfall
(style='whole_year', monsoon_total_rf_gaussian={'mu': 207.0, 'sigma': 64.0}, winter_total_rf_gaussian={'mu': 1.65, 'sigma': 52.0})[source]¶Return a tuple of rainfall totals (mm) for the year, with entries subdividing the yearly total into seasons as appropriate.
Parameters: 


Returns:  tuple – If style==’monsoonal’ or ‘winter’, a len(1) tuple of the total rf. If style==’whole_year’, a len(2) tuple of (monsoon, winter) totals. 
Return type:  (first_season_total, [second_season_total]) 
Examples
>>> mg = RasterModelGrid((10, 10), xy_spacing=500.)
>>> z = mg.add_zeros("topographic__elevation", at="node")
>>> rain = SpatialPrecipitationDistribution(mg)
>>> mytotals = []
>>> for yr in range(5):
... mytotals.append(rain.calc_annual_rainfall(style='whole_year'))
>>> [len(x) == 2 for x in mytotals]
[True, True, True, True, True]
>>> mytotals = []
>>> for yr in range(3):
... mytotals.append(rain.calc_annual_rainfall(style='monsoonal'))
>>> [len(x) == 1 for x in mytotals]
[True, True, True]
coordinates_of_last_storm_center
¶Get the coordinates of the center of the last storm as (x, y).
current_season
¶Get the current season.
‘M’ is monsoon, ‘W’ is winter.
current_year
¶Get the current year as an int.
median_total_rainfall_last_season
¶Get the median total rainfall recorded over the open nodes of the grid during the last (completed) simulated season (mm).
median_total_rainfall_last_year
¶Get the median total rainfall recorded over the open nodes of the grid during the last (completed) simulated year (mm).
median_total_rainfall_this_season
¶Get the accumulated median total rainfall over the open nodes of the grid so far this season (mm).
median_total_rainfall_this_year
¶Get the accumulated median total rainfall over the open nodes of the grid so far this year (mm).
nodes_under_storm
¶Get the IDs of the nodes under the last storm.
number_of_nodes_under_storm
¶Get the number of nodes under the last storm.
storm_area_last_storm
¶Get the area (in m**2) of the last storm.
storm_depth_last_storm
¶Get the maximum storm depth during the last storm (mm).
storm_duration_last_storm
¶Get the duration (in hrs) of the last storm.
storm_intensity_last_storm
¶Get the intensity (mm/hr) of the last storm, averaged under the storm.
footprint. Note that duration * intensity != storm max depth.
storm_recession_value_last_storm
¶Get the recession parameter (radial dieoff) for the last storm.
target_median_total_rainfall_this_season
¶Get the stochastically generated “target” average total rainfall amount over the catchment for the current season.
If limit == ‘total_rainfall’, this will be very close to median_total_rainfall_last_season. If ‘total_time’, it will diverge from this value.
total_rainfall_last_season
¶Get the total recorded rainfall over the last (completed) simulated season, spatially resolved (mm).
total_rainfall_last_year
¶Get the total recorded rainfall over the last (completed) simulated year, spatially resolved (mm).
Equivalent to the field ‘rainfall__total_depth_per_year’.
total_rainfall_this_season
¶Get the accumulated, spatially resolved total rainfall over the grid for the season so far (mm).
total_rainfall_this_year
¶Get the accumulated, spatially resolved total rainfall over the grid for the year so far (mm).
yield_seasons
(limit='total_time', style='whole_year', total_rf_trend=0.0, storminess_trend=0.0, monsoon_fraction_of_year=0.42, monsoon_total_rf_gaussian={'mu': 207.0, 'sigma': 64.0}, monsoon_storm_duration_GEV={'mu': 34.1409, 'shape': 0.570252, 'sigma': 35.7389, 'trunc_interval': (1.0, 1040.0)}, monsoon_storm_area_GEV={'mu': 122419000.0, 'shape': 0.0, 'sigma': 28387600.0, 'trunc_interval': (5000000.0, 300000000.0)}, monsoon_storm_interarrival_GEV={'mu': 10.6108, 'shape': 0.807971, 'sigma': 9.4957, 'trunc_interval': (0.0, 720.0)}, monsoon_storm_radial_weakening_gaussian={'mu': 0.25, 'sigma': 0.08, 'trunc_interval': (0.15, 0.67)}, winter_total_rf_gaussian={'mu': 1.65, 'sigma': 52.0}, winter_storm_duration_fisk={'c': 1.0821, 'scale': 68.4703, 'trunc_interval': (1.0, 5000.0)}, winter_storm_area_GEV={'mu': 122419000.0, 'shape': 0.0, 'sigma': 28387600.0, 'trunc_interval': (5000000.0, 300000000.0)}, winter_storm_interarrival_GEV={'mu': 47.4944, 'shape': 1.1131, 'sigma': 53.2671, 'trunc_interval': (0.0, 720.0)}, winter_storm_radial_weakening_gaussian={'mu': 0.25, 'sigma': 0.08, 'trunc_interval': (0.15, 0.67)})[source]¶Yield a timeseries giving the number if storms occurring each season in a rainfall simulation. Only meaningfully different from yield_years if style==’whole_year’.
All default distributions specified as parameters reflect values for Walnut Gulch, see Singer & Michaelides, 2017 & Singer et al, submitted.
Parameters: 


Yields: 

yield_storms
(limit='total_time', style='whole_year', total_rf_trend=0.0, storminess_trend=0.0, monsoon_fraction_of_year=0.42, monsoon_total_rf_gaussian={'mu': 207.0, 'sigma': 64.0}, monsoon_storm_duration_GEV={'mu': 34.1409, 'shape': 0.570252, 'sigma': 35.7389, 'trunc_interval': (0.0, 1040.0)}, monsoon_storm_area_GEV={'mu': 122419000.0, 'shape': 0.0, 'sigma': 28387600.0, 'trunc_interval': (5000000.0, 300000000.0)}, monsoon_storm_interarrival_GEV={'mu': 10.6108, 'shape': 0.807971, 'sigma': 9.4957, 'trunc_interval': (0.0, 720.0)}, monsoon_storm_radial_weakening_gaussian={'mu': 0.25, 'sigma': 0.08}, winter_total_rf_gaussian={'mu': 1.65, 'sigma': 52.0}, winter_storm_duration_fisk={'c': 1.0821, 'scale': 68.4703, 'trunc_interval': (0.0, 5000.0)}, winter_storm_area_GEV={'mu': 122419000.0, 'shape': 0.0, 'sigma': 28387600.0, 'trunc_interval': (5000000.0, 300000000.0)}, winter_storm_interarrival_GEV={'mu': 47.4944, 'shape': 1.1131, 'sigma': 53.2671, 'trunc_interval': (0.0, 720.0)}, winter_storm_radial_weakening_gaussian={'mu': 0.25, 'sigma': 0.08, 'trunc_interval': (0.15, 0.67)})[source]¶Yield a timeseries giving the number of storms occurring each year in a rainfall simulation.
All default distributions specified as parameters reflect values for Walnut Gulch, see Singer & Michaelides, 2017 & Singer et al, submitted.
Parameters: 


Yields:  (storm_t, interval_t) ((float, float)) – Tuple pair of duration of a single storm, then the interstorm interval that follows it. In hrs. The rainfall__flux field describes the rainfall rate during the interval storm_t as the tuple is yielded. In HRS. Note that the rainfall__total_depth_per_year field gives the total accumulated rainfall depth during the last completed model year, not the year to the point of yield. For the latter, use the property total_rainfall_this_year. 
yield_years
(limit='total_time', style='whole_year', total_rf_trend=0.0, storminess_trend=0.0, monsoon_fraction_of_year=0.42, monsoon_total_rf_gaussian={'mu': 207.0, 'sigma': 64.0}, monsoon_storm_duration_GEV={'mu': 34.1409, 'shape': 0.570252, 'sigma': 35.7389, 'trunc_interval': (1.0, 1040.0)}, monsoon_storm_area_GEV={'mu': 122419000.0, 'shape': 0.0, 'sigma': 28387600.0, 'trunc_interval': (5000000.0, 300000000.0)}, monsoon_storm_interarrival_GEV={'mu': 10.6108, 'shape': 0.807971, 'sigma': 9.4957, 'trunc_interval': (0.0, 720.0)}, monsoon_storm_radial_weakening_gaussian={'mu': 0.25, 'sigma': 0.08, 'trunc_interval': (0.15, 0.67)}, winter_total_rf_gaussian={'mu': 1.65, 'sigma': 52.0}, winter_storm_duration_fisk={'c': 1.0821, 'scale': 68.4703, 'trunc_interval': (1.0, 5000.0)}, winter_storm_area_GEV={'mu': 122419000.0, 'shape': 0.0, 'sigma': 28387600.0, 'trunc_interval': (5000000.0, 300000000.0)}, winter_storm_interarrival_GEV={'mu': 47.4944, 'shape': 1.1131, 'sigma': 53.2671, 'trunc_interval': (0.0, 720.0)}, winter_storm_radial_weakening_gaussian={'mu': 0.25, 'sigma': 0.08, 'trunc_interval': (0.15, 0.67)})[source]¶Yield a timeseries giving the number if storms occurring each year in a rainfall simulation.
All default distributions specified as parameters reflect values for Walnut Gulch, see Singer & Michaelides, 2017 & Singer et al, submitted.
Parameters: 


Yields:  number_of_storms_per_year (float) – Float that gives the number of storms simulated in the year that elapsed since the last yield. The rainfall__total_depth_per_year field gives the total accumulated rainfall depth during the year preceding the yield. rainfall__flux gives the rainfall intensity of the last storm in that year. 