Last active
December 29, 2015 23:39
-
-
Save AndrewBMartin/7743801 to your computer and use it in GitHub Desktop.
Hacker School Application: A routine that simulates the growth and yields of a list of timber stands over a 30 period planning horizon, and writes the results to 4 output files. These output files are used to supply parameter values for an AMPL generated Linear Programming model.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import csv | |
import pres_builder as pb | |
# Indicates whether prescription set A or B | |
# is to be generated, in the case a prescription | |
# set will be generated. | |
global model_type | |
model_type = 'B' | |
def main(pre_pres=None, model_type=model_type, directory=None): | |
""" | |
The central routine from which stand_types, | |
stand_types_eco, harv_types, and stand_age_period | |
files are generated. These files describe each | |
stands growth as it follows a variety of | |
management prescriptions, such as clearcut, | |
commercial thin, shelterwood, selection, | |
and buffer harvests. | |
If running through the User Interface addtion | |
pre_pres will be a list of lists describing | |
each prescription to be generated. | |
If no UI is present then there is a built in | |
pres_generator() function that generates | |
prescriptions based on parameters that are | |
hard coded into the routine below. | |
There is also a global parameter and | |
function argument 'model_type'. If set | |
to 'A' this means, build the full prescription | |
set, as used in the phase 2 and industry models | |
of Andrew B. Martin's thesis. If set to 'B', a | |
reduced prescription set is built that is the same | |
as the phase 1 prescription set from the same thesis. | |
kwargs | |
pre_pres: list of prescriptions generated through | |
user interface application | |
model_type: either 'A' or 'B'; indicates whether the | |
restricted or full prescription set will be | |
generated | |
directory: directory to which files are to be written | |
""" | |
# generate list of prescriptions | |
if pre_pres is None: | |
prescriptions = pres_generator() | |
else: | |
prescriptions = pre_pres | |
# Do nothing will alwyas be the first prescription | |
nothing = prescriptions[0] | |
buffers = [p for p in prescriptions if p.name == 'buffer'] | |
# Any prescription with number >= 200 is a buffer prescription. | |
# Only buffer stands are elligible for these, and nothing else | |
# so separate buffer prescriptions from prescriptions for | |
# non-buffer stands | |
prescriptions = [p for p in prescriptions if p.number < 200] | |
# Print to screen a list of prescriptions | |
# see function description below | |
list_pres(prescriptions) | |
# directory to which files will be | |
# written | |
if directory: | |
cwd = directory | |
else: | |
cwd = os.getcwd() | |
# FBF1 file in form: stand, age, buffer, excl, type; for each stand | |
# Modified for hacker school application to have sample of | |
# stands supplied instead of being read from | |
# an external file | |
# fbf1 = csv.reader(open(cwd+'\\FBF1.tab'), delimiter='\t') | |
fbf1 = getStands() | |
# These are the master lists that are appended to after a stand | |
# runs through all the prescriptions | |
stand_types = [] | |
stand_types_eco = [] | |
# Age and Harv_type tables are indexed by initial stand age, | |
# so use sets to ensure duplicates are not written | |
stand_age = set() | |
harv_types = set() | |
# list of all master lists | |
all_list = [stand_types, stand_types_eco, stand_age, harv_types] | |
# file names that will be written from each master list - in order | |
files = [os.path.join(cwd,'stand_types'), | |
os.path.join(cwd,'stand_types_eco'), | |
os.path.join(cwd, 'stand_age_period'), | |
os.path.join(cwd, 'harv_types')] | |
# headers to be written for each master list file | |
headers = [['Stand','Pres','Period','Stand_Type'], | |
['Stand','Pres','Period','Stand_Type'], | |
['Initial_Age','Pres','Period','Age'], | |
['Initial_Age','Pres','Period','Cut_Type']] | |
# For each stand in fbf1, run through all the prescriptions | |
for stand in fbf1: | |
# sets that store lines to be written for current stand; | |
# their contents will be appended to the master lists | |
# after the current stand has run through all | |
# prescriptions | |
temp_lists = [ set(), set(), set(), set() ] | |
# store entries to master list for this | |
# prescription. The entries in this list | |
# will populate the sets of temp_lists | |
# above, after each prescription is | |
# run through | |
temp_temp_list = [] | |
# Read info from fbf1 row into variables | |
number, age, buff, excl, stype = stand_unpack(stand) | |
# Give some idea of progress | |
if int(number)%1000 == 0: | |
print 'Feeding Stand {0} through prescriptions'.format(number) | |
# Stand site-class, used for shelterwood and CC prescriptions. | |
# This is to determine age of Max MAI for intervention scheduling | |
sc = stype[-3] | |
# Forest state of the stand, used for eligibility tests | |
state = stype[len(stype)-5:len(stype)-3] | |
# Excl stands are only eligible for the do nothing prescription | |
# Buffer stands are only eligible for do nothing and buffer harvest | |
if not buff and not excl: | |
for pres in prescriptions: | |
# Eligibility for each prescription | |
# is dependent on age, state, and/or site-class | |
if eligible(sc, state, age, pres): | |
# Store file entries from assigning current pres | |
# to current stand | |
temp_temp_list = pres.assign_stand(stand) | |
# append current prescription lists to | |
# current stand lists | |
set_unpack(temp_temp_list, temp_lists) | |
elif buff and not excl: | |
for pres in buffers: | |
if eligible(sc, state, age, pres): | |
temp_temp_list = pres.assign_stand(stand) | |
set_unpack(temp_temp_list, temp_lists) | |
else: | |
temp_temp_list = nothing.assign_stand(stand) | |
# Don't need to do this, but it keeps the three | |
# streams consistent | |
set_unpack(temp_temp_list, temp_lists) | |
# Append stand list entries to master lists after running | |
# through all prescriptions | |
list_unpack(temp_lists, all_list) | |
# write each list to files | |
for a, f in enumerate(files): | |
file_writer = csv.writer(open(f+'.tab', 'w'), delimiter='\t') | |
# universal header for ampl | |
# 3 indicates 3 index columns, 1 indicates 1 data column | |
file_writer.writerow(['ampl.tab 3 1']) | |
file_writer.writerow(headers[a]) | |
for row in all_list[a]: | |
file_writer.writerow(row) | |
def list_unpack(lists_1, lists_2): | |
""" | |
Copy the contents from each list/set in lists_1 | |
into lists_2. | |
Stand Type, Stand Type Eco will always be lists | |
and will always occupy the first two indices of | |
both lists. | |
Stand Age and Harv Type are sets will always be | |
indices 2,3 respectively in the both lists. | |
""" | |
for a,i in enumerate(lists_1): | |
# stand Type and stand type eco are lists | |
if a < 2: | |
for j in i: | |
lists_2[a].append(j) | |
# stand age and harv type are sets | |
else: | |
for j in i: | |
lists_2[a].add(j) | |
def set_unpack(sets_1, sets_2): | |
""" | |
Like list unpack, but for two sets | |
""" | |
for a,i in enumerate(sets_1): | |
sets_2[a].update(i) | |
def eligible(sc, state, age, pres): | |
""" | |
Test a stand's site-class and forest state | |
against the restriction set of a prescription. | |
Return true if the stand is eligible for the prescription; | |
false otherwise. | |
""" | |
# pres.restrictions is a dictionary | |
# if it has a key called 'sc' then the | |
# value corresponding to that key, in this | |
# case a list of ineligible site-class numbers, | |
# will be returned; if it does not contain | |
# this key, then a list containing 0 will | |
# be returned. | |
if int(sc) not in pres.restrictions.get('sc', [0]): | |
if state not in pres.restrictions.get('state', [0]): | |
try: | |
# If the prescription has | |
# an alternate track, then | |
# every stand is eligible for | |
# it regardless of age. | |
if pres.alt_i_sched: | |
return True | |
# This exception will be raised if | |
# a prescription does not have an | |
# alternate track. In which case | |
# if the stand is older than the | |
# initial age of the prescription, | |
# it is ineligible. | |
except AttributeError: | |
if age > pres.in_age: | |
return False | |
else: | |
return True | |
return False | |
return False | |
def stand_unpack(stand): | |
""" | |
Break up a row from the FBF1 table | |
into stand number, age, exclusion status, | |
buffer status, and stand type. | |
Return a list of these values. | |
""" | |
number = stand[0] | |
age = int(stand[1]) | |
stype = stand[4] | |
excl = int(stand[3]) | |
buff = int(stand[2]) | |
return [number, age, buff, excl, stype] | |
def pres_generator(): | |
""" | |
Generate and return a list of prescription | |
objects. | |
This is called from main() if no UI | |
is attached; otherwise it is not called. | |
""" | |
# Track prescription number | |
count = 0 | |
# Store all generated prescriptions here | |
pres_list = [] | |
# Generate do nothing prescription. | |
# Set initial intervention age at 50 | |
# this ensures no stands will ever | |
# reach it, since stands die at 40 | |
nothing = [50] | |
# g function calls are used throughout; | |
# they pass action to functions defined | |
# below that feed parameters into the | |
# pres_build module where prescription | |
# objects are instantiated | |
temp_list = g_buffer(nothing, count) | |
for t in temp_list: | |
pres_list.append(t) | |
count+=1 | |
# Generate clearcut prescriptions; | |
# One list for each site-class. | |
# There are four site-classes. | |
cc_tuples = [0]*4 | |
# Initial CC intervention then | |
# no more interventions for the | |
# rest of the planning horizon | |
cc_nothing = [0]*4 | |
# Initial CC intervention in | |
# +\- 2 periods from Max MAI | |
# based on site-class, then have | |
# a second entry based on | |
# earliest intervention age | |
cc_reg = [0]*4 | |
# Generate a 5 period window for each site-class; | |
# periods 18-14 for sc = 1 to 10-14 sc = 4, etc | |
for a,i in enumerate(cc_tuples): | |
# range gives the initial intervention ages | |
# [a+1] is the site-class | |
cc_reg[a] = zip(range(14-a, 19-a), [a+1]*5) | |
cc_nothing[a] = zip(range(14-a, 19-a) ,[a+1]*5) | |
all_cc = cc_tuples + cc_nothing | |
# Cut then no more interventions | |
for cc in cc_nothing: | |
temp_list = g_clearcuts(cc, count, nothing=1) | |
# add new prescriptions to prescription list | |
for t in temp_list: | |
pres_list.append(t) | |
count+=len(temp_list) | |
# Regular two entry Clearcut | |
for cc in cc_reg: | |
temp_list = g_clearcuts(cc, count) | |
for t in temp_list: | |
pres_list.append(t) | |
count+=len(temp_list) | |
# Note that more clearcut prescriptions can be | |
# added by following the above two examples | |
# Generate shelterwood (SHL) prescriptions | |
# For now, this is also done by site-class | |
# First tuple index gives age of first entry | |
# the second gives site class. | |
# For now, all initial entries are followed | |
# by a final felling 2 periods later | |
shl_tuples = [(14,1), (14,2), (14,3),(14,4)] | |
shl_tuples_2 = [(15,1), (15,2), (15,3),(15,4)] | |
shl_tuples_3 = [(16,1), (16,2), (16, 3), (16,4)] | |
# all tuples in one list, so generate all | |
# shelterwood pres in one fell swoop | |
temp_list = g_shelterwood(shl_tuples, count) | |
for t in temp_list: | |
pres_list.append(t) | |
count+=len(temp_list) | |
# SHL with second entry CC | |
# Note that these prescriptions, | |
# i.e. ones with non-clearcut entries | |
# as the initial entry have no need for | |
# an alternate track, since it would be | |
# equivalent to a stand following the | |
# alternate track on a clearcut prescription | |
temp_list = g_shelterwood(shl_tuples_2, count) | |
for t in temp_list: | |
pres_list.append(t) | |
count+=len(temp_list) | |
# SHL with second entry CT | |
temp_list = g_shelterwood(shl_tuples_3, count) | |
for t in temp_list: | |
pres_list.append(t) | |
count+=len(temp_list) | |
# Generate commercial thin prescriptions | |
# Dictionary mapping (state, initial intervention age, number of interventions) | |
# to number of interventions in the prescription. | |
# It's necessary to have number of interventions twice because | |
# the information is needed twice. | |
# ('11', 12, 3) : 3 means stands with state 11, | |
# natural regeneration state, can receive a three entry | |
# clearcut, starting at age 12 periods | |
ct_tuples = { ('11', 12,3) : 3, ('11', 12, 2):2, | |
('07', 10,3) : 3, ('07', 10,2) : 2, ('05', 15,2) : 2, | |
('05', 15,1) : 1, ('03', 19,1) : 1, ('01', 17,2) : 2, | |
('01', 17,1) : 1, ('02', 21,1) : 1, ('08', 8,3) : 3, | |
('08', 8,2) : 2, ('08', 8, 1) : 1, ('04', 17, 1) : 1, | |
('06', 13,2) : 2, } | |
ct_tuples_2 = { ('11', 12,3) : 3, ('11', 12, 2):2, | |
('07', 10,3) : 3, ('07', 10,2) : 2, ('05', 15,2) : 2, | |
('01', 17,2) : 2, | |
('08', 8,3) : 3, | |
('08', 8,2) : 2, | |
('06', 13,2) : 2, } | |
if model_type == 'A': | |
ct_tuples_2 = { | |
('08', 8,3) : 3, | |
('08', 8,2) : 2, | |
('06', 13,2) : 2, } | |
# Dicts are not ordered, so have a list of the tuple keys | |
# given by state and intervention age | |
in_age_state = sorted(ct_tuples.keys(), key = lambda x: (x[0],x[1])) | |
# Again, generate all commercial thin prescriptions in one fell swoop | |
# Two entry regeneration harvests here | |
temp_list = g_commercialthin(in_age_state, count, ct_tuples) | |
for t in temp_list: | |
pres_list.append(t) | |
count+=len(temp_list) | |
# Three entry regeration harvests here | |
temp_list = g_commercialthin(in_age_state,count, ct_tuples, regen=3) | |
for t in temp_list: | |
pres_list.append(t) | |
count+=len(temp_list) | |
in_age_state = sorted(ct_tuples_2.keys(), key = lambda x: (x[0],x[1])) | |
for a in range(4): | |
# clearcut regeneration harvests | |
temp_list = g_commercialthin(in_age_state,count, ct_tuples_2, | |
sc=a+1, cc=True, regen=4) | |
for t in temp_list: | |
pres_list.append(t) | |
count+=len(temp_list) | |
# Generate selection prescriptions | |
# Selection harvest prescriptions all occur at 16 periods | |
# of age. Make a list of all eligible forest states tupled | |
# with '16', the age of initial intervention | |
sel_tuples = zip(['07', '10', '11', '12', '13', '14'], [16]*6) | |
# Selection harvests are modelled the same way as thinnings, | |
# except the age is reset each entry. Use the commercial thin | |
# generator to generate selection prescriptions | |
temp_list = g_commercialthin(sel_tuples, count, sel=True) | |
for t in temp_list: | |
pres_list.append(t) | |
count+=len(temp_list) | |
# Generate buffer prescriptions | |
# one option: go in at 12 periods of age | |
in_age = [12] | |
# Set count to 200 to indicate that these are buffers | |
# Note that if more prescriptions are added, then it | |
# may be necessary to increase this to 300 or 500 | |
count = 200 | |
temp_list=g_buffer(in_age, count) | |
for t in temp_list: | |
pres_list.append(t) | |
# Return a list containing all the prescription objects | |
# generated above | |
return pres_list | |
# In the following functions g stands for "GENERATE" | |
def g_clearcuts(in_age_sc, start_number, nothing=""): | |
""" | |
Take a list of tuples mapping | |
initial_age of prescriptions to corresponding | |
site_classes. Start number lets us know what | |
prescription this is. | |
Return a list of Pres objects for clearcuts. | |
Each clearcut has an alt_age option that is | |
0-5 periods after modelling begins. Could be | |
interesting to have it as 0-10 and randomly selected. | |
Would need to pass a set or something to make sure | |
duplicates were not recorded | |
key word argument 'nothing' if true means | |
that after the first intervention, nothing is | |
done to the stand; otherwise, the stand follows | |
a normal prescription | |
""" | |
# Store pres objects here | |
pres_list = [] | |
# in_age_0 specifies the minimum age | |
# for this set of prescriptions. If | |
# earliest prescription first entry occurs | |
# at 12 periods of age, then no stand older | |
# than 12 periods is elligible for the main track | |
# of these prescriptions. They fall on the alternate | |
# track. This prevents duplicate prescriptions | |
# from being written. | |
in_age_0 = in_age_sc[0][0] | |
for a,in_age in enumerate(in_age_sc): | |
# each site-class has a 5 period window | |
# in which entries can take place | |
# this means a%5 will give 0-4 period | |
# leave times as the alt entry age | |
alt_age = a%5 | |
pres = pb.clearcut(in_age[0], alt_age, in_age[1], start_number+a, | |
in_age_0, nothing=nothing) | |
pres_list.append(pres) | |
return pres_list | |
def g_shelterwood(in_age_sc, start_number): | |
""" | |
Take a list of tuples mapping | |
initial_age of prescriptions to corresponding | |
site_classes. Start number indicates the | |
prescription number. | |
Return a list of Pres objects for shelterwoods. | |
14 period shelterwoods have an alt_age option that is | |
0-5 periods after modelling begins. | |
""" | |
pres_list = [] | |
# Only want alt_age prescriptions for 14 period | |
# Shelterwoods. | |
# If model_type is 'B' then an alternate | |
# track prescription is written using | |
# site-class to set the initial entry age; | |
# otherwise, no alternate track is defined. | |
for a, in_age in enumerate(in_age_sc): | |
if model_type == 'B': | |
alt_age = a | |
else: | |
alt_age = "" | |
pres = pb.shelterwood(in_age[0], in_age[1], start_number + a, | |
alt_age=alt_age) | |
pres_list.append(pres) | |
return pres_list | |
def g_commercialthin(in_age_state, start_number, entries="", | |
regen=2, sel="", cc="", sc=""): | |
""" | |
Take a list of tuples mapping state to initial intervention age. | |
start_number supplies the prescription numbers, and entries | |
indicates how many entries in the prescription. With a slight mod | |
this generation routine can apply to selection harvests, and the sel | |
named arg tells us whether to go that route. | |
key word args: | |
entries: for commercial thinning prescriptions indicates | |
the number of entries in the initial prescription. | |
regen: indicates the number of entries after regeneration | |
sel: whether this is a selection harvest prescription | |
cc: whether this is a clearcut harvest prescription | |
sc: for clearcut prescriptions it is necessary to know | |
the site-class | |
""" | |
pres_list = [] | |
for a, in_age in enumerate(in_age_state): | |
# 'x' indicates that alt_age | |
# will be figured out in the pb | |
# module | |
if model_type == 'B': | |
alt_age = 'x' | |
else: | |
alt_age = "" | |
# Commercial thin track | |
if not sel and not cc: | |
entry = entries[in_age] | |
pres = pb.commercialthin(in_age[1], start_number + a, | |
in_age[0], entry, alt_age, regen=regen) | |
# Selection track | |
elif not cc: | |
pres = pb.selection(in_age[1], in_age[0], start_number+a, alt_age) | |
# Clearcut track | |
else: | |
entry = entries[in_age] | |
pres = pb.commercialthin(in_age[1], start_number+a, in_age[0], | |
entries=entry, alt_age=alt_age, cc=True, | |
sc=sc, regen=regen) | |
pres_list.append(pres) | |
return pres_list | |
def g_buffer(in_age, start_number): | |
""" | |
Take a list of initial ages, and a number | |
indicating what to count up from for prescription | |
numbers. This generates prescription objects | |
for both do nothing and buffer prescriptions. | |
kwargs | |
in_age: first entry age of the prescription | |
start_number: prescription number | |
""" | |
# | |
pres_list = [] | |
for a,i in enumerate(in_age): | |
# Separate the buffer prescriptions | |
# from the do nothing prescriptions | |
if 0 == a and start_number >= 100: | |
alt_age = 'x' | |
else: | |
alt_age = "" | |
pres = pb.buffer_harv(i, start_number+a, alt_age) | |
pres_list.append(pres) | |
return pres_list | |
def list_pres(pres_list): | |
""" | |
List the name and number | |
and scheds of each prescription | |
""" | |
for p in pres_list: | |
try: | |
print p.number, p.name, p.i_sched, p.alt_i_sched | |
except AttributeError: | |
print p.number, p.name, p.i_sched | |
def getStands(): | |
""" | |
Return a list of lists containing sample entries | |
from the FBF1 table. First entry is stand number, | |
second entry is stand age, third entry is buffer | |
status, fourth entry is exclusion status, and | |
the fifth entry the stand_type. | |
""" | |
stands = [ ['4','5','0','0','111211'], | |
['5','7','0','0','111211'], | |
['6','7','0','0','111211'], | |
['7','7','0','0','111311'], | |
['8','1','0','0','111221'], | |
['9','1','0','0','111221'], | |
['10','9','0','0','111221'], | |
['11','14','0','0','111221'], | |
['12','6','0','0','111321'], | |
['13','16','0','0','111321'], | |
['14','17','0','0','111321'], | |
['15','15','0','1','111231'], | |
['16','13','0','0','111231'] | |
] | |
return stands | |
if '__main__' == __name__: | |
main() | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Module houses the Pres class for creating prescription | |
objects. It also contains a number of helper functions | |
that support prescription objects' assign_stand | |
method. I'm not sure if these should be written within | |
the class or not (something to look up) | |
""" | |
import random | |
class Pres(object): | |
""" | |
Pres objects have methods that when supplied a | |
stand, generate the data to write stand_types, stand_type_eco, | |
stand_age_period, and harv_type files. | |
A stand is eligible for a prescription if it does not match any | |
of the restrictions on site-class and state, and if it is younger | |
than the initial entry age. Some prescriptions will have alt_age | |
entries that are offset 0-5 (maybe later 0-10) periods from | |
beginning of the planning horizon. | |
A Pres has three master lists i_sched, t_sched, and r_sched | |
that detail when interventions happen, how a stand transitions | |
as a result of the intervention, and the age the stand assumes after | |
the intervention, respectively. For stands that have alt_age | |
alt_i_sched, alt_t_sched, and al_r_sched supply the same information | |
for a slightly different management track. | |
A Pres also has a harv_type {01, 11, 12, 15, 16} indicating whether | |
it is a thin, clearcut, selection, shelterwood, or buffer, respectively. | |
It will also have a name, and a number. | |
The primary method of a Pres object is assign_stand, which generates lists | |
that the above mentioned files can be written from. | |
""" | |
def __init__(self, restrictions, name, number, | |
sched, in_age, harv_type): | |
self.name = name | |
self.number = number | |
self.i_sched = sched[0] | |
self.t_sched = sched[1] | |
self.r_sched = sched[2] | |
self.in_age = in_age | |
self.harv_type = harv_type | |
self.restrictions = restrictions | |
# Not all prescriptions have alternate age tracks; | |
# they have to be added after the object is instantiated | |
def add_alt(self, sched, harv_type): | |
self.alt_i_sched = sched[0] | |
self.alt_t_sched = sched[1] | |
self.alt_r_sched = sched[2] | |
self.alt_harv_type = harv_type | |
# Filter out exclusion and buffer beforehand | |
# i.e. assign_stand performs no eligibility | |
# check, except for age checking | |
def assign_stand(self, stand): | |
""" | |
Take in a stand. | |
Depending on age of the stand either write | |
primary or alternative prescription. | |
Return a list of lists. | |
Stand_Type | |
Stand_Type_Eco | |
Stand_Age - actually a set | |
Harv_Type - actually a set too | |
The main() method from main_pres (mp) | |
will take these lists and write them to | |
their respective files | |
""" | |
# sa - stand_age, st - stand_type, se - stand_type_eco | |
# ht - harv_type | |
sa_list = set() # misnomer, actually a set | |
st_list = [] | |
se_list = [] | |
ht_list = set() # misnomer, actually a set | |
i_sched, t_sched, r_sched = [], [], [] | |
# Get essential information about the stand | |
# being assigned the prescription | |
number,age,stype = stand[0],int(stand[1]),stand[4] | |
name = self.name | |
# Keep all lists together for easy passing | |
list_of_lists = [st_list, se_list, sa_list, ht_list] | |
# Local versions of the master versions of the lists | |
# held by the prescription object | |
sched_list = [i_sched, r_sched, t_sched] | |
# This is what happens in the following section: | |
# the Pres object's lists are pasted into local | |
# copies of the lists which are then | |
# passed to the assignment function, which | |
# runs the current stand through the prescription | |
# Can only take alt_age track if the prescription | |
# has such a thing | |
if age > self.in_age and hasattr(self, 'alt_i_sched'): | |
# Set all the schedules to be alternate | |
# Populate them from the master copies | |
sched_list[0] = [i for i in self.alt_i_sched] | |
sched_list[1] = [t for t in self.alt_t_sched] | |
sched_list[2] = [r for r in self.alt_r_sched] | |
# For entries that occur at unknown age, they will be | |
# entered in the lists as strings; | |
# when going through the lists, if an entry | |
# is a string that means add the age of the stand | |
# to the integer value of the string to get the | |
# age the entry occurs at. | |
# same practice will apply to the regeneration list. | |
resolve_alt(sched_list, age, name) | |
# don't have to return anything cause the list | |
# is being passed and changes will be made to it | |
alt_harv = [h for h in self.alt_harv_type] | |
assignment(sched_list, list_of_lists, stand, self.number, alt_harv) | |
else: | |
# Populate lists from master copies | |
sched_list[0] = [i for i in self.i_sched] | |
sched_list[1] = [t for t in self.t_sched] | |
sched_list[2] = [r for r in self.r_sched] | |
harv = [h for h in self.harv_type] | |
# Don't return anything, list will be updated in function | |
assignment(sched_list, list_of_lists, stand, self.number, harv) | |
return list_of_lists | |
def resolve_alt(sched, age, name): | |
""" | |
Every case except shelterwood deals with | |
alt prescriptions in a straight forward way: | |
knock the stand down in the next 5 periods and | |
then start managing it. | |
Shelterwoods are a little different, because | |
we want to finish them off shelterwoods the first | |
time round. This function adjusts the regen list | |
and intervention list to suit all intervention methods | |
""" | |
# Add stand age to the period offset for the prescription | |
# for everything except shelterwoods, only the first | |
# entry in alt_i_sched will need to be updated | |
# multipliers apply at 16 under shelterwood prescriptions | |
# therefore no clearcuts can occur at 16 under shelterwood pres | |
if name == 'shelterwood': | |
if int(sched[0][0]) + age > 16: | |
sched[0][0] = int(sched[0][0]) + age | |
else: | |
sched[0][0] = 17 | |
# for shelterwoods, the second entry in alt_i_sched | |
# and the first entry in alt_r_sched need to be updated | |
sched[0][1] = sched[0][0] + 2 | |
sched[2][0] = sched[0][0] + 1 | |
else: | |
sched[0][0] = int(sched[0][0]) + age | |
# Modifying a list so no need to return anything | |
def intervention(stand_type, trans='11', stock='3'): | |
""" | |
change the type of a stand as the result of an | |
intervention taking place. | |
kwargs: | |
regen - defaults to '11' (NRG) the state the stand | |
takes as a result of the intervention | |
stock - defaults to '3'. The stocking of the | |
stand as a result of the intervention. | |
""" | |
new_type = stand_type[:-5] + trans + stand_type[-3] + stock + stand_type[-1] | |
return new_type | |
def assignment(sched, list_of_lists, stand, pres_number, harv_type): | |
""" | |
Update a list of lists with the results of | |
applying a prescription to the provided stand. | |
Age | |
Stand Type | |
Stand Type Eco | |
Harv Type | |
Are updated as the stand ages | |
and passes through transitions | |
""" | |
# Unpack info from stand list | |
number,age,stype = stand[0],int(stand[1]),stand[4] | |
# House period by period entries for the age, stand type, | |
# stand_type eco and harv type lists | |
age_tuple, st_tuple, se_tuple, ht_tuple = (0,),(0,),(0,),(0,) | |
list_of_tuples = [st_tuple, se_tuple, age_tuple, ht_tuple] | |
# Initialize period, current age, stocking | |
period = 0 | |
cur_age = age | |
stock = stype[-2:-1] | |
# Plantation stand_types that cannot be naturally regenerated | |
# without changing the species | |
dead_plt = ['1111335','1111435','1311435','1311235'] | |
# intervention, transition and regeneration schedules, respectively | |
i_sched, t_sched, r_sched = sched[0], sched[1], sched[2] | |
# 31 is the number of periods. | |
for i in range(31): | |
for a,tup in enumerate(list_of_tuples): | |
list_of_tuples[a] = (0,) | |
list_of_tuples[2] = (age, pres_number, period, cur_age) | |
# If sched has entries left, and the current age of | |
# the stand corresponds to an entry age, and the period | |
# is less than 30, then simulate the intervention. | |
if sched[0] and cur_age == sched[0][0] and period < 30: | |
# After applying an intervention, remove | |
# it from the list | |
pop_hold = sched[0].pop(0) | |
# Same thing with harv_types | |
# there was a problem with | |
# the harv_type list getting | |
# exhausted prematurely, hence the | |
# try-except clause. | |
try: | |
harv = harv_type.pop(0) | |
except IndexError: | |
print pres_number, period, age | |
# Get the type resulting from the intervention | |
try: | |
# check if regeneration comes | |
# as a result of a final felling. In this | |
# case sched[2][0] will equal 1 or 2. | |
# These are the regeneration ages. | |
if sched[2][0] in (1, 2): | |
stock = '3' | |
new_type = intervention(stype, trans=sched[1].pop(0), | |
stock=stock) | |
except IndexError: | |
print sched, pres_number | |
# Record the intervention cutting the pre-intervention type | |
list_of_tuples[0] = (number, pres_number, period, stype) | |
# If type changes due to intervention write a line | |
# in stand_types_eco file | |
if stype != new_type: | |
# This type comes up once and there is no yield for it; | |
# brush it under the rug | |
if new_type in dead_plt: | |
new_type = intervention(new_type, trans='08') | |
list_of_tuples[1] = (number, pres_number, period + 1, new_type) | |
#record the intervention type | |
list_of_tuples[3]= (age, pres_number, period, harv) | |
# how the stand's age changes as a result of the prescription | |
cur_age = sched[2].pop(0) | |
# update the stand's type | |
stype = new_type | |
# no intervention takes place, stand ages as usual | |
elif cur_age < 40 and period < 30: | |
cur_age += 1 | |
# Stand is 40 and it is time to die and regenerate | |
elif cur_age == 40 and period < 30: | |
cur_age = 0 | |
# Default death intervention with regen = '11' | |
new_type = intervention(stype) | |
if new_type in dead_plt: | |
new_type = intervention(new_type, trans='08') | |
# If type changes as a result of regenerating than | |
# write a line a in the stand eco file | |
if stype != new_type: | |
list_of_tuples[1]=(number, pres_number, period+1, new_type) | |
stype = new_type | |
# Each time we go through this cycle record any | |
# harvests, or aging in their respective lists | |
for a,tup in enumerate(list_of_tuples): | |
if tup != (0,) and tup not in list_of_lists[a]: | |
# stand_types and stand_types_eco will occupy | |
# the first two indices and they are lists | |
if a < 2: | |
list_of_lists[a].append(tup) | |
# stand_age and harv_types are will occupy the | |
# third and fourth indices and they are sets | |
else: | |
list_of_lists[a].add(tup) | |
period += 1 | |
def clearcut(in_age, alt_age, sc, code, in_age_0, nothing=""): | |
""" | |
Given an initial age, a site class, | |
an offset from initial period for stands older | |
than the initial age, and the number of the prescription, | |
generate and return a prescription | |
object for a clearcut. | |
Alt_age tracks are always generated. | |
Key word args | |
nothing: indicates that after the first intervention | |
nothing is done to the stand | |
""" | |
# Only applicable to stands of a certain site-class | |
site_classes = [1,2,3,4] | |
# Check restrictions by having a dictionary that | |
# you check each stands attributes with using .get(key, 0) | |
restrictions = { 'sc' : [s for s in site_classes if s!=sc], | |
'state' : ['01', '02', '03', '04', | |
'05', '06', '07', '08', | |
'12','13','14'] } | |
harv_type = '11' | |
name = 'clearcut' | |
number = code | |
# primary and alternate tracks respectively | |
sched = [0]*3 | |
alt_sched = [0]*3 | |
# Regen harvests at earliest eligible age | |
if 2 == nothing: | |
regen = { 1 : 16, 2 : 15, 3 : 14, 4 : 13 } | |
elif 1 == nothing: | |
# planning horizon is 30 periods, so | |
# second entry at 31 periods ensures | |
# no second entry will take place | |
regen = { 1 : 31, 2 : 31, 3 : 31, 4 : 31 } | |
else: | |
# Regens are based on Age for max Mai on give site | |
regen = { 1 : 18, 2 : 17, 3 : 16, 4 : 15 } | |
# Simple prescription: Cut, cut, cut | |
# intervention ages | |
sched[0] = [in_age, regen[sc], regen[sc]] | |
# state changes as a result of intervention | |
sched[1] = ['11', '11', '11'] | |
# age as a result of intervention | |
sched[2] = [1, 1, 1] | |
# harvest type of each intervention | |
harv_type = ['11', '11', '11'] | |
# instantiate new prescription object | |
new_pres = Pres(restrictions, name, number, sched, in_age_0, harv_type) | |
# Clearcuts always have an alternate track | |
alt_sched[0] = [str(alt_age),regen[sc], regen[sc]] | |
alt_sched[1] = ['11', '11', '11'] | |
alt_sched[2] = [1,1,1] | |
harv_type = ['11', '11', '11'] | |
# Add alternate prescription for old stands to object | |
new_pres.add_alt(alt_sched, harv_type) | |
return new_pres | |
def shelterwood(in_age, sc, code, alt_age=""): | |
""" | |
Given an initial age, and a prescription number | |
generate a shelterwood prescription object. | |
Named args: | |
alt_age - true if the prescription has an | |
alternate track | |
""" | |
site_classes = [1,2,3,4] | |
restrictions = { 'sc' : [s for s in site_classes if s!=sc], | |
'state' : ['08', '06', '04', '01', | |
'02', '05', '07','12', | |
'13', '03', '14'] } | |
harv_type = '15' | |
name = 'shelterwood' | |
number = code | |
# Primary and alternate tracks, respectively | |
sched = [0]*3 | |
alt_sched = [0]*3 | |
# Regeneration ages if shelterwood is regeneration | |
# harvest, or if clearcuts are regeneration harvests. | |
regen = { 1 : 14, 2 : 14, 3 : 14, 4 : 14 } | |
regen_cc = { 1 : 18, 2 : 17, 3 : 16, 4 : 15 } | |
if in_age == 14: | |
sched[0] = [in_age, in_age+2, regen[sc], regen[sc] + 2, regen[sc], | |
regen[sc] + 2] | |
sched[1] = ['11', '11', '11', '11', '11', '11'] | |
sched[2] = [in_age+1, 2, regen[sc]+1, 2] | |
harv_type = ['15', '15', '15', '15', '15', '15'] | |
# indicates after a shelterwood system the stand | |
# moves onto a clearcut track | |
elif in_age == 15: | |
alt_age = "" | |
in_age = 14 | |
# simple too: cut, grow two years, fell | |
sched[0] = [in_age, in_age+2, regen_cc[sc], regen_cc[sc]] | |
sched[1] = ['11', '11', '11', '11'] | |
sched[2] = [in_age+1, 2, 1, 1] | |
harv_type = ['15', '15', '11', '11', '11'] | |
# indicates after a shelterwood system the stand | |
# moves onto a commercial thinning track | |
elif in_age == 16: | |
alt_age = "" | |
in_age = 14 | |
sched[0] = [in_age, in_age+2, 3, 10, 15, 3, 10, 15] | |
sched[1] = ['11', '11', '01', '01', '11', '01', '01', '11'] | |
sched[2] = [in_age+1, 2, 4, 11, 1, 4, 11, 1] | |
harv_type = ['15', '15', '01', '01', '11', '01', '01', '11'] | |
new_pres = Pres(restrictions, name, number, sched, in_age, harv_type) | |
# If an alternate track is to be defined | |
if alt_age != "": | |
in_age = alt_age | |
# First entry is a clearcut, then move onto shelterwood | |
# management track. | |
alt_sched[0] = [str(in_age), regen[sc], regen[sc]+2, regen[sc]] | |
alt_sched[1] = [ '11', '11', '11', '11'] | |
alt_sched[2] = [ 1, regen[sc]+1, 2, regen[sc]+1, 2] | |
harv_type = ['11', '15', '15', '15', '15'] | |
new_pres.add_alt(alt_sched, harv_type) | |
return new_pres | |
def commercialthin(in_age, code, state, entries, alt_age ="", cc="", sc="", | |
regen=2): | |
""" | |
Given initial age, a prescription number | |
and optional pct and alt_age arguments. | |
Generate a precommercial thin prescription. | |
Unlike others, this will have a state it is aimed at. | |
Key word args | |
alt_age: true if this prescription has an alternate track | |
cc: true if first set of entries are clearcuts before | |
stand moves onto a commerical thinning track. | |
sc: indicates the site-class for use if the first entry | |
is a clearcut | |
regen: indicates if after first regeneration the stand | |
receives a two or three entry commercial thinning system | |
""" | |
# Primary and alternate tracks, respectively | |
sched = [0]*3 | |
alt_sched = [0]*3 | |
name = 'commercial thin' | |
number = code | |
harv_type = '01' | |
site_classes = [1,2,3,4] | |
# The keys are initial states and the values are the state | |
# that the initiaal state key will transition into as | |
# a result of a thinning. | |
states = { '11' : '01', '01' : '02', '07' : '05', '05' : '03', | |
'08' : '06', '06' : '04', '10' : '10', '02' : '11', | |
'03' : '11', '04' : '08'} | |
# The keys are initial states and the values are the state | |
# that particular initial state will regenerate as after | |
# receiving a final felling. | |
initial_states = { '11' : '11', '01' : '11', '02' : '11', '07' : '11', | |
'05' : '11', '03' : '11', '08' : '08', '06' : '08', | |
'04' : '08' } | |
regen_state = initial_states[state] | |
regen_cc = { 1 : 18, 2 : 17, 3 : 16, 4 : 15 } | |
# Only the specified state can take this prescription | |
restrictions = { 'state' : [s for s in states.keys() if s !=state] } | |
# If clearcuts in this prescription must take into accoutn | |
# site-class. | |
if cc: | |
restrictions = { 'sc' : [s for s in site_classes if s!=sc], | |
'state' : [s for s in states.keys() | |
if s !=state] | |
} | |
# Plantation stands are treated differently from naturals | |
if state in ('08', '06', '04'): | |
if regen == 2: | |
sched[0] = [in_age, in_age+5, in_age+9, 8, 13, | |
8, 13] | |
sched[1] = [states[state], states[states[state]], '08', '06' | |
,'08', '06', '08'] | |
sched[2] = [in_age+1, in_age+6, 1, 9, 1, 9, 1] | |
harv_type = ['01', '01', '11', '01', '11', '01', '11'] | |
elif regen == 3: | |
sched[0] = [in_age, in_age+5, in_age+9, 8, 13, 17, | |
8, 13] | |
sched[1] = [states[state], states[states[state]], '08', '06' | |
,'04', '08', '06','04', '08'] | |
sched[2] = [in_age+1, in_age+6, 1, 9, 14, 1, 9, 14, 1] | |
harv_type = ['01', '01', '11', '01', '01', '11', '01', '01', '11'] | |
elif regen == 4: | |
sched[0] = [in_age, in_age+5, in_age+9, regen_cc[sc], regen_cc[sc]] | |
sched[1] = [states[state], states[states[state]], '08', '08', '08'] | |
sched[2] = [in_age+1, in_age+6, 1, 1, 1] | |
harv_type = ['01', '01', '11', '11', '11'] | |
# If less than three entries are requested, | |
# remove index 2 then index 1 from each sched[0] and sched[1] | |
# remove index 1 then index 0 from sched[2] | |
for i in range(3 - entries): | |
sched[0].remove(sched[0][2-i]) | |
sched[1].remove(sched[1][1-i]) | |
sched[2].remove(sched[2][1-i]) | |
harv_type.remove(harv_type[1-i]) | |
# This where no plantation stands get shunted | |
else: | |
if regen == 2: | |
sched[0] = [in_age, in_age+5, in_age+9, 10, 15, 10, | |
15] | |
sched[1] = [states[state], states[states[state]],'07', | |
'05', '07', '05', '07' ] | |
sched[2] = [in_age+1, in_age+6, 1, 11, 1, 11, 1] | |
harv_type = ['01', '01', '11', '01', '11', '01', '11'] | |
elif regen == 3: | |
sched[0] = [in_age, in_age+5, in_age+9, 10, 15, 19, 10, | |
15] | |
sched[1] = [states[state], states[states[state]],'07', | |
'05', '03', '07', '05', '03', '07' ] | |
sched[2] = [in_age+1, in_age+6, 1, 11, 16, 1, 11, 16, 1] | |
harv_type = ['01', '01', '11', '01', '01', '11', '01', '01' '11'] | |
elif regen == 4: | |
sched[0] = [in_age, in_age+5, in_age+9, regen_cc[sc], regen_cc[sc]] | |
sched[1] = [states[state], states[states[state]], '11', '11', '11'] | |
sched[2] = [in_age+1, in_age+6, 1, 1, 1] | |
harv_type = ['01', '01', '11', '11', '11'] | |
if entries < 3: | |
for i in range(3 - entries): | |
sched[0].remove(sched[0][2-i]) | |
sched[1].remove(sched[1][1-i]) | |
sched[2].remove(sched[2][1-i]) | |
harv_type.remove(harv_type[1-i]) | |
if entries == 2 and int(state) <= 7: | |
sched[0][1] = in_age+4 | |
new_pres = Pres(restrictions, name, number, sched, in_age, harv_type) | |
if regen == 4: | |
alt_age = "" | |
# Alternate track | |
if alt_age != "": | |
# waiting time for the initial alternate entry is determined | |
# by the number of the prescription modulo 5 | |
wait = code%5 | |
# Again, plantation and natural are separated | |
if state in ('08', '06', '04'): | |
if regen == 2: | |
alt_sched[0] = [wait, 8, 13, 8, 13, 8, 13] | |
alt_sched[1] = ['08', '06', '08', '06', '08', '06', '08'] | |
alt_sched[2] = [1, 9, 1, 9, 1, 9, 1, 9, 1] | |
harv_types = ['11', '01', '11', '01', '11', '01', '11', '01'] | |
elif regen == 3: | |
alt_sched[0] = [wait, 8, 13, 17, 8, 13, 17, 8, 13] | |
alt_sched[1] = ['08', '06', '04','08', '06', '04', '08', | |
'06', '08'] | |
alt_sched[2] = [1, 9, 14, 1, 9, 14, 1, 9, 14, 1, 9, 1] | |
harv_types = ['11', '01', '01', '11', '01', '01', '11', '01'] | |
else: | |
if regen == 2: | |
alt_sched[0] = [wait, 3, 10, 15, 3, 10, 15, 3] | |
alt_sched[1] = ['11', '07', '05', '11','07', '05', '11', '07'] | |
alt_sched[2] = [1, 4,11,1,4,11,1,4] | |
harv_types = ['11', '01', '01', '11', '01', '01', | |
'11', '01', '01', '11'] | |
elif regen == 3: | |
alt_sched[0] = [wait, 3,10, 15, 19, 3, 10, 15] | |
alt_sched[1] = ['11','07','05', '03', '11', '07', '05', '03', | |
'11','07' ] | |
alt_sched[2] = [1, 4, 11, 16, 1, 4, 11, 16, 1] | |
harv_types = ['11', '01', '01', '01', '11', '01', '01', | |
'01', '11'] | |
new_pres.add_alt(alt_sched, harv_types) | |
new_pres.state = state | |
return new_pres | |
def selection(in_age, state, code, alt_age = ""): | |
""" | |
Return a selection harvest prescription based | |
on initial age, state of forest it applies to | |
and prescription number. | |
An alternative age option can be specified as | |
well. | |
Selection harvests, after the initial intervention | |
occur every 4 years, and are modelled by setting the | |
stands age back to 12 after harvest of desirable species. | |
Key word args | |
alt_age: indicates whether this prescription has | |
an alternate track | |
""" | |
# Boilerplate prescription object details | |
name = 'selection' | |
number = code | |
harv_type = '12' | |
sched = [0]*3 | |
alt_sched = [0]*3 | |
# Mapping of how selection harvests change the state of the stand | |
states = {'11': '12', '12': '12', '07' : '13', '13' : '13', | |
'14' : '14', '10' : '14' } | |
# Dictionary of stands that are not eligible | |
never = ['01', '02', '03', '04', '05', '06', '08'] | |
states_list = [s for s in states.keys() if s!= state] | |
for s in states_list: | |
never.append(s) | |
restrictions = {'state' : never } | |
# Intervention, transition, and regeneration schedules respectively | |
sched[0] = [in_age, 16, 16, 16, 16, 16, 16, 16, 16, 16] | |
sched[1] = [states[state] for i in range(10)] | |
sched[2] = [13 for i in range(10)] | |
harv_type = ['12', '12', '12', '12', '12', '12', '12', '12', '12', | |
'12'] | |
# Instantiate prescription object and give it a new attribute | |
# that holds the state of the forest it applies to | |
new_pres = Pres(restrictions, name, number, sched, in_age, harv_type) | |
new_pres.state = state | |
# Define potential alt_age prescription | |
if alt_age != "": | |
wait = code%5 | |
alt_sched[0] = [wait, 3, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16] | |
alt_sched[1] = ['11', '07', '13', '13', '13', '13', '13', '13', '13'] | |
alt_sched[2] = [1, 4, 13,13,13,13,13,13,13,13,13,13,13] | |
harv_type = ['11', '01', '12', '12', '12', '12', '12', '12', '12', | |
'12', '12'] | |
new_pres.add_alt(alt_sched, harv_type) | |
return new_pres | |
def buffer_harv(in_age, code, alt_age=""): | |
""" | |
Define buffer prescription objects. | |
Just like all the other ones. | |
""" | |
name = 'buffer' | |
number = code | |
harv_type = '16' | |
sched = [0]*3 | |
alt_sched = [0]*3 | |
# Buffer restrictions are only that it must be a buffer stand, | |
# and not in an excl. These are checked prior to the stand getting | |
# access to the prescription. This means all states are eligible. | |
restrictions = {'state' : ['08', '06', '04', '01', '02', '03','05', '07', | |
'12', '13', '14']} | |
if in_age > 12: | |
sched[0] = [in_age, 12, 24] | |
sched[1] = [ '11', '11', '11'] | |
sched[2] = [1, 13, 1] | |
harv_type = ['16', '16', '16'] | |
else: | |
sched[0] = [in_age, 24, 12, 24] | |
sched[1] = ['11', '11', '11', '11'] | |
sched[2] = [13, 1, 13, 1] | |
harv_type = ['16', '16', '16', '16'] | |
new_pres = Pres(restrictions, name, number, sched, in_age, harv_type) | |
alt_age = "" | |
if alt_age != "": | |
wait = random.randint(0,5) | |
alt_sched[0] = [wait, 12, 24] | |
alt_sched[1] = ['11', '11', '11'] | |
alt_sched[2] = [0, 13, 0] | |
new_pres.add_alt(alt_sched) | |
return new_pres | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment