Skip to content

Instantly share code, notes, and snippets.

@AndrewBMartin
Last active December 29, 2015 23:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AndrewBMartin/7743801 to your computer and use it in GitHub Desktop.
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.
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()
"""
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