8  ABE management library

8.1 Introduction

The iLand Management Library (ABE Library) simplifies the implementation of management activities in the iLand forest landscape model. It provides a collection of pre-defined management functions, each of which can be customized by adjusting key variables, making it easier for users to include management strategies such as harvests into their simulations.

This document introduces the concept of the ABE Library, explains its structure, describes how to use its features, and provides examples of implementation.

8.2 Key Concepts

8.2.1 What is the ABE Library?

The ABE Library is a collection of real-world forest management approaches, implemented in Javascript and built on top of the ABE system of iLand. It provides a simplified interface to ABE, hiding much of the underlying complexity, but retaining a lot of flexibility of ABE.

The library provides for each of the major management phases planting, thinning, and harvest a set of pre-defined management options, that can be further customized for particular applications. A user can compose an individual forest management “menu” by picking for each phase one of the provided options, say a regular planting, a selective thinning (with three cuts), and a clear cut (as the harvest “course”). Note, that the system is flexible: not all phases are required, and an actual STP can include further management activities.

< table with three columns for planting / thinning / harvest>

TODO: Link to the abe-library documentation

8.2.2 How to use the ABE library?

The ABE library builds upon (and is compatible with) the ABE system of iLand. The general setup of using the library is very similar to standard ABE procedures and could look like this:

  • a single Javascript file that contains the code to build the specific management programs for your purpose. This code uses the ABE library and defines one or several uniquely named stand treatment programs (STPs). This file is typically referenced from the project file (e.g. model.management.abe.file)

  • a single table (in CSV format) that defines for each stand the STP that should be used (model.management.abe.agentDataFile)

  • the stands on th landscape are defined via a standgrid - see x, y, z

The Javascript file includes the library (see below), and then uses the library to build the specific management for your purpose. How to do that is described below.

The ABE library itself is written in Javascript and consists of a few Javascript files. One copy of the library is “baked” into iLand and can simply be used by adding a single line to your code:


// load the copy of the library that is bundled with iLand
Globals.include(':/abe-lib/ABE-library.js');

