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/logging_config.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging configuration for FVS-Python.
|
|
3
|
+
Provides structured logging with different levels and formats.
|
|
4
|
+
"""
|
|
5
|
+
import logging
|
|
6
|
+
import logging.config
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Dict, Any, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StructuredFormatter(logging.Formatter):
|
|
14
|
+
"""JSON formatter for structured logging."""
|
|
15
|
+
|
|
16
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
17
|
+
"""Format log record as JSON."""
|
|
18
|
+
log_data = {
|
|
19
|
+
'timestamp': datetime.now(timezone.utc).isoformat(),
|
|
20
|
+
'level': record.levelname,
|
|
21
|
+
'logger': record.name,
|
|
22
|
+
'message': record.getMessage(),
|
|
23
|
+
'module': record.module,
|
|
24
|
+
'function': record.funcName,
|
|
25
|
+
'line': record.lineno
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Add extra fields if present
|
|
29
|
+
if hasattr(record, 'species'):
|
|
30
|
+
log_data['species'] = record.species
|
|
31
|
+
if hasattr(record, 'stand_id'):
|
|
32
|
+
log_data['stand_id'] = record.stand_id
|
|
33
|
+
if hasattr(record, 'simulation_year'):
|
|
34
|
+
log_data['simulation_year'] = record.simulation_year
|
|
35
|
+
if hasattr(record, 'tree_count'):
|
|
36
|
+
log_data['tree_count'] = record.tree_count
|
|
37
|
+
|
|
38
|
+
# Add exception info if present
|
|
39
|
+
if record.exc_info:
|
|
40
|
+
log_data['exception'] = self.formatException(record.exc_info)
|
|
41
|
+
|
|
42
|
+
return json.dumps(log_data)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def setup_logging(
|
|
46
|
+
log_level: str = 'INFO',
|
|
47
|
+
log_file: Optional[Path] = None,
|
|
48
|
+
structured: bool = False
|
|
49
|
+
) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Set up logging configuration.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
55
|
+
log_file: Optional log file path
|
|
56
|
+
structured: Whether to use structured (JSON) logging
|
|
57
|
+
"""
|
|
58
|
+
# Base configuration
|
|
59
|
+
config: Dict[str, Any] = {
|
|
60
|
+
'version': 1,
|
|
61
|
+
'disable_existing_loggers': False,
|
|
62
|
+
'formatters': {
|
|
63
|
+
'standard': {
|
|
64
|
+
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
65
|
+
'datefmt': '%Y-%m-%d %H:%M:%S'
|
|
66
|
+
},
|
|
67
|
+
'detailed': {
|
|
68
|
+
'format': '%(asctime)s - %(name)s - %(levelname)s - [%(module)s:%(funcName)s:%(lineno)d] - %(message)s',
|
|
69
|
+
'datefmt': '%Y-%m-%d %H:%M:%S'
|
|
70
|
+
},
|
|
71
|
+
'structured': {
|
|
72
|
+
'()': StructuredFormatter
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
'handlers': {
|
|
76
|
+
'console': {
|
|
77
|
+
'class': 'logging.StreamHandler',
|
|
78
|
+
'level': log_level,
|
|
79
|
+
'formatter': 'standard' if not structured else 'structured',
|
|
80
|
+
'stream': 'ext://sys.stdout'
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
'root': {
|
|
84
|
+
'level': log_level,
|
|
85
|
+
'handlers': ['console']
|
|
86
|
+
},
|
|
87
|
+
'loggers': {
|
|
88
|
+
'fvs_python': {
|
|
89
|
+
'level': log_level,
|
|
90
|
+
'handlers': ['console'],
|
|
91
|
+
'propagate': False
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Add file handler if specified
|
|
97
|
+
if log_file:
|
|
98
|
+
config['handlers']['file'] = {
|
|
99
|
+
'class': 'logging.handlers.RotatingFileHandler',
|
|
100
|
+
'level': log_level,
|
|
101
|
+
'formatter': 'detailed' if not structured else 'structured',
|
|
102
|
+
'filename': str(log_file),
|
|
103
|
+
'maxBytes': 10485760, # 10MB
|
|
104
|
+
'backupCount': 5
|
|
105
|
+
}
|
|
106
|
+
config['root']['handlers'].append('file')
|
|
107
|
+
config['loggers']['fvs_python']['handlers'].append('file')
|
|
108
|
+
|
|
109
|
+
# Apply configuration
|
|
110
|
+
logging.config.dictConfig(config)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_logger(name: str) -> logging.Logger:
|
|
114
|
+
"""
|
|
115
|
+
Get a logger instance.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
name: Logger name (usually __name__)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Logger instance
|
|
122
|
+
"""
|
|
123
|
+
return logging.getLogger(name)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class SimulationLogContext:
|
|
127
|
+
"""Context manager for simulation-specific logging context."""
|
|
128
|
+
|
|
129
|
+
def __init__(self, logger: logging.Logger, **context):
|
|
130
|
+
"""
|
|
131
|
+
Initialize logging context.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
logger: Logger instance
|
|
135
|
+
**context: Context variables (species, stand_id, etc.)
|
|
136
|
+
"""
|
|
137
|
+
self.logger = logger
|
|
138
|
+
self.context = context
|
|
139
|
+
self.old_factory = None
|
|
140
|
+
|
|
141
|
+
def __enter__(self):
|
|
142
|
+
"""Enter context and set up log record factory."""
|
|
143
|
+
old_factory = logging.getLogRecordFactory()
|
|
144
|
+
|
|
145
|
+
def record_factory(*args, **kwargs):
|
|
146
|
+
record = old_factory(*args, **kwargs)
|
|
147
|
+
for key, value in self.context.items():
|
|
148
|
+
setattr(record, key, value)
|
|
149
|
+
return record
|
|
150
|
+
|
|
151
|
+
self.old_factory = old_factory
|
|
152
|
+
logging.setLogRecordFactory(record_factory)
|
|
153
|
+
return self
|
|
154
|
+
|
|
155
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
156
|
+
"""Exit context and restore original factory."""
|
|
157
|
+
if self.old_factory:
|
|
158
|
+
logging.setLogRecordFactory(self.old_factory)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# Convenience functions for common log messages
|
|
162
|
+
def log_simulation_start(logger: logging.Logger, species: str, years: int,
|
|
163
|
+
trees_per_acre: int, site_index: float) -> None:
|
|
164
|
+
"""Log simulation start."""
|
|
165
|
+
logger.info(
|
|
166
|
+
f"Starting simulation: species={species}, years={years}, "
|
|
167
|
+
f"TPA={trees_per_acre}, SI={site_index}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def log_simulation_progress(logger: logging.Logger, current_year: int,
|
|
172
|
+
total_years: int, trees_alive: int) -> None:
|
|
173
|
+
"""Log simulation progress."""
|
|
174
|
+
progress = (current_year / total_years) * 100
|
|
175
|
+
logger.debug(
|
|
176
|
+
f"Simulation progress: {current_year}/{total_years} years "
|
|
177
|
+
f"({progress:.1f}%), {trees_alive} trees alive"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def log_growth_summary(logger: logging.Logger, period: int,
|
|
182
|
+
dbh_growth: float, height_growth: float,
|
|
183
|
+
mortality: int) -> None:
|
|
184
|
+
"""Log growth period summary."""
|
|
185
|
+
logger.info(
|
|
186
|
+
f"Period {period} growth: DBH +{dbh_growth:.2f}\", "
|
|
187
|
+
f"Height +{height_growth:.1f}', Mortality: {mortality} trees"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def log_model_transition(logger: logging.Logger, tree_id: str,
|
|
192
|
+
from_model: str, to_model: str, dbh: float) -> None:
|
|
193
|
+
"""Log model transition for a tree."""
|
|
194
|
+
logger.debug(
|
|
195
|
+
f"Tree {tree_id} transitioned from {from_model} to {to_model} "
|
|
196
|
+
f"model at DBH={dbh:.1f}\""
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def log_configuration_loaded(logger: logging.Logger, config_type: str,
|
|
201
|
+
config_file: str) -> None:
|
|
202
|
+
"""Log configuration loading."""
|
|
203
|
+
logger.info(f"Loaded {config_type} configuration from {config_file}")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def log_error_with_context(logger: logging.Logger, error: Exception,
|
|
207
|
+
context: Dict[str, Any]) -> None:
|
|
208
|
+
"""Log error with additional context."""
|
|
209
|
+
logger.error(
|
|
210
|
+
f"Error occurred: {type(error).__name__}: {str(error)}",
|
|
211
|
+
exc_info=True,
|
|
212
|
+
extra=context
|
|
213
|
+
)
|
pyfvs/main.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Main entry point for FVS-Python simulations.
|
|
4
|
+
Uses the unified simulation engine for all operations.
|
|
5
|
+
"""
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from .simulation_engine import SimulationEngine, run_simulation, generate_yield_table
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
"""Main execution function."""
|
|
15
|
+
# Create output directory
|
|
16
|
+
output_dir = Path(__file__).parent.parent.parent / 'test_output'
|
|
17
|
+
output_dir.mkdir(exist_ok=True)
|
|
18
|
+
|
|
19
|
+
# Initialize simulation engine
|
|
20
|
+
engine = SimulationEngine(output_dir)
|
|
21
|
+
|
|
22
|
+
print("Starting FVS-Python simulation...")
|
|
23
|
+
print("-" * 50)
|
|
24
|
+
|
|
25
|
+
# Example 1: Single stand simulation
|
|
26
|
+
print("\n1. Running single stand simulation (Loblolly Pine)...")
|
|
27
|
+
results = engine.simulate_stand(
|
|
28
|
+
species='LP',
|
|
29
|
+
trees_per_acre=500,
|
|
30
|
+
site_index=70,
|
|
31
|
+
years=50,
|
|
32
|
+
time_step=5
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
print(f" Simulation complete. Final metrics:")
|
|
36
|
+
final = results.iloc[-1]
|
|
37
|
+
print(f" - Age: {final['age']} years")
|
|
38
|
+
print(f" - Trees/acre: {final['tpa']:.0f}")
|
|
39
|
+
print(f" - Mean DBH: {final['mean_dbh']:.1f} inches")
|
|
40
|
+
print(f" - Mean Height: {final['mean_height']:.1f} feet")
|
|
41
|
+
print(f" - Volume: {final['volume']:.0f} ft³/acre")
|
|
42
|
+
|
|
43
|
+
# Example 2: Generate yield table
|
|
44
|
+
print("\n2. Generating yield table for multiple scenarios...")
|
|
45
|
+
yield_table = engine.simulate_yield_table(
|
|
46
|
+
species=['LP', 'SP'], # Loblolly and Shortleaf pine
|
|
47
|
+
site_indices=[60, 70, 80],
|
|
48
|
+
planting_densities=[300, 500, 700],
|
|
49
|
+
years=40
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
print(f" Generated yield table with {len(yield_table)} records")
|
|
53
|
+
print(f" Species included: {yield_table['species'].unique()}")
|
|
54
|
+
print(f" Site indices: {yield_table['site_index'].unique()}")
|
|
55
|
+
print(f" Initial densities: {yield_table['initial_tpa'].unique()}")
|
|
56
|
+
|
|
57
|
+
# Example 3: Scenario comparison
|
|
58
|
+
print("\n3. Comparing management scenarios...")
|
|
59
|
+
scenarios = [
|
|
60
|
+
{
|
|
61
|
+
'name': 'Low Density LP',
|
|
62
|
+
'species': 'LP',
|
|
63
|
+
'trees_per_acre': 300,
|
|
64
|
+
'site_index': 70
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
'name': 'High Density LP',
|
|
68
|
+
'species': 'LP',
|
|
69
|
+
'trees_per_acre': 700,
|
|
70
|
+
'site_index': 70
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
'name': 'Low Site LP',
|
|
74
|
+
'species': 'LP',
|
|
75
|
+
'trees_per_acre': 500,
|
|
76
|
+
'site_index': 60
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
'name': 'High Site LP',
|
|
80
|
+
'species': 'LP',
|
|
81
|
+
'trees_per_acre': 500,
|
|
82
|
+
'site_index': 80
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
comparison = engine.compare_scenarios(scenarios, years=30)
|
|
87
|
+
|
|
88
|
+
print("\n Scenario comparison at age 30:")
|
|
89
|
+
age_30 = comparison[comparison['age'] == 30]
|
|
90
|
+
for _, row in age_30.iterrows():
|
|
91
|
+
print(f" {row['scenario']:20s} - Volume: {row['volume']:6.0f} ft³/acre")
|
|
92
|
+
|
|
93
|
+
print("\n" + "="*50)
|
|
94
|
+
print("All simulations completed successfully!")
|
|
95
|
+
print(f"Results saved to: {output_dir}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == '__main__':
|
|
99
|
+
main()
|