def do_fires(self): """Uses the various components of the fire model to draw and (possibly) spread one year's fires. """ ########################################################## # 1) Find out how many ignitions there are this year, # and their associated weather streams/forecasts ########################################################## weather_seed = seed_add(self.primary_random_seed, self.current_year) weather_seed = seed_add(weather_seed, 298238234) weathers, forecasts = self.WeatherModel.get_new_fires(random_seed=weather_seed) #if there are no fires this year, return nothing if len(weathers) == 0: return [] #generate ignition locations def _rand_loc(i): random.seed(seed_add(weather_seed, i+253456)) _x = random.uniform(0, self.size[0]) _y = random.uniform(0, self.size[1]) return (_x, _y) locations = [ _rand_loc(i) for i in range(len(weathers)) ] #set up fire record object list fire_records = [None] * len(weathers) ### FOR EACH IGNITION THIS YEAR: for i in range(len(weathers)): #we can't get a list of suppression decisions all at once, because each fire # may change the landscape in the vicinity of the NEXT fire, and that may in # turn change the policy decision. So we have to do one at a time and # simulate the fire, before getting the next decision #check to make sure this weather stream has anything in it. if len(weathers[i]) == 0: continue ########################################################## # 2) Use the current suppression policy to make choices ########################################################## supr_decision = self.Policy.get_decision(self, locations[i], forecasts[i]) ########################################################## # 3) Simulate each fire (and potential suppression) ########################################################## fire_records[i] = self.FireModel.simulate_fire(self, locations[i], weathers[i], supr_decision) return fire_records
def draw_wind(self, date, random_seed=None): """Random wind speed in kph Drawing from an exponential distribution with mean = 10 TODO: what SHOULD it be? """ wind_mean = 10 random.seed(seed_add(random_seed,439201)) return random.expovariate(1.0 / wind_mean)
def draw_rain(self, date, random_seed=None): #from http://www.na.fs.fed.us/spfo/pubs/silvics_manual/Volume_1/pinus/ponderosa.htm # July, August, and September are dry; average rainfall is less than 25 mm # #that's ~ 0.8 mm per day. Lets draw from an exponential with mean = 0.8 mean_summer_precip = 0.8 #mm random.seed(seed_add(random_seed, 792723)) rainfall = random.expovariate(1.0 / mean_summer_precip) #cutting it off, so that many values are zero if rainfall < 0.8: rainfall = 0.0 return rainfall
def draw_RH(self, date, random_seed=None): #from the Western Regional Climate Center #http://www.wrcc.dri.edu/narratives/OREGON.htm #The afternoon average relative humidity ranges between 75 and 85 percent in January, #while in July this drops to 25 or 30 percent east of the Cascades and slightly higher # on the western side. Relative humidities of 10 to 20 percent often occur under extreme # conditions during the summer and early fall. date_mean_humidity = math.cos( (2.0*(date)*math.pi) / (365.0) ) * (80-15) + 15 #lets let actual humidity vary +/- 5% random.seed(seed_add(random_seed, 985727)) rh = random.normalvariate(date_mean_humidity, 2.5) if rh < 0: rh = 0.0 if rh > 100: rh = 100.0 return rh
def get_new_fires(self, random_seed=None): """Generates a new series of fires for a simulation year and returns any/all of them PARAMETERS ---------- random_seed Any numeric value for seeding the weather/forecast generation process. Ensures replicability. RETURNS ------- weather_streams, forecasts weather_streams A list of weather streams. Each weather stream is a 2-d list, indexed by day, of a particular fire/weather event. forecasts A list of weather forecasts. Each forecast is a a 2-d list, indexed by day, of weather forecast information for a particular fire/weather event. """ #how many fires are there this year? ave_fire_count = 0.5 random.seed(seed_add(random_seed, 471294932)) fire_count = int(random.expovariate(1.0 / ave_fire_count)) if fire_count == 0: return [[],[]] weather_streams = [None]*fire_count forecasts = [None]*fire_count for f in range(fire_count): #drawing fire days bewteen April-ish and October-ish streams = self.get_new_fire_weather_stream_pair( date=random.randint(100,310), random_seed=seed_add(random_seed,128439322)) weather_streams[f] = streams[0] forecasts[f] = streams[1] return weather_streams, forecasts
def draw_temp(self, date, random_seed=None): """Random temperature for the given date (as day of year)""" # from http://www.na.fs.fed.us/spfo/pubs/silvics_manual/Volume_1/pinus/ponderosa.htm # average annual temperatures are between 5 and 10 C (41 and 50 F), # and average July-August temperatures are between 17 and 21 C # Annual extremes are from -40 to 43 # #Lets call average annual temperature 7.5C, with average summer temps of 19C # so the mean temp will rise from 7.5C in May to 19C in July, and back down to # 7.5 in October # #May starts on day 121 date = date-121 date_mean_temp = math.sin( (2.0*(date)*math.pi) / (365.0) ) * (19-7.5) + 7.5 random.seed(seed_add(random_seed, 390752)) #lets let daily actual temperatures vary around the mean by way of a normal distribution # where 95%-ish remain within +/- 20C from the mean (so one standard devation will be 10) return random.normalvariate(date_mean_temp, 10)
def draw_wind_direction(self, date, random_seed=None): """A purely random wind direction, in degrees from North""" random.seed(seed_add(random_seed, 818127)) return random.uniform(0,364)
def get_new_fire_weather_stream_pair(self, date, random_seed=None): """Creates a new weather stream and forcast PARAMETERS ---------- date the integer date of the year random_seed Any numeric value for seeding the weather/forecast generation process. Ensures replicability. RETURNS ------- weather, forecast weather A list of dictionaries of weather variables with the primary index being day. forcast The associated weather forcast, of identical form/shape as "weather" """ #how many days long is this fire event? ave_fire_weather_days = 3 random.seed(seed_add(random_seed, 3789284)) day_count = int(random.expovariate(1.0/ave_fire_weather_days)) #if the fire event is less than three days long, we'll need to add some extras to make sure #that at least the FORECAST is three days long extra_forecast_days = 0 if day_count < 3: extra_forecast_days = 3 - day_count #add days for the lead-in time, which is needed to allow FWI index values to come # to equilibrium buffer_days = 10 day_count += buffer_days day_count += extra_forecast_days #get random weather variables for the days weather = [ self.draw_weather_variables(date, seed_add(random_seed, 9472843)) for i in range(day_count) ] forecast = weather[:] #now do a blending of the variables, walking forward from the first day, where # each day is a combination of (mostly) the previous day's value, and (somewhat) # it's own value w_blend = 0.8 f_blend = self.forecast_accuracy * w_blend for i in range(1,day_count): weather[i]["Temperature"] = w_blend * weather[i-1]["Temperature"] + (1-w_blend) * weather[i]["Temperature"] weather[i]["RH"] = w_blend * weather[i-1]["RH"] + (1-w_blend) * weather[i]["RH"] weather[i]["Wind Speed"] = w_blend * weather[i-1]["Wind Speed"] + (1-w_blend) * weather[i]["Wind Speed"] weather[i]["Wind Direction"] = w_blend * weather[i-1]["Wind Direction"] + (1-w_blend) * weather[i]["Wind Direction"] weather[i]["Rainfall"] = w_blend * weather[i-1]["Rainfall"] + (1-w_blend) * weather[i]["Rainfall"] forecast[i]["Temperature"] = f_blend * forecast[i-1]["Temperature"] + (1-f_blend) * forecast[i]["Temperature"] forecast[i]["RH"] = f_blend * forecast[i-1]["RH"] + (1-f_blend) * forecast[i]["RH"] forecast[i]["Wind Speed"] = f_blend * forecast[i-1]["Wind Speed"] + (1-f_blend) * forecast[i]["Wind Speed"] forecast[i]["Wind Direction"] = f_blend * forecast[i-1]["Wind Direction"] + (1-f_blend) * forecast[i]["Wind Direction"] forecast[i]["Rainfall"] = f_blend * forecast[i-1]["Rainfall"] + (1-f_blend) * forecast[i]["Rainfall"] #now add the FWI variables #The first day will get a value of 50 for FFMC, DMC, and DC (moisture and drought codes) #After that, they will adjust themselves as they go weather[0]["FFMC"] = 50 weather[0]["DMC"] = 50 weather[0]["DC"] = 50 forecast[0]["FFMC"] = 50 forecast[0]["DMC"] = 50 forecast[0]["DC"] = 50 for i in range(1,day_count): #I'm holding the month to whatever it was when the fire started to avoid any # discontinuities partway through a weather stream. Hense 'weather[0]["Date"]' MONTH = self.get_month(weather[0]["Date"]) LAT = 45 TEMP = weather[i]["Temperature"] RH = weather[i]["RH"] WIND = weather[i]["Wind Speed"] RAIN = weather[i]["Rainfall"] FFMCPrev = weather[i-1]["FFMC"] DMCPrev = weather[i-1]["DMC"] DCPrev = weather[i-1]["DC"] weather[i]["FFMC"] = FWI.FFMC(TEMP,RH,WIND,RAIN,FFMCPrev) weather[i]["DMC"] = FWI.DMC(TEMP,RH,RAIN,DMCPrev,LAT,MONTH) weather[i]["DC"] = FWI.DC(TEMP,RAIN,DMCPrev,LAT,MONTH) weather[i]["FWI"] = FWI.calcFWI(MONTH,TEMP,RH,WIND,RAIN,FFMCPrev,DMCPrev,DCPrev,LAT) TEMP = forecast[i]["Temperature"] RH = forecast[i]["RH"] WIND = forecast[i]["Wind Speed"] RAIN = forecast[i]["Rainfall"] FFMCPrev = forecast[i-1]["FFMC"] DMCPrev = forecast[i-1]["DMC"] DCPrev = forecast[i-1]["DC"] forecast[i]["FFMC"] = FWI.FFMC(TEMP,RH,WIND,RAIN,FFMCPrev) forecast[i]["DMC"] = FWI.DMC(TEMP,RH,RAIN,DMCPrev,LAT,MONTH) forecast[i]["DC"] = FWI.DC(TEMP,RAIN,DCPrev,LAT,MONTH) forecast[i]["FWI"] = FWI.calcFWI(MONTH,TEMP,RH,WIND,RAIN,FFMCPrev,DMCPrev,DCPrev,LAT) #the forecast will always be three days forecast = weather[buffer_days:buffer_days+3] #trim off the ten lead-in days weather = weather[buffer_days:] #trim off any extra forecast days if extra_forecast_days > 0: weather = weather[:-1*extra_forecast_days] return weather, forecast
def __init__(self, landscape_size=(129,129), random_seed=None): #maintenance flags self.VERBOSE = True self.DEBUGGING = True #primary simulation models self.FireModel = FGFire.SpreadModel() self.HarvestModel = FGHarvest.HarvestModel() self.TreatmentModel = FGTreatments.TreatmentModel() self.GrowthModel = FGGrowth.GrowthModel() self.WeatherModel = FGWeather.WeatherModel() #fire suppression policy self.Policy = FGPolicy.SuppressionPolicy() #sanitize landscape_size #check if it's iterable, and of length 2 if not hasattr(landscape_size,"__iter__"): #it's not iterable, so assume it's an int and correc that self.size = (landscape_size, landscape_size) else: #it's iterable, so save the first two values self.size = (landscape_size[0], landscape_size[1]) #important pathway values self.year_history = [] # a list to hold all of this simulation's YearRecord objects self.current_year = 0 self.primary_random_seed = random_seed #Primary data arrays, etc... # site index: this remains constant self.site_index = DS.diamond_square(self.size, min_height=0, max_height=100, roughness=0.5, random_seed=seed_add(self.primary_random_seed, 0),AS_NP_ARRAY=True).astype(int) # each cell's stand initiation year: each one started in the last 100 years or so self.stand_init_yr = DS.diamond_square(self.size, min_height=-100, max_height=0, roughness=0.95, random_seed=seed_add(self.primary_random_seed, 1), AS_NP_ARRAY=True).astype(int) # the last year in which each cell experienced a stand-replacing fire, leaving dead trees standing self.stand_rplc_fire_yr = DS.diamond_square(self.size, min_height=-100, max_height=0, roughness=0.95, random_seed=seed_add(self.primary_random_seed, 2), AS_NP_ARRAY=True).astype(int) # the last year in which there was a surface fire self.surf_fire_yr = DS.diamond_square(self.size, min_height=-100, max_height=0, roughness=0.95, random_seed=seed_add(self.primary_random_seed, 2), AS_NP_ARRAY=True).astype(int) #units #how many acres per cell? self.acres_per_cell = 25
def _rand_loc(i): random.seed(seed_add(weather_seed, i+253456)) _x = random.uniform(0, self.size[0]) _y = random.uniform(0, self.size[1]) return (_x, _y)