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
@@ -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()