Runtime Invariants
This page documents invariants that must hold during ws3 execution.
Model State Invariants
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 inForestModel.dtypeswill cause undefined behavior.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.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.
Area is non-negative
All area values stored in
DevelopmentType._areasmust be >= 0. Negative areas indicate a bug.Ages are non-negative integers
Stand ages must be non-negative integers. The
ageslist inForestModelislist(range(max_age + 1)).Volume curves are monotonically non-decreasing (with exceptions)
Volume curves should not decrease with age, except for thinning effects or harvest-related yield components.
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
Flow constraints are feasible
Flow constraints must allow at least one feasible solution. If
min_ratio > max_ratio, the problem is infeasible.Area constraints don’t exceed available area
Sum of max_area constraints across all periods must not exceed total available area.
Harvest doesn’t exceed growth
Harvest volume in any period should not exceed available volume from standing inventory.
Problem variables are unique
Variable names must be unique within a
ws3.opt.Probleminstance. Duplicate variable names will overwrite existing variables.Problem sense is consistent
The objective sense (
SENSE_MAXIMIZEorSENSE_MINIMIZE) is set at construction and should not change after variables and constraints are added.
Schedule Application Invariants
Total area is conserved
Total area across all development types should remain constant when actions are applied, unless transitions create or destroy area.
Transitions are deterministic
Given the same input state and actions,
ws3.forest.ForestModel.apply_schedule()should produce the same output.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.GreedyAreaSelector patches missing area
When
ws3.forest.ForestModel.apply_schedule()is called withrecourse_enabled=True(default), thews3.forest.GreedyAreaSelectorautomatically fills any missing area from operable age classes.
Spatial Allocation Invariants
Raster dimensions match model horizon
The
ws3.spatial.ForestRastercreates one output GeoTIFF per (period, year_within_period) combination. The number of periods is determined byforestmodel.horizon.Pixel area is computed from raster resolution
ForestRaster._pixel_area = d^2 * 0.0001converts square meters to hectares based on the raster’s pixel dimensiond.ForestRaster is single-use
After calling
ws3.spatial.ForestRaster.commit()orws3.spatial.ForestRaster.cleanup(), the instance is expired. Further calls tows3.spatial.ForestRaster.allocate_schedule()will raiseRuntimeError.
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.