It is straightforward to use your own copy of the library - this allows you to extend or customize the library to your own needs. You just need a copy of the library files (e.g. taken from https://github.com/edfm-lab/iland-model).

// Assume your copy of the library is in the "scripts" folder of your project:

Globals.include(Globals.path('scripts/abe-lib/ABE-library.js'));

Let’s define a simple management program, a “clearcut” system. We want it to include a tending, thinnings from below, and a clearcut as a final harvest. It could look like this:


Globals.include(':/abe-lib/ABE-library.js');

// Use the pre-defined management options provided by the library:
const Tending = lib.thinning.tending(); 
const ThinningFromBelow = lib.thinning.fromBelow();  
const Clearcut = lib.harvest.shelterwood(); 

// build a STP with the name 'ClearcutSystem'
lib.createSTP('ClearcutSystem', Tending, ThinningFromBelow, Clearcut);

/* note: this would be equivalent:
lib.createSTP('ClearcutSystem',
               lib.thinning.tending(),
               lib.thinning.fromBelow(),
               lib.harvest.shelterwood()); */

The next section looks at the case when we want to change the default behavior of the management options.

8.3 Options and default values

Each management option comes with a set of options that can be customized by the user. Options have default values which are used when no specific values are provided. Each management option of the library takes a options argument. This argument is itself a Javascript object (todo: Link to book) . Here is an example:

// create a planting activity for planting spruce. All else is at its default
// (e.g., the planting runs in year 1 after clearcut)
const planting = lib.planting.general({species='piab'});
// alternatively, create a planting element that runs only in year 10
const planting_later  = lib.planting.general({species='piab', schedule: 10});

Check the online-documentation of the library for the available options for each management option!

8.3.1 Scheduling

Most management options come with a default assumption about when to be executed; For example, the default schedule for planting is the beginning of the rotation. While the defaults work in some instances, in others you may want to adapt the schedule to your needs. All you need to do is to specify a (valid) schedule object as part of the options when calling the library function:

// the thinning should start when 25% of rotation age are reached
const thinning = lib.thinning.fromBelow( { schedule: { optRel: 0.25 }  });

8.3.2 Other frequently used options

8.3.3 How does it work under the hood?

A management function in the library (e.g. lib.thinning.tending()) takes optional parameters as input and creates one or multiple ABE management activities that implement the forest management. Most options can be heavily customized; you can modify target species, diameter thresholds, et cetera. After creating the desired options you can then build a ABE Stand treatment program by combining the individual objects that were created by the library functions (typically by using lib.createSTP() ). Note that you can mix and match activities from the library or hand-written ABE activities!

8.3.4 Introspection

Management options have a description property, that briefly describes its intention. For example, use console.log(<option>.description) . Furthermore, the library provides a human-readable description of a full STP:

// 
const my_stand_id = 1;
// Show a popup window with the description
Globals.alert( lib.formattedSTP( my_stand_id ) );

// alternatively: console.log( lib.formattedSTP(my_stand_id) );

8.3.5 Augmenting and further customizing of STPs

The result of a library call is (usually) one activity, or an array of activities. You can modify and update the activity before building a STP. For example, you can add additional properties or event handlers.

For example:

// we use "var" instead of const (not strictly necessary)
var thinning = lib.thinning.fromBelow();
// the result is a JS object which you can look into:
printObj( thinning ); 


thinning.onExecuted = function() { /* ... do some extra work 
                                     Be careful to avoid unintended effects! */ };

You can also mix and match activities that come from the library with “standard” activities by using lib.buildProgram():


const StructureThinning = lib.thinning.selectiveThinning({mode = 'dynamic'});
const stp = lib.buildProgram(StructureThinning);
// you can still modify the program, e.g, by adding activities!
stp['a_new_activity'] = { type: 'general', schedule: .... };

// use ABE function to add management
fmengine.addManagement(stp, "my_stp")

8.4 Structure of the ABE Library

8.4.1 Activity Sections

Management activities in the ABE Library are organized into descriptive sections (or sub-libraries), each representing a type of activity:

  • Planting: Activities related to establishing new trees.

  • Thinning: Activities focused on selective removal of trees to reduce competition.

  • Harvest: Activities for final timber extraction.

New sections can be added to accommodate additional management types (e.g. fire related activities as prescribed burning).

8.4.2 Build you own library functions

It is relatively straightforward to extend the library with your own custom functions. The best approach is to look for similar activities in the library, and copy and modify their code.

Each activity is a JavaScript function that:

  1. Accepts predefined variables that can be modified by the user

  2. Calls the ABE module to define the activity

  3. Returns an activity object

As an example, the following clearcut activity is part of the harvest section and takes the variables (id, schedule, dbhTreshold, and constraints) as potential input variables; it calls ABE module to create a scheduled activity act, and returns this activity:

lib.harvest.clearcut = function(options) {
    // 1. Default Options
    const defaultOptions = {
      id: 'Clearcut',
      schedule: { minRel: 0.8, optRel: 1, maxRel: 1.2, force: true }, 
      dbhThreshold: 0,
      constraint: undefined
    };
    // lib.mergeOptions() is a helper function to combine
    // defaults with options provided by the user
    const opts = lib.mergeOptions(defaultOptions, options || {});

    // define the ABE object, use the options defined above
    const act = { 
            type: "scheduled",
            id: opts.id,
            schedule: opts.schedule, 
            onEvaluate: function() {
              stand.trees.loadAll(`dbh>${opts.dbhThreshold}`);
              stand.trees.harvest();
              lib.activityLog(`Clearcut executed`);
                return true; 
            },
            onCreate: function() { this.finalHarvest = true; }
    };
    if (opts.constraint !== undefined) act.constraint = opts.constraint;
    
    act.description = `A clearcut operation, that removes all trees above a minimum diameter of ( ${opts.dbhThreshold} cm)).`;
  return act;  
};

New activities can be added by the user, by defining them within the JavaScript source code or directly in the within the abe file (see above).

8.4.3 Customizing Activities

Each activity can also be assigned a specific id and activity description, which can enhance clarity during simulations.

To modify the pre-defined variables, users can simply include the variables, when they call the function:

const Clearcut = lib.harvest.clearcut(); // Default version using the predefined variables
const Clearcut_modified = lib.harvest.clearcut({id: 'Clearcut of big trees', dbhThreshold: 30}); // Modified version, that harvests only all trees with a tree diameter above 30cm 

It is also possible, that activities incorporate stand-specific flags from the ABE agentDataFile (e.g., rotation period, target species) to dynamically adjust activities (see below).

8.5 Advanced features

8.5.1 Dynamic scheduling with Signals

To coordinate activities within an STP, the ABE Library uses a system of signals. A signal is a notification emitted by an activity that other activities can respond to. This enables activities to be dynamically timed relative to one another. For example:

  • A planting activity occurs 5 years before a final harvest.

  • A final harvest occurs 10 years after the last thinning.

Technically, the signal mechanism is inspired by the signal & slot mechanism used by the Qt library. In a nutshell:

  • signals can be “emitted” (sent) by any activity at any point in time

  • other activities may “listen” to signals; the right signal would then trigger the execution of an activity (note that multiple receivers are possible, and that signals without receivers are silently dropped)

  • signals are only visible (audible) for a single stand (and within a STP).

  • signals are identified by a signal name (character string), and can have a parameter (some information that can be used by the receiver)

To include this in an STP, users add the signals at a specific time during one activity (emitter), and include the signal option in the schedule of a different activity (listener).

lib.harvest.Activity1 = function(options) {
  // 1. Default Options
  const defaultOptions = { 
    id: 'Activity',
    schedule: { minRel: 0.7, optRel: 0.8, maxRel: 0.9, force: true }, // activity should start before the optimal rotation length is reached.
    EmittingSignal: undefined,
    constraint: undefined
    
  };
  
  const opts = lib.mergeOptions(defaultOptions, options || {});
  
  const act = { 
    type: "scheduled",
    id: opts.id,
    schedule: opts.schedule, 
    onExit: function() {
      stand.stp.signal(opts.EmittingSignal);
    },
    description: `Emit  signal ${opts.EmittingSignal} before the scheduled final harvest.`
  }
}

lib.harvest.Activity2 = function(options) {
  // 1. Default Options
  const defaultOptions = { 
    id: 'Activity',
    ReceivingSignal: undefined,
    waitTime: 10,
    constraint: undefined
    
  };
  
  const opts = lib.mergeOptions(defaultOptions, options || {});
  
  const act = {
        type: 'general',
        id: opts.id,
        schedule: { signal: opts.ReceivingSignal, wait=opts.waitTime},
        action: function() {
          // do something
        },

        description: `An activity that does something ${opts.waitTime} after the signal ${opts.EmittingSignal} occured`
    }
  }
}

