Chapter 12: Harvest Cost Curves with FHOPS

Learning Objectives

After reading this chapter, you should be able to:

  • Explain what FHOPS is and its role in the ws3 ecosystem

  • Generate harvest cost yield curves using fhops

  • Understand the relationship between productivity, costing, and yield

  • Inject fhops-generated curves into ws3 models

  • Use the fhops CLI for common tasks

What Is FHOPS?

FHOPS (Forest Harvest Operations) is a tool for generating harvest cost yield curves. While ws3 models what gets harvested and when, FHOPS models how much it costs to harvest.

FHOPS fills a critical gap: traditional wood supply models often use simplified, static harvest costs. FHOPS generates dynamic harvest cost curves that vary by:

  • Productivity: Site quality, stand density, tree size

  • Distance: Distance to landing, road access

  • Terrain: Slope, soil conditions

  • Species: Different species require different harvesting techniques

        graph TD
  INPUTS["Input Data<br/>(inventory, terrain, roads)"] --> FHOPS["FHOPS<br/>Cost modeling"]
  FHOPS --> CURVES["Harvest Cost<br/>Yield Curves"]
  CURVES --> WS3["ws3 ForestModel<br/>(integration)"]
  WS3 --> OPT["Optimization<br/>(with accurate costs)"]
    

The Productivity Concept

FHOPS organizes cost modeling around productivity. A productivity class represents a combination of factors that affect harvesting efficiency:

  • Stand density: More trees per hectare = more time per hectare

  • Tree size: Larger trees = more time per tree

  • Terrain: Steeper slopes = slower machine movement

  • Soil conditions: Wet soils = reduced machine mobility

from fhops.productivity import ProductivityRegistry

# Register productivity classes
registry = ProductivityRegistry()

registry.add_productivity_class(
    name="high_productivity",
    description="Flat terrain, good access, moderate density",
    base_cost_per_m3=25.0,
    density_factor=1.0,
    slope_factor=1.0,
    distance_factor=1.0
)

registry.add_productivity_class(
    name="low_productivity",
    description="Steep terrain, poor access, high density",
    base_cost_per_m3=45.0,
    density_factor=1.5,
    slope_factor=1.8,
    distance_factor=2.0
)

Generating Cost Curves

FHOPS generates harvest cost curves that relate harvest volume to cost:

from fhops.costing import CostCurveGenerator

# Generate cost curves for different productivity classes
generator = CostCurveGenerator(registry)

cost_curves = generator.generate(
    productivity_classes=["high_productivity", "low_productivity"],
    volume_range=[0, 1000],  # m³/ha
    num_points=50
)

# Each curve maps volume to cost
for pc_name, curve in cost_curves.items():
    print(f"{pc_name}: cost at 500 m³/ha = ${curve(500):.2f}")

Cost Curve Structure

A harvest cost curve typically has this structure:

        graph TD
  VOLUME["Harvest Volume<br/>(m³/ha)"] --> FIXED["Fixed Costs<br/>(setup, mobilization)"]
  VOLUME --> VARIABLE["Variable Costs<br/>(per m³)"]
  VARIABLE --> DENSITY["Density adjustment"]
  VARIABLE --> DISTANCE["Distance adjustment"]
  VARIABLE --> SLOPE["Slope adjustment"]
  FIXED --> TOTAL["Total Cost<br/>($/ha)"]
  VARIABLE --> TOTAL
    

The total cost is:

\[\begin{split}\\text{Total Cost} = \\text{Fixed Costs} + \\text{Variable Cost per m³} \\times \\text{Volume} \\times \\text{Adjustment Factors}\end{split}\]

Integrating with ws3

The key integration point: fhops cost curves can be injected into ws3 as part of the financial analysis:

from ws3.forest import ForestModel
from ws3.core import Curve
from fhops.costing import CostCurveGenerator

# Generate fhops cost curves
registry = ProductivityRegistry()
# ... add productivity classes ...
generator = CostCurveGenerator(registry)
cost_curves = generator.generate(
    productivity_classes=["high_productivity", "low_productivity"],
    volume_range=[0, 1000],
    num_points=50
)

# Create ws3 model with required parameters
model = ForestModel(
    model_name="fhops_example",
    model_path="/path/to/data",
    base_year=2024,
    horizon=20,
    period_length=10,
    max_age=200
)

# Development types are loaded from Woodstock-format data files
# via model.import_areas_section(), import_yields_section(), etc.

# Add growth curve (volume) using register_curve
volume_curve = Curve(
    label="DF-SI50_volume",
    is_volume=True,
    points=[(0, 0), (10, 5), (20, 25), (30, 65), (40, 120),
            (50, 200), (60, 300), (70, 400), (80, 470),
            (90, 500), (100, 510)]
)
model.register_curve(volume_curve)

# Add cost curve from fhops using register_curve
# Note: Curve constructor takes points=[(x,y)], not x= and y= separately
high_prod_cost = cost_curves["high_productivity"]
cost_curve = Curve(
    label="DF-SI50_HighProd_cost",
    points=list(zip(high_prod_cost.x, high_prod_cost.y))
)
model.register_curve(cost_curve)

# Now the model has both volume and cost curves
# Use them in optimization to maximize net revenue
volume_at_60 = volume_curve(60)  # m³/ha
cost_at_60 = cost_curve(60)  # $/ha
net_revenue_per_ha = volume_at_60 * 50 - cost_at_60  # $/ha

The fhops CLI

FHOPS provides a command-line interface for common tasks:

# Generate cost curves from configuration
fhops generate-cost-curves \
    --config config/costing.yaml \
    --output output/cost_curves.csv

# List available productivity classes
fhops list-productivity-classes

# Validate a costing configuration
fhops validate-config config/costing.yaml

# Export curves for ws3 integration
fhops export-ws3 config/costing.yaml output/ws3_curves/

CLI Configuration

The fhops CLI uses YAML configuration files:

# config/costing.yaml
productivity_classes:
  - name: high_productivity
    base_cost_per_m3: 25.0
    density_factor: 1.0
    slope_factor: 1.0
    distance_factor: 1.0

  - name: low_productivity
    base_cost_per_m3: 45.0
    density_factor: 1.5
    slope_factor: 1.8
    distance_factor: 2.0

volume_range:
  min: 0
  max: 1000
  num_points: 50

output:
  format: csv
  path: output/cost_curves.csv

Best Practices

  1. Calibrate costs to local conditions: Use actual harvesting data to calibrate fhops parameters

  2. Validate against benchmarks: Compare fhops output to known cost estimates

  3. Use productivity classes consistently: Define clear criteria for each productivity class

  4. Document assumptions: Record the data sources and assumptions behind cost parameters

  5. Version configurations: Track changes to costing parameters over time

Exercises

Exercise 1 (Easy): Generate harvest cost curves for two productivity classes and plot them.

Exercise 2 (Medium): Create a ws3 model with both volume and cost curves, and calculate net revenue at different ages.

Exercise 3 (Hard): Build an optimization problem that uses fhops cost curves to find the rotation age that maximizes net present value.

Further Reading