fvs-python 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fvs_python-0.2.3.dist-info/METADATA +254 -0
- fvs_python-0.2.3.dist-info/RECORD +149 -0
- fvs_python-0.2.3.dist-info/WHEEL +5 -0
- fvs_python-0.2.3.dist-info/licenses/LICENSE +21 -0
- fvs_python-0.2.3.dist-info/top_level.txt +1 -0
- pyfvs/__init__.py +107 -0
- pyfvs/bark_ratio.py +323 -0
- pyfvs/cfg/CFG_README.md +73 -0
- pyfvs/cfg/dbh_bounding_table_4_7_1_8.json +103 -0
- pyfvs/cfg/ecounit_coefficients_table_4_7_1_5.json +981 -0
- pyfvs/cfg/ecounit_coefficients_table_4_7_1_6.json +856 -0
- pyfvs/cfg/forest_type_mapping_table_4_7_1_4.json +38 -0
- pyfvs/cfg/fortype_coefficients_table_4_7_1_3.json +1183 -0
- pyfvs/cfg/functional_forms.yaml +111 -0
- pyfvs/cfg/growth_model_parameters.yaml +98 -0
- pyfvs/cfg/plant_values_table_4_7_1_7.json +15 -0
- pyfvs/cfg/site_index_transformation.yaml +113 -0
- pyfvs/cfg/sn_bark_ratio_coefficients.json +115 -0
- pyfvs/cfg/sn_crown_competition_factor.json +109 -0
- pyfvs/cfg/sn_crown_ratio_coefficients.json +956 -0
- pyfvs/cfg/sn_crown_width_coefficients.json +1664 -0
- pyfvs/cfg/sn_diameter_growth_coefficients.json +300 -0
- pyfvs/cfg/sn_height_diameter_coefficients.json +97 -0
- pyfvs/cfg/sn_large_tree_diameter_growth.json +191 -0
- pyfvs/cfg/sn_large_tree_height_growth.json +229 -0
- pyfvs/cfg/sn_large_tree_height_growth_coefficients.json +1187 -0
- pyfvs/cfg/sn_mortality_model.json +176 -0
- pyfvs/cfg/sn_regeneration_model.json +252 -0
- pyfvs/cfg/sn_relative_site_index.json +263 -0
- pyfvs/cfg/sn_small_tree_height_growth.json +879 -0
- pyfvs/cfg/sn_species_codes_table.json +728 -0
- pyfvs/cfg/sn_stand_density_index.json +398 -0
- pyfvs/cfg/species/ab_american_basswood.yaml +251 -0
- pyfvs/cfg/species/ae_american_elm.yaml +240 -0
- pyfvs/cfg/species/ah_american_hornbeam.yaml +250 -0
- pyfvs/cfg/species/ap_american_plum.yaml +251 -0
- pyfvs/cfg/species/as_american_sycamore.yaml +253 -0
- pyfvs/cfg/species/ba_black_ash.yaml +254 -0
- pyfvs/cfg/species/bb_basswood.yaml +254 -0
- pyfvs/cfg/species/bc_black_cherry.yaml +254 -0
- pyfvs/cfg/species/bd_sweet_birch.yaml +252 -0
- pyfvs/cfg/species/be_american_beech.yaml +251 -0
- pyfvs/cfg/species/bg_black_gum.yaml +252 -0
- pyfvs/cfg/species/bj_blue_jay.yaml +254 -0
- pyfvs/cfg/species/bk_sugar_maple.yaml +251 -0
- pyfvs/cfg/species/bn_butternut.yaml +252 -0
- pyfvs/cfg/species/bo_red_maple.yaml +255 -0
- pyfvs/cfg/species/bt_bigtooth_aspen.yaml +254 -0
- pyfvs/cfg/species/bu_buckeye.yaml +252 -0
- pyfvs/cfg/species/by_bald_cypress.yaml +255 -0
- pyfvs/cfg/species/ca_american_chestnut.yaml +254 -0
- pyfvs/cfg/species/cb_cucumber_tree.yaml +254 -0
- pyfvs/cfg/species/ck_virginia_pine.yaml +254 -0
- pyfvs/cfg/species/co_pond_cypress.yaml +251 -0
- pyfvs/cfg/species/ct_catalpa.yaml +251 -0
- pyfvs/cfg/species/cw_chestnut_oak.yaml +253 -0
- pyfvs/cfg/species/dw_dogwood.yaml +250 -0
- pyfvs/cfg/species/el_american_hornbeam.yaml +254 -0
- pyfvs/cfg/species/fm_flowering_dogwood.yaml +251 -0
- pyfvs/cfg/species/fr_fraser_fir.yaml +247 -0
- pyfvs/cfg/species/ga_green_ash.yaml +254 -0
- pyfvs/cfg/species/ha_hawthorn.yaml +252 -0
- pyfvs/cfg/species/hb_hornbeam.yaml +254 -0
- pyfvs/cfg/species/hh_dogwood.yaml +251 -0
- pyfvs/cfg/species/hi_hickory_species.yaml +252 -0
- pyfvs/cfg/species/hl_holly.yaml +254 -0
- pyfvs/cfg/species/hm_eastern_hemlock.yaml +246 -0
- pyfvs/cfg/species/hy_holly.yaml +252 -0
- pyfvs/cfg/species/ju_eastern_juniper.yaml +247 -0
- pyfvs/cfg/species/lb_loblolly_bay.yaml +254 -0
- pyfvs/cfg/species/lk_laurel_oak.yaml +254 -0
- pyfvs/cfg/species/ll_longleaf_pine.yaml +265 -0
- pyfvs/cfg/species/lo_silver_maple.yaml +252 -0
- pyfvs/cfg/species/lp_loblolly_pine.yaml +268 -0
- pyfvs/cfg/species/mb_mountain_birch.yaml +250 -0
- pyfvs/cfg/species/mg_magnolia.yaml +251 -0
- pyfvs/cfg/species/ml_maple_leaf.yaml +254 -0
- pyfvs/cfg/species/ms_maple_species.yaml +247 -0
- pyfvs/cfg/species/mv_magnolia_vine.yaml +254 -0
- pyfvs/cfg/species/oh_other_hardwood.yaml +231 -0
- pyfvs/cfg/species/os_other_softwood.yaml +232 -0
- pyfvs/cfg/species/ot_other_tree.yaml +210 -0
- pyfvs/cfg/species/ov_overcup_oak.yaml +254 -0
- pyfvs/cfg/species/pc_pond_cypress.yaml +254 -0
- pyfvs/cfg/species/pd_pitch_pine.yaml +245 -0
- pyfvs/cfg/species/pi_pine_species.yaml +246 -0
- pyfvs/cfg/species/po_american_beech.yaml +254 -0
- pyfvs/cfg/species/pp_pond_pine.yaml +246 -0
- pyfvs/cfg/species/ps_persimmon.yaml +251 -0
- pyfvs/cfg/species/pu_pond_pine.yaml +249 -0
- pyfvs/cfg/species/qs_flowering_dogwood.yaml +254 -0
- pyfvs/cfg/species/ra_red_ash.yaml +245 -0
- pyfvs/cfg/species/rd_redbud.yaml +251 -0
- pyfvs/cfg/species/rl_red_elm.yaml +240 -0
- pyfvs/cfg/species/rm_red_maple.yaml +256 -0
- pyfvs/cfg/species/ro_eastern_hemlock.yaml +255 -0
- pyfvs/cfg/species/sa_slash_pine.yaml +265 -0
- pyfvs/cfg/species/sb_sweet_birch.yaml +255 -0
- pyfvs/cfg/species/sd_sand_pine.yaml +251 -0
- pyfvs/cfg/species/sk_swamp_oak.yaml +253 -0
- pyfvs/cfg/species/sm_sugar_maple.yaml +252 -0
- pyfvs/cfg/species/sn_loblolly_pine.yaml +254 -0
- pyfvs/cfg/species/so_southern_oak.yaml +253 -0
- pyfvs/cfg/species/sp_shortleaf_pine.yaml +267 -0
- pyfvs/cfg/species/sr_spruce_pine.yaml +246 -0
- pyfvs/cfg/species/ss_basswood.yaml +251 -0
- pyfvs/cfg/species/su_sweetgum.yaml +255 -0
- pyfvs/cfg/species/sv_silver_maple.yaml +255 -0
- pyfvs/cfg/species/sy_sycamore.yaml +254 -0
- pyfvs/cfg/species/tm_tamarack.yaml +246 -0
- pyfvs/cfg/species/to_tulip_oak.yaml +254 -0
- pyfvs/cfg/species/ts_tulip_tree.yaml +253 -0
- pyfvs/cfg/species/vp_virginia_pine.yaml +248 -0
- pyfvs/cfg/species/wa_white_ash.yaml +254 -0
- pyfvs/cfg/species/we_white_elm.yaml +250 -0
- pyfvs/cfg/species/wi_willow.yaml +248 -0
- pyfvs/cfg/species/wk_water_oak.yaml +254 -0
- pyfvs/cfg/species/wn_walnut.yaml +254 -0
- pyfvs/cfg/species/wo_white_oak.yaml +256 -0
- pyfvs/cfg/species/wp_white_pine.yaml +250 -0
- pyfvs/cfg/species/wt_water_tupelo.yaml +254 -0
- pyfvs/cfg/species/yp_yellow_poplar.yaml +261 -0
- pyfvs/cfg/species_config.yaml +106 -0
- pyfvs/clark_profile.py +323 -0
- pyfvs/competition.py +332 -0
- pyfvs/config_loader.py +375 -0
- pyfvs/crown_competition_factor.py +464 -0
- pyfvs/crown_ratio.py +377 -0
- pyfvs/crown_width.py +512 -0
- pyfvs/data_export.py +356 -0
- pyfvs/ecological_unit.py +272 -0
- pyfvs/exceptions.py +86 -0
- pyfvs/fia_integration.py +876 -0
- pyfvs/forest_type.py +253 -0
- pyfvs/growth_plots.py +579 -0
- pyfvs/harvest.py +603 -0
- pyfvs/height_diameter.py +248 -0
- pyfvs/large_tree_height_growth.py +822 -0
- pyfvs/logging_config.py +213 -0
- pyfvs/main.py +99 -0
- pyfvs/mortality.py +431 -0
- pyfvs/parameters.py +121 -0
- pyfvs/simulation_engine.py +386 -0
- pyfvs/stand.py +1004 -0
- pyfvs/stand_metrics.py +436 -0
- pyfvs/stand_output.py +552 -0
- pyfvs/tree.py +756 -0
- pyfvs/validation.py +190 -0
- pyfvs/volume_library.py +761 -0
pyfvs/crown_ratio.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Crown ratio relationship functions for FVS-Python.
|
|
3
|
+
Implements Weibull-based crown model and other crown ratio equations from the SN variant.
|
|
4
|
+
"""
|
|
5
|
+
import math
|
|
6
|
+
import random
|
|
7
|
+
from typing import Dict, Any, Optional, Tuple
|
|
8
|
+
from .config_loader import load_coefficient_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_crown_ratio_data() -> Dict[str, Any]:
|
|
12
|
+
"""Get crown ratio data using ConfigLoader (with caching)."""
|
|
13
|
+
try:
|
|
14
|
+
return load_coefficient_file('sn_crown_ratio_coefficients.json')
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
return {}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CrownRatioModel:
|
|
20
|
+
"""Crown ratio model implementing FVS Southern variant equations."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, species_code: str = "LP"):
|
|
23
|
+
"""Initialize with species-specific parameters.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
species_code: Species code (e.g., "LP", "SP", "WO", etc.)
|
|
27
|
+
"""
|
|
28
|
+
self.species_code = species_code
|
|
29
|
+
self._load_parameters()
|
|
30
|
+
|
|
31
|
+
def _load_parameters(self):
|
|
32
|
+
"""Load crown ratio parameters from configuration."""
|
|
33
|
+
try:
|
|
34
|
+
crown_data = _get_crown_ratio_data()
|
|
35
|
+
|
|
36
|
+
if crown_data and self.species_code in crown_data.get('species_coefficients', {}):
|
|
37
|
+
self.coefficients = crown_data['species_coefficients'][self.species_code]
|
|
38
|
+
self.equations = crown_data.get('equations', {})
|
|
39
|
+
elif crown_data and 'LP' in crown_data.get('species_coefficients', {}):
|
|
40
|
+
# Fallback to default LP parameters if species not found
|
|
41
|
+
self.coefficients = crown_data['species_coefficients']['LP']
|
|
42
|
+
self.equations = crown_data.get('equations', {})
|
|
43
|
+
else:
|
|
44
|
+
# Fallback parameters if file not found or loading fails
|
|
45
|
+
self._load_fallback_parameters()
|
|
46
|
+
except Exception:
|
|
47
|
+
# Fallback parameters if file not found or loading fails
|
|
48
|
+
self._load_fallback_parameters()
|
|
49
|
+
|
|
50
|
+
def _load_fallback_parameters(self):
|
|
51
|
+
"""Load fallback parameters if crown ratio file not available."""
|
|
52
|
+
# Default LP parameters
|
|
53
|
+
self.coefficients = {
|
|
54
|
+
"acr_equation": "4.3.1.3",
|
|
55
|
+
"d0": 3.8284,
|
|
56
|
+
"d1": -0.2234,
|
|
57
|
+
"d2": 0.0172,
|
|
58
|
+
"a": 4.9701,
|
|
59
|
+
"b0": -14.6680,
|
|
60
|
+
"b1": 1.3196,
|
|
61
|
+
"c": 2.8517
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
self.equations = {
|
|
65
|
+
"live_trees_weibull": {
|
|
66
|
+
"average_crown_ratio_equations": {
|
|
67
|
+
"4_3_1_3": "ACR = exp[d0 + (d1 * ln(RELSDI)) + (d2 * RELSDI)]",
|
|
68
|
+
"4_3_1_4": "ACR = exp[d0 + (d1 * ln(RELSDI))]",
|
|
69
|
+
"4_3_1_5": "ACR = d0 + (d2 * RELSDI)",
|
|
70
|
+
"4_3_1_6": "ACR = d0 + (d1 * log10(RELSDI))",
|
|
71
|
+
"4_3_1_7": "ACR = RELSDI / ((d0 * RELSDI) + d1)"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
def calculate_average_crown_ratio(self, relsdi: float) -> float:
|
|
77
|
+
"""Calculate average crown ratio for the stand using species-specific equation.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
relsdi: Relative stand density index ((Stand SDI / Maximum SDI) * 10)
|
|
81
|
+
Bounded between 1.0 and 12.0
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Average crown ratio as a proportion (0-1)
|
|
85
|
+
"""
|
|
86
|
+
# Bound RELSDI
|
|
87
|
+
relsdi = max(1.0, min(12.0, relsdi))
|
|
88
|
+
|
|
89
|
+
equation_type = self.coefficients['acr_equation']
|
|
90
|
+
d0 = self.coefficients['d0']
|
|
91
|
+
d1 = self.coefficients.get('d1')
|
|
92
|
+
d2 = self.coefficients.get('d2')
|
|
93
|
+
|
|
94
|
+
if equation_type == "4.3.1.3":
|
|
95
|
+
# ACR = exp[d0 + (d1 * ln(RELSDI)) + (d2 * RELSDI)]
|
|
96
|
+
if d1 is not None and d2 is not None:
|
|
97
|
+
acr = math.exp(d0 + (d1 * math.log(relsdi)) + (d2 * relsdi))
|
|
98
|
+
else:
|
|
99
|
+
acr = math.exp(d0)
|
|
100
|
+
elif equation_type == "4.3.1.4":
|
|
101
|
+
# ACR = exp[d0 + (d1 * ln(RELSDI))]
|
|
102
|
+
if d1 is not None:
|
|
103
|
+
acr = math.exp(d0 + (d1 * math.log(relsdi)))
|
|
104
|
+
else:
|
|
105
|
+
acr = math.exp(d0)
|
|
106
|
+
elif equation_type == "4.3.1.5":
|
|
107
|
+
# ACR = d0 + (d2 * RELSDI)
|
|
108
|
+
if d2 is not None:
|
|
109
|
+
acr = d0 + (d2 * relsdi)
|
|
110
|
+
else:
|
|
111
|
+
acr = d0
|
|
112
|
+
elif equation_type == "4.3.1.6":
|
|
113
|
+
# ACR = d0 + (d1 * log10(RELSDI))
|
|
114
|
+
if d1 is not None:
|
|
115
|
+
acr = d0 + (d1 * math.log10(relsdi))
|
|
116
|
+
else:
|
|
117
|
+
acr = d0
|
|
118
|
+
elif equation_type == "4.3.1.7":
|
|
119
|
+
# ACR = RELSDI / ((d0 * RELSDI) + d1)
|
|
120
|
+
if d1 is not None:
|
|
121
|
+
acr = relsdi / ((d0 * relsdi) + d1)
|
|
122
|
+
else:
|
|
123
|
+
acr = relsdi / (d0 * relsdi + 1.0)
|
|
124
|
+
else:
|
|
125
|
+
# Default fallback
|
|
126
|
+
acr = math.exp(d0 + (d1 or 0) * math.log(relsdi) + (d2 or 0) * relsdi)
|
|
127
|
+
|
|
128
|
+
# Convert from percentage to proportion and bound
|
|
129
|
+
if acr > 1.0: # Assume it's in percentage
|
|
130
|
+
acr = acr / 100.0
|
|
131
|
+
|
|
132
|
+
return max(0.05, min(0.95, acr))
|
|
133
|
+
|
|
134
|
+
def calculate_weibull_parameters(self, average_crown_ratio: float) -> Tuple[float, float, float]:
|
|
135
|
+
"""Calculate Weibull distribution parameters from average crown ratio.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
average_crown_ratio: Average crown ratio as proportion (0-1)
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Tuple of (A, B, C) Weibull parameters
|
|
142
|
+
"""
|
|
143
|
+
a = self.coefficients['a']
|
|
144
|
+
b0 = self.coefficients['b0']
|
|
145
|
+
b1 = self.coefficients['b1']
|
|
146
|
+
c = self.coefficients['c']
|
|
147
|
+
|
|
148
|
+
# Convert ACR from proportion (0-1) to percentage (0-100) for Weibull calculation
|
|
149
|
+
# The b0/b1 coefficients were calibrated expecting ACR in percentage form
|
|
150
|
+
acr_pct = average_crown_ratio * 100.0
|
|
151
|
+
|
|
152
|
+
# Calculate Weibull parameters
|
|
153
|
+
A = a
|
|
154
|
+
B = max(3.0, b0 + b1 * acr_pct) # Bounded to be greater than 3.0
|
|
155
|
+
C = max(2.0, c) # Bounded to be greater than 2.0
|
|
156
|
+
|
|
157
|
+
return A, B, C
|
|
158
|
+
|
|
159
|
+
def calculate_scale_factor(self, ccf: float) -> float:
|
|
160
|
+
"""Calculate density-dependent scaling factor.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
ccf: Crown competition factor
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Scale factor (bounded 0.3 < SCALE < 1.0)
|
|
167
|
+
"""
|
|
168
|
+
scale = 1.0 - 0.00167 * (ccf - 100)
|
|
169
|
+
return max(0.3, min(1.0, scale))
|
|
170
|
+
|
|
171
|
+
def predict_individual_crown_ratio(self, tree_rank: float, relsdi: float,
|
|
172
|
+
ccf: float = 100.0) -> float:
|
|
173
|
+
"""Predict individual tree crown ratio using Weibull distribution.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
tree_rank: Tree's rank in diameter distribution (0-1, where 0=smallest, 1=largest)
|
|
177
|
+
relsdi: Relative stand density index
|
|
178
|
+
ccf: Crown competition factor (default: 100)
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Crown ratio as proportion (0-1)
|
|
182
|
+
"""
|
|
183
|
+
# Calculate average crown ratio
|
|
184
|
+
acr = self.calculate_average_crown_ratio(relsdi)
|
|
185
|
+
|
|
186
|
+
# Calculate Weibull parameters
|
|
187
|
+
A, B, C = self.calculate_weibull_parameters(acr)
|
|
188
|
+
|
|
189
|
+
# Calculate scale factor
|
|
190
|
+
scale = self.calculate_scale_factor(ccf)
|
|
191
|
+
|
|
192
|
+
# Bound tree rank to avoid numerical issues
|
|
193
|
+
x = max(0.05, min(0.95, tree_rank))
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
# Calculate crown ratio using Weibull distribution
|
|
197
|
+
# Y = A + B(-ln(1-X))^(1/C)
|
|
198
|
+
crown_ratio = A + B * ((-math.log(1 - x)) ** (1/C))
|
|
199
|
+
|
|
200
|
+
# Apply scale factor
|
|
201
|
+
crown_ratio *= scale
|
|
202
|
+
|
|
203
|
+
# Convert from percentage to proportion if needed
|
|
204
|
+
if crown_ratio > 1.0:
|
|
205
|
+
crown_ratio = crown_ratio / 100.0
|
|
206
|
+
|
|
207
|
+
# Bound between 5% and 95% as specified in FVS
|
|
208
|
+
return max(0.05, min(0.95, crown_ratio))
|
|
209
|
+
|
|
210
|
+
except (ValueError, OverflowError):
|
|
211
|
+
# Fallback to simple calculation if Weibull fails
|
|
212
|
+
return max(0.05, min(0.95, acr * scale))
|
|
213
|
+
|
|
214
|
+
def predict_dead_tree_crown_ratio(self, dbh: float, random_seed: Optional[int] = None) -> float:
|
|
215
|
+
"""Predict crown ratio for dead trees using equations 4.3.1.1 and 4.3.1.2.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
dbh: Diameter at breast height (inches)
|
|
219
|
+
random_seed: Optional random seed for reproducibility
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Crown ratio as proportion (0-1)
|
|
223
|
+
"""
|
|
224
|
+
if random_seed is not None:
|
|
225
|
+
random.seed(random_seed)
|
|
226
|
+
|
|
227
|
+
# Equation 4.3.1.1
|
|
228
|
+
if dbh < 24.0:
|
|
229
|
+
X = 0.70 - 0.40/24.0 * dbh
|
|
230
|
+
else:
|
|
231
|
+
X = 0.30
|
|
232
|
+
|
|
233
|
+
# Add random component (standard deviation not specified, using 0.2)
|
|
234
|
+
random_component = random.gauss(0, 0.2)
|
|
235
|
+
|
|
236
|
+
# Equation 4.3.1.2: CR = 1 / (1 + exp(X + N(0,SD)))
|
|
237
|
+
crown_ratio = 1.0 / (1.0 + math.exp(X + random_component))
|
|
238
|
+
|
|
239
|
+
# Bound to specified range
|
|
240
|
+
return max(0.05, min(0.95, crown_ratio))
|
|
241
|
+
|
|
242
|
+
def predict_regeneration_crown_ratio(self, pccf: float, random_seed: Optional[int] = None) -> float:
|
|
243
|
+
"""Predict crown ratio for newly established trees during regeneration.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
pccf: Crown competition factor on the inventory point where tree is established
|
|
247
|
+
random_seed: Optional random seed for reproducibility
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Crown ratio as proportion (0-1)
|
|
251
|
+
"""
|
|
252
|
+
if random_seed is not None:
|
|
253
|
+
random.seed(random_seed)
|
|
254
|
+
|
|
255
|
+
# Small random component
|
|
256
|
+
ran = random.gauss(0, 0.05)
|
|
257
|
+
|
|
258
|
+
# Equation 4.3.3.1: CR = 0.89722 - 0.0000461 * PCCF + RAN
|
|
259
|
+
crown_ratio = 0.89722 - 0.0000461 * pccf + ran
|
|
260
|
+
|
|
261
|
+
# Bound to specified range
|
|
262
|
+
return max(0.2, min(0.9, crown_ratio))
|
|
263
|
+
|
|
264
|
+
def update_crown_ratio_change(self, current_cr: float, predicted_cr: float,
|
|
265
|
+
height_growth: float, cycle_length: int = 5) -> float:
|
|
266
|
+
"""Calculate crown ratio change with bounds checking.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
current_cr: Current crown ratio (proportion)
|
|
270
|
+
predicted_cr: Predicted crown ratio at end of cycle (proportion)
|
|
271
|
+
height_growth: Height growth during cycle (feet)
|
|
272
|
+
cycle_length: Length of projection cycle (years)
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
New crown ratio (proportion)
|
|
276
|
+
"""
|
|
277
|
+
# Calculate potential change
|
|
278
|
+
change = predicted_cr - current_cr
|
|
279
|
+
|
|
280
|
+
# Check that change doesn't exceed what's possible with height growth
|
|
281
|
+
# Assume all height growth produces new crown
|
|
282
|
+
max_possible_change = height_growth / 100.0 # Rough approximation
|
|
283
|
+
|
|
284
|
+
# Bound change to 1% per year for cycle length
|
|
285
|
+
max_annual_change = 0.01
|
|
286
|
+
max_cycle_change = max_annual_change * cycle_length
|
|
287
|
+
|
|
288
|
+
# Apply bounds
|
|
289
|
+
bounded_change = max(-max_cycle_change,
|
|
290
|
+
min(max_cycle_change,
|
|
291
|
+
min(change, max_possible_change)))
|
|
292
|
+
|
|
293
|
+
new_cr = current_cr + bounded_change
|
|
294
|
+
|
|
295
|
+
# Final bounds
|
|
296
|
+
return max(0.05, min(0.95, new_cr))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def create_crown_ratio_model(species_code: str = "LP") -> CrownRatioModel:
|
|
300
|
+
"""Factory function to create a crown ratio model for a species.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
species_code: Species code (e.g., "LP", "SP", "WO", etc.)
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
CrownRatioModel instance
|
|
307
|
+
"""
|
|
308
|
+
return CrownRatioModel(species_code)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def calculate_average_crown_ratio(species_code: str, relsdi: float) -> float:
|
|
312
|
+
"""Standalone function to calculate average crown ratio.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
species_code: Species code
|
|
316
|
+
relsdi: Relative stand density index
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Average crown ratio as proportion
|
|
320
|
+
"""
|
|
321
|
+
model = create_crown_ratio_model(species_code)
|
|
322
|
+
return model.calculate_average_crown_ratio(relsdi)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def predict_tree_crown_ratio(species_code: str, tree_rank: float, relsdi: float,
|
|
326
|
+
ccf: float = 100.0) -> float:
|
|
327
|
+
"""Standalone function to predict individual tree crown ratio.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
species_code: Species code
|
|
331
|
+
tree_rank: Tree's rank in diameter distribution (0-1)
|
|
332
|
+
relsdi: Relative stand density index
|
|
333
|
+
ccf: Crown competition factor
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Crown ratio as proportion
|
|
337
|
+
"""
|
|
338
|
+
model = create_crown_ratio_model(species_code)
|
|
339
|
+
return model.predict_individual_crown_ratio(tree_rank, relsdi, ccf)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def compare_crown_ratio_models(species_codes: list, relsdi_range: list) -> Dict[str, Any]:
|
|
343
|
+
"""Compare crown ratio predictions across species and density levels.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
species_codes: List of species codes to compare
|
|
347
|
+
relsdi_range: List of RELSDI values to evaluate
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
Dictionary with comparison results
|
|
351
|
+
"""
|
|
352
|
+
results = {
|
|
353
|
+
'relsdi': relsdi_range,
|
|
354
|
+
'species_results': {}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
for species in species_codes:
|
|
358
|
+
model = create_crown_ratio_model(species)
|
|
359
|
+
|
|
360
|
+
acr_values = []
|
|
361
|
+
individual_cr_values = []
|
|
362
|
+
|
|
363
|
+
for relsdi in relsdi_range:
|
|
364
|
+
acr = model.calculate_average_crown_ratio(relsdi)
|
|
365
|
+
acr_values.append(acr)
|
|
366
|
+
|
|
367
|
+
# Calculate individual tree CR for median tree (rank = 0.5)
|
|
368
|
+
individual_cr = model.predict_individual_crown_ratio(0.5, relsdi)
|
|
369
|
+
individual_cr_values.append(individual_cr)
|
|
370
|
+
|
|
371
|
+
results['species_results'][species] = {
|
|
372
|
+
'average_crown_ratio': acr_values,
|
|
373
|
+
'individual_crown_ratio': individual_cr_values,
|
|
374
|
+
'equation_type': model.coefficients['acr_equation']
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return results
|