Runtime Invariants

This page documents invariants that must hold during ws3 execution.

Model State Invariants

  1. Development type keys are unique tuples

    Each development type is identified by a unique tuple of theme values (e.g., ('SP', 50, 'T1')). Duplicate keys in ForestModel.dtypes will cause undefined behavior.

  2. Yield curves are registered before use

    Every yield component curve referenced by a development type must have been registered with the model via ws3.forest.ForestModel.register_curve(). Curves are keyed by their point list — two curves with identical points share the same registered instance.

  3. Action transitions reference valid development types

    All target development types produced by action transitions must be resolvable from the source development type’s theme values.

  4. Area is non-negative

    All area values stored in DevelopmentType._areas must be >= 0. Negative areas indicate a bug.

  5. Ages are non-negative integers

    Stand ages must be non-negative integers. The ages list in ForestModel is list(range(max_age + 1)).

  6. Volume curves are monotonically non-decreasing (with exceptions)

    Volume curves should not decrease with age, except for thinning effects or harvest-related yield components.

  7. Curves are immutable after registration

    Once a curve is registered via ws3.forest.ForestModel.register_curve(), it is locked (curve.is_locked = True). Modifying a locked curve’s points will corrupt the model’s curve registry.

Optimization Invariants

  1. Flow constraints are feasible

    Flow constraints must allow at least one feasible solution. If min_ratio > max_ratio, the problem is infeasible.

  2. Area constraints don’t exceed available area

    Sum of max_area constraints across all periods must not exceed total available area.

  3. Harvest doesn’t exceed growth

    Harvest volume in any period should not exceed available volume from standing inventory.

  4. Problem variables are unique

    Variable names must be unique within a ws3.opt.Problem instance. Duplicate variable names will overwrite existing variables.

  5. Problem sense is consistent

    The objective sense (SENSE_MAXIMIZE or SENSE_MINIMIZE) is set at construction and should not change after variables and constraints are added.

Schedule Application Invariants

  1. Total area is conserved

    Total area across all development types should remain constant when actions are applied, unless transitions create or destroy area.

  2. Transitions are deterministic

    Given the same input state and actions, ws3.forest.ForestModel.apply_schedule() should produce the same output.

  3. Applied actions are tracked per period

    ForestModel.applied_actions[period][acode][dtype_key][age] stores the area applied. This structure must remain consistent after each schedule application.

  4. GreedyAreaSelector patches missing area

    When ws3.forest.ForestModel.apply_schedule() is called with recourse_enabled=True (default), the ws3.forest.GreedyAreaSelector automatically fills any missing area from operable age classes.

Spatial Allocation Invariants

  1. Raster dimensions match model horizon

    The ws3.spatial.ForestRaster creates one output GeoTIFF per (period, year_within_period) combination. The number of periods is determined by forestmodel.horizon.

  2. Pixel area is computed from raster resolution

    ForestRaster._pixel_area = d^2 * 0.0001 converts square meters to hectares based on the raster’s pixel dimension d.

  3. ForestRaster is single-use

    After calling ws3.spatial.ForestRaster.commit() or ws3.spatial.ForestRaster.cleanup(), the instance is expired. Further calls to ws3.spatial.ForestRaster.allocate_schedule() will raise RuntimeError.

Error Conditions

The following conditions indicate bugs or misconfiguration:

  • Development type with zero area in schedule

  • Action with no operability expressions defined

  • Curve with fewer than 2 data points after simplification

  • Negative volume or area values in output

  • Infeasible optimization (no solution found)

  • Locked curve modified after registration

  • Missing yield component curve referenced by development type

Validation

Use Running Optimization to check these invariants after running your model.