Software for spatially-explicit simulation of forest dynamics

Creating a new behavior

Part 2: Preparing to code

In this part, you get ready to write the behavior code. You define parameter inputs and write your header file.

Step 1: Define parameters

In this step you formally define your parameters in the parameter file DTD. First, you decide where you should define the parameters. You will probably add to an existing DTD, depending on what kind of behavior you are writing. It makes the most sense to class our example biomass calculation behavior as an analysis behavior, so we would add to the file "Analysis.dtd".

When you define parameters in a DTD, you are stating what their XML tag names are and in what format they will appear. There are two standard formats. When you define parameters with these formats, you will find it much easier to extract parameter data from the parameter file since there are already standard functions written to help you do this.

The first format is for a single piece of data, like this:

< li_julianDayGrowthStarts> 120< /li_julianDayGrowthStarts>

This particular parameter is the first day of the growing season, for light behaviors.

The second format is for species-specific parameters, like this:

< tr_canopyRadiusExponent>
< tr_creVal species="ACRU"> 1.0< /tr_creVal>
< tr_creVal species="ACSA"> 1.0< /tr_creVal>
< tr_creVal species="BEAL"> 1.0< /tr_creVal>
< /tr_canopyRadiusExponent>

This parameter is an equation exponent for tree allometry for each species.

Parameter file tag names must be unique. To help ensure this, XML tags for parameters have a prefix identifying to which group they belong. Follow the convention of the group to which your behavior belongs.

In our example behavior, we have two parameters: "slope of biomass equation (a)" and "exponent of biomass equation (d)". We would add the following to the file "Analysis.dtd":

< !-- slope of biomass equation (a)-->
< !ELEMENT an_slopeBiomassEq (an_sbeVal+)*>
< !ELEMENT an_sbeVal (#PCDATA)>
< !ATTLIST an_sbeVal species IDREF #REQUIRED>

< !-- exponent of biomass equation (d)-->
< !ELEMENT an_exponentBiomassEq (an_ebeVal+)*>
< !ELEMENT an_ebeVal (#PCDATA)>
< !ATTLIST an_ebeVal species IDREF #REQUIRED>

The name should be descriptive. I chose to prefix with "an_" because these are parameters for an "analysis" behavior.

Step 2: Write your header file

Writing the header file in C++ lets you make sure everything is in order one last time before you start to code.

Each new behavior is its own class. These behavior classes descend from clBehaviorBase. Pick your class name. By SORTIE-ND convention, it should start with a "cl" prefix. Usually you will name your files the same as your class, just ommitting the prefix. For our example, our class name is clSampleBehavior, so our filenames should be "SampleBehavior.h" and "SampleBehavior.cpp".

Use the documentation for clBehaviorBase to decide which functions you need to override.

I have written the sample behavior header here. (I've put in far more commenting than you would typically need, so you can use it as a guide. You can use any real SORTIE-ND header file as a guide to writing comments.)

Note on adding certain types of behaviors

Certain types of behaviors, such as growth and mortality behaviors, work together. They do this to be more efficient. Consider growth behaviors. There may be several in a single run, applied to different species and life history stages. If each growth behavior searched through all the trees looking for just the trees to which it applied, that would be much slower than if the trees were searched only once and each tree assigned to the correct growth behavior. That's why these types of behaviors work together. They designate an organizational object to do the tree searching and parameter management for them.

If you add or change a behavior that works together like this, your approach will be different than if you were writing a behavior that worked alone. Read this to get an idea of how the organization works, then pay close attention to class documentation to understand how to write that kind of behavior.

Behaviors in a submodel grouping are separate but coordinate their actions through an organizational object. The organizational object can recognize the behaviors it needs to organize (through a key character sequence in their namestrings) and places pointers to them in a table organized by species and type. For example, it would know that saplings of species 2 use behavior X for their growth.

A set of behaviors that needs to work together are all descended from a common base class that is in turn descended from clBehaviorBase. One of the behaviors is "hooked" to the organizational object. The behavior group base class overrides the functions of clBehaviorBase such as GetData() and Action(). When one of these functions is called, if a behavior is the "hooked" behavior, it hands the call over to the organizational object. Otherwise it ignores it. This system guarantees that the overall group of behaviors performs their work only once per timestep, no matter how many behaviors are in the group.

When it comes time to process trees in a timestep, the organizational object will simply do an "all" search (which is fairly efficient) and point each tree it gets to the appropriate Behavior object in its table.