7  Forest management

Forest management is a key component of a forest model, particularly when used in areas where forests are strongly affected by human interventions.

iLand includes two sub-systems that handle forest management:

The ABE model provides a framework to dynamically simulate adaptive forest management in multi-agent landscapes under changing environmental conditions. While the Rammer, W., Seidl, R., 2015 paper desribes ABE scientifically, this chapter provides more detail on both the concepts and the technical implementation.

From a technical point of view, forest management in iLand uses the integration of JavaScript with the model. Specifically, ABE uses a declarative programming paradigm. The section Section 6.3.4 provides more details.

7.1 Components of ABE

Agent and agent types
In ABE, all forest managers are represented by Agents. Agents are responsible for the management of a specific part of the landscape. Each Agent is of a certain Agent Type. An Agent Type corresponds to a manager archetype such as “farmer” or “forest company”, while an Agent represents an individual manager with specific properties, e.g., the agents’ age. The AgentType is created implicitly, when just an agent is created.

Stand treatment program (STP)
A stand treatment program consists basically of a collection of activity objects, which typically cover the silvicultural treatments to regenerate, tend and thin, and harvest a stand. In addition, the STP includes a definition of a time window for each activity, from which ABE autonomously derives the sequence and preliminary time of execution for each activity.

Management activities
Activities are the core element of forest management in ABE, as they are the elements that actually change the forest state in the ecosystem model by removing or planting trees. ABE provides a library of pre-defined activities that cover the most important aspects of forest management. Yet, the activity library can be easily extended by providing user-defined activities. Activities are fetched from the library by specifying the name of the activity type and by further defining the properties of the activity.

7.1.1 Defining a minimum ABE script

 var stp = {   U: 100,   
              thinning1: { /* defined below */ },   
              thinning2: {/* defined below */ },   
              clearcut: { /* defined below */ } 
 }  
fmengine.addManagement(stp, "program");

