Chapter 9: Advanced Topics ========================== Learning Objectives ------------------- After reading this chapter, you should be able to: - Extend ws3 with custom growth functions and area selectors - Integrate ws3 with other forest planning tools - Implement parallel computation for large models - Understand the limitations and boundaries of ws3 Custom Growth Functions ----------------------- While ws3 provides basic curve functionality, you may need custom growth functions for specific species or regions. Subclassing Curve ~~~~~~~~~~~~~~~~~ .. code-block:: python from ws3.core import Curve class CustomCurve(Curve): """Custom growth curve with additional methods.""" def __init__(self, points, growth_rate=0.1, **kwargs): super().__init__(points=points, **kwargs) self.growth_rate = growth_rate def relative_growth_rate(self, age): """Calculate relative growth rate at a given age.""" vol = self.lookup(age) if vol == 0: return 0 return (self.lookup(age + 1) - self.lookup(age)) / vol def time_to_double(self): """Estimate time to double current volume.""" current_vol = self.lookup(self.xmax) target_vol = 2 * current_vol for age in self.x: if self.lookup(age) >= target_vol: return age return None # Use the custom curve custom = CustomCurve( points=[(0, 0), (10, 5), (20, 25), (30, 65), (40, 120), (50, 200)], label=\"custom_DF\", is_volume=True, growth_rate=0.15 ) print(f\"Relative growth rate at age 30: {custom.relative_growth_rate(30):.3f}\") print(f\"Time to double: {custom.time_to_double()} years\") Custom Area Selectors --------------------- The :py:class:`ws3.forest.GreedyAreaSelector` class can be subclassed to implement custom harvest targeting logic. .. code-block:: python from ws3.forest import GreedyAreaSelector class HabitatAreaSelector(GreedyAreaSelector): """Select harvest areas based on habitat requirements.""" def __init__(self, model, min_habitat_area=100): super().__init__(model) self.min_habitat_area = min_habitat_area def operate(self, period, acode, target_area, mask=None, commit_actions=True, verbose=False): # Get all development types dts = self.model.development_types # Sort by habitat value (highest first) sorted_dts = sorted( dts, key=lambda dt: self.get_habitat_value(dt), reverse=True ) # Harvest from highest habitat value first harvested = 0 for dt in sorted_dts: if harvested >= target_area: break available = self.model.get_development_type_area(dt) harvest = min(available, target_area - harvested) self.model.operate(period, acode, harvest, dt) harvested += harvest return harvested def get_habitat_value(self, dt_code): """Calculate habitat value for a development type.""" # Example: older stands have higher habitat value age = self.model.get_development_type_age(dt_code) return age / 100.0 # Use the custom selector selector = HabitatAreaSelector(model, min_habitat_area=50) selector.operate(period=0, acode="HARV", target_area=100) Integrating with Other Tools ---------------------------- ws3 can be integrated with other forest planning tools: .. mermaid:: graph LR WS3["ws3
Wood supply model"] --> OUTPUT["Schedule output"] OUTPUT --> GIS["GIS software
(QGIS, ArcGIS)"] GIS --> MAP["Harvest maps"] WS3 --> FIN["Financial software
(Excel, Python)"] FIN --> REPORT["Financial reports"] Exporting Results ~~~~~~~~~~~~~~~~~ .. code-block:: python import pandas as pd # Export simulation results to CSV # Simulation results are accessed via dtype.area(period) and # yield curve lookups for each period. # Example: results_rows = [] for period in model.periods: for dtype_key, dtype in model.dtypes.items(): area = dtype.area(period) age = dtype.age(period) if hasattr(dtype, 'age') else period * model.period_length results_rows.append({ "period": period, "dtype": str(dtype_key), "area": area, "age": age }) df = pd.DataFrame(results_rows) df.to_csv("simulation_results.csv", index=False) # Export optimization solution solution = prob.solution() sol_df = pd.DataFrame(list(solution.items()), columns=["variable", "value"]) sol_df.to_csv("optimization_solution.csv", index=False) Importing External Data ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python # Load external growth curves from a CSV file import pandas as pd curve_data = pd.read_csv("growth_curves.csv") for _, row in curve_data.iterrows(): ages = [int(a) for a in row["ages"].split(",")] volumes = [float(v) for v in row["volumes"].split(",")] curve = Curve( label=row["name"], points=list(zip(ages, volumes)) ) model.register_curve(curve) Parallel Computation -------------------- For large models, ws3 provides parallel computation capabilities: .. code-block:: python from ws3.forest_helper import PersistentWorkerPool # Create a worker pool pool = PersistentWorkerPool(n_workers=4) # Define work items work_items = [(i, model) for i in range(100)] # Process in parallel results = pool.map(simulate_period, work_items) # Shutdown pool pool.shutdown() Limitations and Boundaries -------------------------- ws3 has known limitations: 1. **Aspatial default**: Spatial allocation is optional and less mature 2. **No stochastic optimization**: Only deterministic optimization 3. **Limited disturbance modeling**: Fire, insects require custom code 4. **Single objective**: Multi-objective optimization requires extension 5. **No real-time updating**: Models are static, not dynamic When ws3 May Not Be the Right Tool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Consider alternative tools if you need: - **Real-time decision support**: Use a database-driven system - **Complex spatial constraints**: Use a GIS-based optimizer - **Stochastic programming**: Use a dedicated stochastic optimizer - **Multi-agent simulation**: Use an agent-based modeling framework - **Climate change integration**: Use a dynamic vegetation model Extending ws3: Best Practices ----------------------------- 1. **Subclass, don't modify**: Extend base classes rather than changing ws3 code 2. **Keep it modular**: Each extension should have a single responsibility 3. **Write tests**: Test your extensions thoroughly 4. **Document assumptions**: Document any assumptions about growth, prices, etc. 5. **Validate against data**: Compare model output to observed data Exercises --------- **Exercise 1 (Easy)**: Create a custom growth curve that uses a logistic function instead of tabulated values. **Exercise 2 (Medium)**: Implement a custom area selector that avoids harvesting within 100 meters of water bodies. **Exercise 3 (Hard)**: Extend ws3 to support multi-objective optimization (Pareto front) by modifying the Problem class. Further Reading --------------- - :doc:`ch07_financial_analysis` — Financial analysis - :doc:`ch08_uncertainty_and_risk` — Uncertainty and risk - :doc:`/guides/coding-agent-onboarding` — Coding agent onboarding and extending ws3 - :doc:`/reference/contracts/index` — Data contracts and module boundaries