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.
Files changed (149) hide show
  1. fvs_python-0.2.3.dist-info/METADATA +254 -0
  2. fvs_python-0.2.3.dist-info/RECORD +149 -0
  3. fvs_python-0.2.3.dist-info/WHEEL +5 -0
  4. fvs_python-0.2.3.dist-info/licenses/LICENSE +21 -0
  5. fvs_python-0.2.3.dist-info/top_level.txt +1 -0
  6. pyfvs/__init__.py +107 -0
  7. pyfvs/bark_ratio.py +323 -0
  8. pyfvs/cfg/CFG_README.md +73 -0
  9. pyfvs/cfg/dbh_bounding_table_4_7_1_8.json +103 -0
  10. pyfvs/cfg/ecounit_coefficients_table_4_7_1_5.json +981 -0
  11. pyfvs/cfg/ecounit_coefficients_table_4_7_1_6.json +856 -0
  12. pyfvs/cfg/forest_type_mapping_table_4_7_1_4.json +38 -0
  13. pyfvs/cfg/fortype_coefficients_table_4_7_1_3.json +1183 -0
  14. pyfvs/cfg/functional_forms.yaml +111 -0
  15. pyfvs/cfg/growth_model_parameters.yaml +98 -0
  16. pyfvs/cfg/plant_values_table_4_7_1_7.json +15 -0
  17. pyfvs/cfg/site_index_transformation.yaml +113 -0
  18. pyfvs/cfg/sn_bark_ratio_coefficients.json +115 -0
  19. pyfvs/cfg/sn_crown_competition_factor.json +109 -0
  20. pyfvs/cfg/sn_crown_ratio_coefficients.json +956 -0
  21. pyfvs/cfg/sn_crown_width_coefficients.json +1664 -0
  22. pyfvs/cfg/sn_diameter_growth_coefficients.json +300 -0
  23. pyfvs/cfg/sn_height_diameter_coefficients.json +97 -0
  24. pyfvs/cfg/sn_large_tree_diameter_growth.json +191 -0
  25. pyfvs/cfg/sn_large_tree_height_growth.json +229 -0
  26. pyfvs/cfg/sn_large_tree_height_growth_coefficients.json +1187 -0
  27. pyfvs/cfg/sn_mortality_model.json +176 -0
  28. pyfvs/cfg/sn_regeneration_model.json +252 -0
  29. pyfvs/cfg/sn_relative_site_index.json +263 -0
  30. pyfvs/cfg/sn_small_tree_height_growth.json +879 -0
  31. pyfvs/cfg/sn_species_codes_table.json +728 -0
  32. pyfvs/cfg/sn_stand_density_index.json +398 -0
  33. pyfvs/cfg/species/ab_american_basswood.yaml +251 -0
  34. pyfvs/cfg/species/ae_american_elm.yaml +240 -0
  35. pyfvs/cfg/species/ah_american_hornbeam.yaml +250 -0
  36. pyfvs/cfg/species/ap_american_plum.yaml +251 -0
  37. pyfvs/cfg/species/as_american_sycamore.yaml +253 -0
  38. pyfvs/cfg/species/ba_black_ash.yaml +254 -0
  39. pyfvs/cfg/species/bb_basswood.yaml +254 -0
  40. pyfvs/cfg/species/bc_black_cherry.yaml +254 -0
  41. pyfvs/cfg/species/bd_sweet_birch.yaml +252 -0
  42. pyfvs/cfg/species/be_american_beech.yaml +251 -0
  43. pyfvs/cfg/species/bg_black_gum.yaml +252 -0
  44. pyfvs/cfg/species/bj_blue_jay.yaml +254 -0
  45. pyfvs/cfg/species/bk_sugar_maple.yaml +251 -0
  46. pyfvs/cfg/species/bn_butternut.yaml +252 -0
  47. pyfvs/cfg/species/bo_red_maple.yaml +255 -0
  48. pyfvs/cfg/species/bt_bigtooth_aspen.yaml +254 -0
  49. pyfvs/cfg/species/bu_buckeye.yaml +252 -0
  50. pyfvs/cfg/species/by_bald_cypress.yaml +255 -0
  51. pyfvs/cfg/species/ca_american_chestnut.yaml +254 -0
  52. pyfvs/cfg/species/cb_cucumber_tree.yaml +254 -0
  53. pyfvs/cfg/species/ck_virginia_pine.yaml +254 -0
  54. pyfvs/cfg/species/co_pond_cypress.yaml +251 -0
  55. pyfvs/cfg/species/ct_catalpa.yaml +251 -0
  56. pyfvs/cfg/species/cw_chestnut_oak.yaml +253 -0
  57. pyfvs/cfg/species/dw_dogwood.yaml +250 -0
  58. pyfvs/cfg/species/el_american_hornbeam.yaml +254 -0
  59. pyfvs/cfg/species/fm_flowering_dogwood.yaml +251 -0
  60. pyfvs/cfg/species/fr_fraser_fir.yaml +247 -0
  61. pyfvs/cfg/species/ga_green_ash.yaml +254 -0
  62. pyfvs/cfg/species/ha_hawthorn.yaml +252 -0
  63. pyfvs/cfg/species/hb_hornbeam.yaml +254 -0
  64. pyfvs/cfg/species/hh_dogwood.yaml +251 -0
  65. pyfvs/cfg/species/hi_hickory_species.yaml +252 -0
  66. pyfvs/cfg/species/hl_holly.yaml +254 -0
  67. pyfvs/cfg/species/hm_eastern_hemlock.yaml +246 -0
  68. pyfvs/cfg/species/hy_holly.yaml +252 -0
  69. pyfvs/cfg/species/ju_eastern_juniper.yaml +247 -0
  70. pyfvs/cfg/species/lb_loblolly_bay.yaml +254 -0
  71. pyfvs/cfg/species/lk_laurel_oak.yaml +254 -0
  72. pyfvs/cfg/species/ll_longleaf_pine.yaml +265 -0
  73. pyfvs/cfg/species/lo_silver_maple.yaml +252 -0
  74. pyfvs/cfg/species/lp_loblolly_pine.yaml +268 -0
  75. pyfvs/cfg/species/mb_mountain_birch.yaml +250 -0
  76. pyfvs/cfg/species/mg_magnolia.yaml +251 -0
  77. pyfvs/cfg/species/ml_maple_leaf.yaml +254 -0
  78. pyfvs/cfg/species/ms_maple_species.yaml +247 -0
  79. pyfvs/cfg/species/mv_magnolia_vine.yaml +254 -0
  80. pyfvs/cfg/species/oh_other_hardwood.yaml +231 -0
  81. pyfvs/cfg/species/os_other_softwood.yaml +232 -0
  82. pyfvs/cfg/species/ot_other_tree.yaml +210 -0
  83. pyfvs/cfg/species/ov_overcup_oak.yaml +254 -0
  84. pyfvs/cfg/species/pc_pond_cypress.yaml +254 -0
  85. pyfvs/cfg/species/pd_pitch_pine.yaml +245 -0
  86. pyfvs/cfg/species/pi_pine_species.yaml +246 -0
  87. pyfvs/cfg/species/po_american_beech.yaml +254 -0
  88. pyfvs/cfg/species/pp_pond_pine.yaml +246 -0
  89. pyfvs/cfg/species/ps_persimmon.yaml +251 -0
  90. pyfvs/cfg/species/pu_pond_pine.yaml +249 -0
  91. pyfvs/cfg/species/qs_flowering_dogwood.yaml +254 -0
  92. pyfvs/cfg/species/ra_red_ash.yaml +245 -0
  93. pyfvs/cfg/species/rd_redbud.yaml +251 -0
  94. pyfvs/cfg/species/rl_red_elm.yaml +240 -0
  95. pyfvs/cfg/species/rm_red_maple.yaml +256 -0
  96. pyfvs/cfg/species/ro_eastern_hemlock.yaml +255 -0
  97. pyfvs/cfg/species/sa_slash_pine.yaml +265 -0
  98. pyfvs/cfg/species/sb_sweet_birch.yaml +255 -0
  99. pyfvs/cfg/species/sd_sand_pine.yaml +251 -0
  100. pyfvs/cfg/species/sk_swamp_oak.yaml +253 -0
  101. pyfvs/cfg/species/sm_sugar_maple.yaml +252 -0
  102. pyfvs/cfg/species/sn_loblolly_pine.yaml +254 -0
  103. pyfvs/cfg/species/so_southern_oak.yaml +253 -0
  104. pyfvs/cfg/species/sp_shortleaf_pine.yaml +267 -0
  105. pyfvs/cfg/species/sr_spruce_pine.yaml +246 -0
  106. pyfvs/cfg/species/ss_basswood.yaml +251 -0
  107. pyfvs/cfg/species/su_sweetgum.yaml +255 -0
  108. pyfvs/cfg/species/sv_silver_maple.yaml +255 -0
  109. pyfvs/cfg/species/sy_sycamore.yaml +254 -0
  110. pyfvs/cfg/species/tm_tamarack.yaml +246 -0
  111. pyfvs/cfg/species/to_tulip_oak.yaml +254 -0
  112. pyfvs/cfg/species/ts_tulip_tree.yaml +253 -0
  113. pyfvs/cfg/species/vp_virginia_pine.yaml +248 -0
  114. pyfvs/cfg/species/wa_white_ash.yaml +254 -0
  115. pyfvs/cfg/species/we_white_elm.yaml +250 -0
  116. pyfvs/cfg/species/wi_willow.yaml +248 -0
  117. pyfvs/cfg/species/wk_water_oak.yaml +254 -0
  118. pyfvs/cfg/species/wn_walnut.yaml +254 -0
  119. pyfvs/cfg/species/wo_white_oak.yaml +256 -0
  120. pyfvs/cfg/species/wp_white_pine.yaml +250 -0
  121. pyfvs/cfg/species/wt_water_tupelo.yaml +254 -0
  122. pyfvs/cfg/species/yp_yellow_poplar.yaml +261 -0
  123. pyfvs/cfg/species_config.yaml +106 -0
  124. pyfvs/clark_profile.py +323 -0
  125. pyfvs/competition.py +332 -0
  126. pyfvs/config_loader.py +375 -0
  127. pyfvs/crown_competition_factor.py +464 -0
  128. pyfvs/crown_ratio.py +377 -0
  129. pyfvs/crown_width.py +512 -0
  130. pyfvs/data_export.py +356 -0
  131. pyfvs/ecological_unit.py +272 -0
  132. pyfvs/exceptions.py +86 -0
  133. pyfvs/fia_integration.py +876 -0
  134. pyfvs/forest_type.py +253 -0
  135. pyfvs/growth_plots.py +579 -0
  136. pyfvs/harvest.py +603 -0
  137. pyfvs/height_diameter.py +248 -0
  138. pyfvs/large_tree_height_growth.py +822 -0
  139. pyfvs/logging_config.py +213 -0
  140. pyfvs/main.py +99 -0
  141. pyfvs/mortality.py +431 -0
  142. pyfvs/parameters.py +121 -0
  143. pyfvs/simulation_engine.py +386 -0
  144. pyfvs/stand.py +1004 -0
  145. pyfvs/stand_metrics.py +436 -0
  146. pyfvs/stand_output.py +552 -0
  147. pyfvs/tree.py +756 -0
  148. pyfvs/validation.py +190 -0
  149. 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