8.5.2 Repeating activities

Using signals it is also possible to perform similar activities several times in a given interval. This is especially handy for repeating tending or thinning activities. Adding repeating activities is easy using a ABE-library internal helper function:

lib.repeater({ schedule: { signal: 'ReceivingSignal'},
               signal: 'EmittingSignal',
               interval: 5,
               count: 3})

This activity is performed, when it receives the signal 'ReceivingSignal'. Afterwards it emits the signal 'EmittingSignal' 3 times in a 5-year interval. Other activities like a thinning activity can thereby wait for the emitted signal and do their thinning activity every time they get the signal.

8.5.3 Dynamic species targets

“Dynamic species targets” is an approach that allows to define management programs with flexible target species (i.e., that can vary between stands and can be changed dynamically). In other words, you can define a general “scheme”, and define the details (e.g., which species to focus on in management) externally. This section describes one way how such a system can be implemented.

8.5.3.1 Definition of species targets

We define a list of species targets; targets can be one or multiple species and can have varying weights. Here is the definition:

  • saved as text table (.tsv)
  • hold dash-separated lists of species / proportions for different operations
targetid planting_species planting_intensity tending_species tending_intensity
pnv_1_1 fasy-pisy-acps 0.3-0.2-0.5 fasy-pisy-acps 0.3-0.2-0.5
pnv_1_2 quro-qupe-pisy-fasy 0.3-0.3-0.2-0.1 quro-qupe-pisy-fasy 0.3-0.3-0.2-0.2

8.5.3.2 Parsing / loading of species targets

see species_targets.js.

  • load the target file (described above)
  • parse and create objects in stand.obj that have the required format for the library (i.e., key-value pairs of species / intensities) (todo: link to doc of stand.obj)
  • functions to dynamically load the targets for the current stand (e.g. dynamicPlantingSelectivity()). Such a function would return the relevant information from the stand, e.g. the species proportions for planting.

8.5.3.3 Use of targets in activities

Some activities in the ABE-library can use target species dynamically. Oftentimes there is an option such as speciesSelectivity; for example the “dynamic planting” has such a option.

To use dynamic species targets for simulations, define activities:

 const planting = lib.planting.dynamic({ schedule: 1,          
      speciesSelectivity: function() {return dynamicPlantingSelectivity()} });

Here is what happens:

  • the stand gets a targetSpecies as a flag via the CSV file

  • the function for speciesSelectivity is evaluated on run time, and reads the target from the internal structure based on targetSpecies (defined as property of the stand)

  • the activity uses the targets, e.g. to plant specifically the target species

8.5.3.4 Other considerations

Flexibility: the organization of target species is separated from the library; the library can either get static targets, or, if they are dynamic, does not care from where they come (e.g. they could be generated on the fly, similarly to the spinup)

Timing of evaluation: this is a bit tricky, and different approaches can be implemented. Currently they vary:

  • selective thinning: the species targets are evaluated during the evaluate, i.e. during the simulation from C++ (doing something like FMSTP::evaluateJS(mSelectiveThinning.speciesProb))

  • planting: evaluation is during execution of the planting, but in JS with explicit call() commands.

8.5.4 Management History

the library includes the ability to store a management history for each stand, useful for SVD training down the road. Right now this is fully JS and stores the history in stand.obj. This requires that library-activities actively call a logging function, e.g. lib.activityLog('thinning_selection')`. Currently only activity / year are recorded, but extra information could be added (e.g. to give more context for SVD, could be target species).