Note that if you do not define a unit and an agent, a default unit (with the name _unit and default agent (_agent) are automatically created by iLand.

7.1.2 Accessing elements of ABE

There are two central objects for accessing ABE, namely fmengine and stand. On the one hand, fmengine provides access to global properties of ABE, such as a list of all stands in the system. stand, on the other hand, allows to access properties of the stand that is currently processed. Linked to a stand are then the management unit , the stand treatment program (stp) or the current activity that is executed. For “normal” management activities you can just use stand, as ABE takes care of everything. You can, however, also manually override the current stand that ABE processes - this can be useful for debugging or advanced scripting tasks. To set the “focus” of ABE to a specific stand, you just have to set fmengine.standId to the numeric Id of this stand.

7.2 Inspection

7.2.1 Loop over all stands in a simulation

The property fmengine.standIds lets you access all stands that are set up with ABE (i.e., stands for which at least basic information is available in the file referenced by abe.agentDataFile) . In order to access objects related to this stand, you need to set fmengine.standId to the respective stand:

// print id and name of STP for each stand:
for (const id of fmengine.standIds) {
  // set the focus of ABE to this stand:
  fmengine.standId = id;
  // access data for the stand
  console.log('Stand ' + id + ' managed by ' + stand.stp.name );
}

7.2.2 Check all available STPs

A list of all STPs currently available can be queried via fmengine.stpNames. Note that since the names of STPs are unique, you can easily loop over all STPs:

// Show details for each STP.
for (var i=0; i<fmengine.stpNames.length; ++i) {
  let s = fmengine.stpNames[i];   
  // Show a pop-up window with a detailed report for each STP
  Globals.alert(fmengine.stpByName(s).info);
}

7.2.3 Assign a new STP to a stand

It’s always possible to assign a new STP to a stand or overwrite an already existing STP:

// create STPs, details omitted
fmengine.addManagement({ ... }, 'bau'); // default
fmengine.addManagement({ ... }, 'am1');

fmengine.standId = 1; 

console.log(stand.stp.name);  // -> "bau"
// set to a new STP identified by the name
stand.setSTP('am1');
console.log(stand.stp.name);  // -> "am1"

7.2.4 Dynamic update of a STP program

This is the case, when a STP changes during a simulation, i.e., when the definition of a STP changes dynamically. Note, that usually dynamic behavior is implemented within a static STP using conditional statements or similar. The spinup of iLand is an example that is changing STPs iteratively to improve the match of simulated with target forest state within a longer spinup simulation.

Caution

Updating a STP while it is currently executed is not possible! A workaround solution could look like this:

  • the STP that realizes that a new STP should be generated stores that information to a list (e.g. stand_to_process.push_back(stand.id)

  • Using iLand events you can trigger the creation of new STPs; for example, use a onYearEnd event:

    function onYearEnd() {
       while (stands_to_process.length>0) {
            var stand_id = stands_to_process.pop();
            console.log("*** processing stand" + stand_id );
            // create a STP programmatically. Remember the definition
            // of a STP is just a collection of objects (representing
            // activities)...
            const stp = createNewStp( stand_id ); 
            // update the STP for the stand (use unique STP names here)
            fmengine.updateManagement(stp, "mgmt_"+stand_id);
        }
        console.log('**** update stands finished *****');
    }

7.3 Working with activities

Activities are the main building blocks of forest management in ABE. A detailed description is on the wiki: https://iland-model.org/ABE+activities

Here we cover some practical aspects of working with activities.

7.3.1 Defining activities

Activities are JavaScript objects, that (can) include also code. Therefore there are several ways how activities can be created; a straightforward is to create an explicit object:

// (1): explicit object

// a simple activity that runs when the stand is 10 years old 
// and does not do much
const my_activity = {
   type: 'general', schedule: 10,
   action: function() { fmengine.log('Activity executed!')}

}

You can later use my_activity as part of the definition of a STP. A more advanced approach facilitates the fact that functions can generate activities and uses JavaScript closures:

// (2) An activity generator
function createActivity(max_dbh, age) {
   // the function is "hidden" here
   // and can encapsulate complex behavior
   // note that for instance 'max_dbh'
   // is still visbile in do_x() - this is 
   // a closure.
   function do_x() {
      // 
      fmengine.log(max_dbh); 
   }
   return {
     type: 'general', schedule: age,
     action: function() { do_x(); }
   }
}

const my_act = createActivity(20, 50);

Note that in many instances the simple approach is sufficing. Note further, that usually an activity can react to the specifics of a stand (see next section) without the need to create new activities for every stand.

7.3.2 Stand variables and stand flags

Activities are executed for a stand, and stand-specific information is available typically via the stand object. In this section, we introduce some patterns how this can be done in practice. Look at the following example that shows the action function of a (general) activity.

...
action: function() {
  // access properties of the stand, for instance volume
  // see also: https://iland-model.org/apidoc/classes/Stand.html
  if (stand.volume > 300 ) {
     // do something only if standing volume / ha is high enough
     const target_volume = stand.flag('target_volume');
     // ... use target volume
  }
}
...

Some statistics on the current state of the stand are available via the stand object, for example stand.volume is the standing volume (m3/ha) (see https://iland-model.org/apidoc/classes/Stand.html for more variables). More interesting is the stand.flag() in the example. Flags are stored as key-value pairs per stand; different stands can have different keys (or also the same keys) and can hold any (JavaScript) value, including JavaScript options. In the following, we discuss some example use of flags.

Example 1: Memory

Flags can be used as “memory”. For instance, a counter (per stand) for a repeating activity can be implemented via a flag, or flags can indicate that certain events happened already in the past (for example, a flag could indicate that a planting happened)

// counter example
{ type: 'general', schedule: {repeat: true, repeatInterval: 5 }, 
  action: function() {
            let event_happened = ....; // logic that evaluates event
            if (event_happened) {
               let cnt = stand.flag('my_counter');
               if (cnt > 3) {
                   // the "event" happened already three times: time to
                   // do something specific!
               }
               stand.setFlag('my_counter', cnt + 1);
            }
                 
  
          },
  onSetup: function() { 
      // the onSetup event is a good place to initialize the counter
      stand.setFlag('my_counter', 1);
      }
  }

Example 2: Pass information

Flags can pass information between different activities of a STP. Think of a situation where an (earlier) activity makes a decision e.g. which species to promote, and a later activity needs to know the species to further protect.

//  activity A (runs earlier)
...
action: function() {
    // decide which species to promote, for example
    const promoted = ['acps', 'frex']; // an array!
    stand.setFlag('promoted', promoted);
} ...

//  activity B (runs later)
...
action: function() {
    // get list of promoted species
    let p = stand.flag('promoted');
    // convert to syntax used by expression
    let save_string = `in(species, ${p.join(', ')})=false`;
    // will be: "in(species, acps, frex)=false"
    // do *not* load the promoted trees
    trees.load(save_string);
}

Example 3: External properties

Flags can be used to provide extra information to ABE; for example, you could provide a flag providing the distance to the next forest road - which could influence the actual management! While you can always programmatically set flags, e.g., read a table, set flags for each record (see Section 6.3.5.3), iLand provides a much simpler way, namely the ABE data file (agentDataFile). iLand reads data from some columns (e.g., id, stp, see https://iland-model.org/ABE+spatial+setup). If the file contains additional columns, then the values are automatically assigned to each stand as stand flags! See the following part of the agentDataFile, that defines “target species” for each stand (Note that you could also define two different STPs here!).

id stp species
1 bau abal, fasy
2 bau acps, frex

Now it is straight-forward to use this information:

...
action: function() {
    // read the value from the 'species' column
    let target_species = stand.flag('species'); 
    // let's assume we want to run over something
    // for each species in the list of species
    for (const s of target_species.split(',')) {
       fmengine.log(`processing species ${s}...`);
       // do something...
    }
    
}

7.4 Advanced topics

7.4.1 Working with patches

From a spatial perspective, ABE typically operates on stands, i.e., (pixelated) polygons which represent forest stands. Such stands are usually relatively homogeneous in composition and structure and are usually also the typical spatial entity of real-world forest management. As a user, you provide the logic how each single stand should be treated, and that logic is then applied to (potentially) multiple stands, one at a time. In many cases the management on a stand applies for the whole area of the stand - a clear-cut removes all trees from a stand just as a wall-to-wall planting affects the full area. The TreeList and SaplingList object of iLand are perfectly suited for such tasks. Not all management activities work that way - there is increasing interest and real-world application of more spatially explicit forest management. Such activities create gaps of different sizes to foster regeneration, harvest stands as sequence of strip cuts, or respond to disturbance by planting target species in crown openings created by disturbance. The patches feature of iLand is designed to aid the implementation of such spatially explicit activities.

A patch describes an area within a stand, and is basically a list of pixels on the stand grid (i.e., with 10m resolution). The pixels of a patch are usually, but not necessarily, adjacent, e.g., to form a gap in the forest. Each patch is identified by a numeric integer ID and an area, and a score (see below).

The patches property of a stand holds a collection of 0 or more patches, (the list-property) and provides a number of functions to create or modify patches. Internally, the object maintains a map of the stand and each 10m cell is either linked to none or exactly one patch. However, 10x10m pixels can be part of multiple patches at the same time!

7.4.1.1 Using patches to manipulate trees and saplings

Both trees and saplings provide a variable patch which can be used in every expression for filtering, loading etc. of tree- and sapling-lists. The value of patch is -1 if the entity is not located on a patch (or if there are no patches on a stand).

Listing 7.1: Code example for a clear cut on certain patches defined in ABE
// within ABE code:
// fill the trees list with trees standing on patches 11,12,...
stand.trees.load('patch > 10'); 

function clearPatch(patch_id) {
  stand.trees.load('patch=' + patch_id);
  stand.trees.kill();
  // use a SaplingList defined elsewere 
  // (e.g. using var saplist = new SaplingList)
  saplist.load('patch=' + patch_id);
  saplist.kill();
}
 clearPatch(4); // kill all trees and saps on that patch

Note, that you can use the patch property also outside of ABE, e.g., to visualize patches in the iLand user interface!

Patches can also be used to specify locations the as target area for planting activities in ABE. To do this, set the patches property of the planting activity. The property is either an expression that is evaluated for every 10m pixel of the stand (use the patch variable to indicate the patch), or a Javascript array of patch-Ids.

Listing 7.2: Example how to implement a planting activity on certain patches with ABE
// run a planting manually, i.e. from within some other ABE code:
// plant on all defined patches (i.e, with values > -1)
fmengine.runPlanting(stand.id, { 
   species: "abal", fraction: 0.4, height: 0.3, clear: true,  
   patches: 'patch>-1' });

// Example for a regular planting activity within the definition 
// use a Javascript array to specify the patches
**** not implemented :( *****

7.4.1.2 Creating patches

You can create patches using the functions provided by the patches object. It provides functions to create (a large number of) regularly spaced patches or to split a stand into several strips, e.g., for strip-cutting. Moreover, there are functions that allow to copy patches from another grid - this could be used for pre-defined patterns, but also to react to disturbances. Typically, those functions return multiple patches. Note that you need to explicitly update the list variable in order to effectively use the patches!

See the section on Patches in the iLand documentation and the examples below for details.

7.4.1.3 Manipulating lists of patches

The list of individual patch objects (each representing a single patch) can be accessed and manipulated from JavaScript via the stand.patches property in the context of ABE. Thus, you can apply advanced JavaScript logic to select / remove patches! For an overview of available options see here.

Note that it is not possible to change individual elements of the list, instead you have to set the list to a new set of patch objects! After changing the list, the internal map is updated and thus the new list is effectively used for subsequent use in expressions.

Listing 7.3: Simple example: how to work with the patches list
// Example 1: just create patches. 
// Note that stand.patches.list is set explictly.
stand.patches.list = stand.patches.createStrips(20, /*horizontal=*/ false);

// now use JavaScript to loop over the list, here use the forEach() function
stand.patches.list.forEach( (p) => console.log(p.id + " area: " + p.area) );

// the same thing, with another looping construct
for (p of stand.patches.list) {
  console.log(p.id + " area: " + p.area) );
}

// ....
// now use the patches, e.g. for harvesting in an activity
// 
stand.trees.load('patch=' + current_strip);
stand.trees.harvest();
current_strip = current_strip + 1;

The following example is more complex and outlines the steps required to find “optimal” patches for specific use. The steps are:

  • create a large number of candidate patches

  • evaluate each candidate patch (based on vegetation on the patch) and calculate a score based on some user-defined metric

  • select the “best” patches

Listing 7.4: Advanced example on how to work with patches
// Example 2: complex manipulation of patches

// Let us start with the top level view:
function findPatches() {
    // (1) create patches, here just strips
    stand.patches.list = stand.patches.createStrips(10,/*horiziontal=*/false);
    
    // (2) run the evaluation function for every patch
    stand.patches.list.forEach((p) => patchEvaluation(p));
    
    // (3) select patches based on the score provided in the evaluation 
    // function and *update* the patch list by setting the list property.
    stand.patches.list = topN(5);
}

// Ok, let us look at the evaluation function that is called for each patch
// evaluate trees and saplings and calculate a score for the patch
// The score is high on patches with only few trees, but abundant regeneration
function patchEvaluation(patch) {
    var score = 0;
    stand.trees.load('patch = ' + patch.id);
    let n_adult = stand.trees.count / patch.area;

    // calc number of (represented) saplings
    saplist.loadFromStand(stand.id, 'patch = ' + patch.id);
    let n_target = saplist.sum('min(nrep,10)', 'in(species, fasy, abal)') / 
    patch.area;
    
    console.log(`N-Adult: ${n_adult}, n-saps: ${saplist.count} n-good-saps: 
    ${n_target}`);
    
    // insert fancy scoring logic here :)
    score = Math.max(0, n_target - n_adult); // 
    patch.score = score;
}

// The last part is selecting a subset and actually *modifying* the patches 
// of the stand select the N patches with top scores
function topN(n) {
    // Note that sorting *directly* within stand.patches.list does not work. 
    // instead use an extra list:
    var slist = stand.patches.list;
    
    // sort score (descending) - this uses the standard sort() function 
    // of JavaScript arrays
    slist.sort( (a,b) => b.score - a.score);
    
    // reduce to the N patches with top score (again, slice() is a)
    slist = slist.slice(0, n);
    return slist;
}