3.1. About the ws3 Package

The ws3 package is implemented using the Python programming language. ws3 is an aspatial wood supply model, which applies actions (and matching transitions) to development types, simulates growth, and tracks inventory area (by development type and by age class) at each time step. Aspatial models output aspatial activity schedules—each line of the output schedule specifies the stratification variable values (which constitute a unique key into the list of development types), the age class, the time step, the action code, and the area treated.

Because the model is aspatial, the area treated on a given line of the output schedule may not be spatially contiguous (i.e., the area may be geographically dispersed throughout the landscape). Furthermore, in the common case where only a subset of available area in a given development type and age class combination is treated in a given time step, an aspatial model provides no information regarding which spatial subset of available area is treated (and, conversely, not treated).

Some applications (e.g., linking to spatially-explicit or highly–spatially-referenced models) require a spatially-explicit activity schedule. ws3 includes a spatial disturbance allocator sub-module, which contains functions that can map aspatial multi-period action schedules onto a rasterized spatial representation of the forest. The spatial disturbance allocator is implemented using the rasterio package and is available as part of the ws3.spatial module. The area-based forest inventory input data for a ws3 model is typically compiled by aggregating stands in a spatially explicity forest inventory data layer (i.e., by combining spatially discontiguous stands into area bins, grouping by stratum and age class). The spatial disturbance allocation functions are effectively disaggregating the aspatial disturbance schedule, restoring the spatial dimensionality of the forest inventory data layer. These same functions are also capable of disaggregating ws3 output schedules from multi-year simulation periods into annual time steps (which is useful if you plan to pipe ws3 output to a downstream spatially-explicit model that also operates on a smaller simulation time step than your source ws3 model). Note that this disaggregation operation is always going to be somewhat problematic, because we are essentially fabricating details that are not represented in the source data.

ws3 uses a scripted Python interface to control the model, which provides maximum flexibility and makes it easy to automate modelling workflows. This ensures reproducible methodologies, and enables linking ws3 input or outout to other software packages to form complex modelling pipelines. The scripted interface also makes it relatively easy to implement custom data-importing functions, which makes it easier to import existing data from a variety of ad-hoc sources without the need to recompile the data into a standard ws3-specific format (i.e., importing functions can be implemented such that the conversion process is fully automated and applied to raw input data on the fly). Similarly, users can easily implement custom functions to re-format ws3 output data on the fly (either for static serialization to disk, or to be piped live into another process).

Although we recommend using Jupyter Notebooks as an interactive interface to ws3 (the package was specifically designed with an interactive notebook interface in mind), ws3 functions can also be imported and run in fully scripted workflow (e.g., non-interactive batch processes can be run in a massively-paralleled workflow on high-performance–computing resources, if available). The ability to mix interactive and massively-paralleled non-interactive workflows is a unique feature of ws3.

ws3 is a complex and flexible collection of functional software units. The following sections describe some of the main classes and functions in the package, and describe some common use cases, and link to sample notebooks that implement these use cases.

3.1.1. Overview of Main Classes and Functions

This section describes some of the main classes and functions that make up the ws3 pacakge.

The ws3.forest.ForestModel class is the core class in the package. This class encapsulates all the information used to simulate scenarios from a given dataset (i.e., stratified initial inventory, growth and yield functions, action eligibility, transition matrix, action schedule, etc.), as well as a collection of functions to import and export data, generate activity schedules, and simulate application of these schedules (i.e., run scenarios).

At the heart of the ForestModel class is a list of ws3.forest.DevelopmentType instances. Each DevelopmentType instance encapsulates information about one development type (i.e., a forest stratum, which is an aggregate of smaller stands that make up the raw forest inventory input data). The DevelopmentType class also stores a list of operable actions, maps state variable transitions to these actions, stores growth and yield functions, and knows how to grow itself when time is incremented during a simulation.

3.1.2. Common Use Case and Sample Notebooks

In this section, we assume an interactive Jupyter Notebook environment is used to interface with ws3.

A typical use case starts with creating an instance of the ForestModel class. Then, we need to load data into this instance, define one or more scenarios (using a mix of heuristic and optimization approaches), run the scenarios, and export output data to a format suitable for analysis (or link to the next model in a larger modelling pipeline).

The first step in a typical workflow is to run a mix of standard ws3 and custom data-importing functions. These functions import data from various sources, on-the-fly reformat this data to be compatible with ws3, and load the reformated data into the ForestModel instance using standard methods. For example, ws3 includes functions to import legacy Woodstock [1] model data (including LANDSCAPE, CONSTANTS, AREAS, YIELDS, LIFESPAN, ACTIONS, TRANSITIONS, and SCHEDULE section data), as well as functions to import and rasterize vector stand inventory data.

For example, one might define the following custom Python function in a Jupyter Notebook, to import data formatted for Woodstock.

from ws3.forest import ForestModel

def instantiate_forestmodel(model_name,
                            model_path,
                            horizon,
                            period_length,
                            max_age,
                            add_null_action=True):
   fm = ForestModel(model_name=model_name,
                    model_path=model_path,
                    horizon=horizon,
                    period_length=period_length,
                    max_age=max_age)
   fm.import_landscape_section()
   fm.import_areas_section()
   fm.import_yields_section()
   fm.import_actions_section()
   fm.add_null_action()
   fm.import_transitions_section()
   fm.reset_actions()
   fm.initialize_areas()
   fm.grow()
   return fm

The next step in a typical workflow is to define one or more scenarios. Assuming that we are using an optimization approach to harvest scheduling, we need to define an objective function (e.g., maximize total harvest volume) and constraints (e.g., species-wise volume and area even-flow constraints, ending standing inventory constraints, periodic minimum late-seral-stage area constraints) [2], build the optimization model matrix, solve the model to optimality [3].