Chapter 5: Optimization ======================= Learning Objectives ------------------- After reading this chapter, you should be able to: - Formulate a wood supply optimization problem using the :py:class:`ws3.opt.Problem` class - Define decision variables, objective functions, and constraints - Solve optimization problems with different solvers (HiGHS, Gurobi) - Interpret optimization results and extract harvest schedules What Is Optimization in Forest Planning? ---------------------------------------- Optimization helps forest managers find the **best** management plan given a set of constraints. Instead of guessing which harvest schedule is best, optimization systematically searches for the schedule that maximizes (or minimizes) your objective. Common objectives: - **Maximize net present value (NPV)**: Maximize economic returns - **Maximize sustained yield**: Maximize average annual harvest - **Minimize cost**: Minimize harvesting and silviculture costs - **Maximize carbon sequestration**: Maximize standing biomass Common constraints: - **Area constraints**: Maximum harvest area per period - **Volume constraints**: Minimum or maximum volume harvested - **Inventory constraints**: Minimum ending inventory - **Spatial constraints**: Contiguity, adjacency requirements The Optimization Problem ------------------------ A wood supply optimization problem has three components: 1. **Decision variables**: What we can control (e.g., harvest area) 2. **Objective function**: What we want to optimize (e.g., NPV) 3. **Constraints**: What limits our decisions (e.g., max harvest area) .. mermaid:: graph TD VAR["Decision Variables
What we control"] --> OBJ["Objective Function
What we optimize"] VAR --> CON["Constraints
What limits us"] OBJ --> SOL["Solution
Optimal values"] CON --> SOL Setting Up an Optimization Problem ---------------------------------- .. code-block:: python from ws3.opt import Problem # Create an optimization problem prob = Problem("example_problem") # Add decision variables # x1 = harvest area for development type 1 # x2 = harvest area for development type 2 prob.add_var("harvest_DT1", vtype="continuous", lb=0, ub=500) prob.add_var("harvest_DT2", vtype="continuous", lb=0, ub=300) # Add objective: maximize NPV # The z() method sets objective coefficients as a dict keyed on variable names # NPV = 50 * x1 + 40 * x2 (price per m³ * volume per ha * area) prob.z(coeffs={"harvest_DT1": 50.0, "harvest_DT2": 40.0}) # Add constraints # Constraint 1: Total harvest cannot exceed 200 ha per period # add_constraint(name, coeffs_dict, sense, rhs) where sense is 'leq', 'geq', or 'eq' prob.add_constraint("max_harvest", coeffs={"harvest_DT1": 1.0, "harvest_DT2": 1.0}, sense="leq", rhs=200) # Constraint 2: At least 100 ha of DT1 must remain prob.add_constraint("min_inventory", coeffs={"harvest_DT1": 1.0}, sense="leq", rhs=400) Solving the Problem ------------------- ws3 supports multiple solvers: .. code-block:: python # Set solver and solve # The solver is set via prob.solver("highs") before calling solve() prob.solver("highs") # or "gurobi" or "pulp" prob.solve() Extracting Results ------------------ .. code-block:: python # Get the optimal solution solution = prob.solution() # Print decision variable values print(f"Optimal harvest for DT1: {solution['harvest_DT1']:.1f} ha") print(f"Optimal harvest for DT2: {solution['harvest_DT2']:.1f} ha") # Print the objective value print(f"Maximum NPV: ${prob.z():,.0f}") Multi-Period Optimization ------------------------- For realistic forest planning, you need to optimize over multiple periods: .. code-block:: python # Create variables for each development type and period # Track variable names for building objective and constraints harvest_var_names = {} # {(dt_code, period): var_name} for dt_code in ["DF-SI50", "SP-SI40"]: for period in range(20): var_name = f"harv_{dt_code}_p{period}" prob.add_var(var_name, vtype="continuous", lb=0, ub=100) # Max 100 ha per period harvest_var_names[(dt_code, period)] = var_name # Objective: maximize NPV over all periods # z() takes a dict keyed on variable names with coefficient values npv_coeffs = {} discount_rate = 0.05 for (dt_code, period), var_name in harvest_var_names.items(): volume_per_ha = 200 # m³/ha (from growth curve) price = 50 # $/m³ coeff = volume_per_ha * price * (1 + discount_rate) ** (-period * 5) npv_coeffs[var_name] = coeff prob.z(coeffs=npv_coeffs) # Constraint: Maximum harvest area per period for period in range(20): period_var_names = [ harvest_var_names[(dt_code, period)] for dt_code in ["DF-SI50", "SP-SI40"] ] period_coeffs = {vn: 1.0 for vn in period_var_names} prob.add_constraint( f"max_harvest_p{period}", coeffs=period_coeffs, sense="leq", rhs=200 ) # Solve prob.solver("highs") prob.solve() # Extract solution solution = prob.solution() for (dt_code, period), var_name in harvest_var_names.items(): area = solution[var_name] if area > 0: print(f"Period {period}: Harvest {area:.1f} ha of {dt_code}") Solver Comparison ----------------- .. list-table:: :header-rows: 1 :widths: 20 30 25 25 * - Solver - Type - License - Best For * - HiGHS - Open-source - Free - Small to medium problems * - Gurobi - Commercial - Paid license - Large problems, MIP * - PuLP - Open-source - Free - Linear problems HiGHS is the default and works well for most wood supply problems. Use Gurobi if you need: - Mixed-integer programming (binary decisions) - Quadratic objectives - Very large problems (>10,000 variables) Common Optimization Patterns ---------------------------- 1. **Sustained yield**: Maximize average harvest over all periods 2. **Even flow**: Minimize variance in harvest across periods 3. **Rotation optimization**: Find the optimal rotation age 4. **Multi-objective**: Balance economic and ecological objectives Exercises --------- **Exercise 1 (Easy)**: Set up and solve a simple optimization problem to maximize NPV with two decision variables and two constraints. **Exercise 2 (Medium)**: Extend the multi-period optimization to include a constraint that total harvest over all periods does not exceed 2,000 ha. **Exercise 3 (Hard)**: Formulate a rotation optimization problem to find the optimal rotation age for Douglas-fir that maximizes NPV. Further Reading --------------- - :doc:`ch04_actions_and_transitions` — Defining actions - :doc:`/howto/running-optimization` — Detailed optimization guide - :doc:`/howto/parallel-optimization` — Parallel optimization for large models