Table of Contents

How to add a new model in Capsis (part 2.)

This doc may help scientists or developers who have good skills in Java programming to begin implement their model by themselves. It can be particularly interesting for people from abroad. Remember that you can ask for support at starting time.

Prerequisite

This doc can be used to begin the integration of your model in Capsis. You must have read the part 1 before.

General introduction

To integrate a new model in Capsis, you have to add a new module. All modules have the same structure and can be run with the Capsis graphical user interface or in script mode without a user interface. The part 1 helped you build a skeleton module for your new module. This documentation will explain you the structure of this module and how you may modify it to implement your own growth model.

Architecture of Capsis

Capsis is a generic software built around the Capsis kernel. This kernel contains the main classes of the software (organized in several packages, including capsis.kernel, capsis.util, capsis.app…). Further details regarding this kernel and the general architecture of Capsis may be found in the Capsis training and are not presented here.

Fig 1. The Capsis architecture

Fig 1. The Capsis architecture

Figure 1. shows the Capsis kernel in blue. The Capsis modellers do not work in the Capsis kernel, nor in the green parts (the capsis pilots: main user interface, script pilot…). They mainly work in the red / orange parts: the Capsis modules. Some of them may create extensions or libraries for particular purposes.

Structure of a module

The structure of a module is represented in figure 2. The main classes of the module are in the red part. The Scene and Model classes are the most important ones (see lower for details). The orange part may contain additional classes if particular adaptations are needed for some pilots.

The classes in the module extend (object oriented programming extension, from the Java language, black arrows) some main classes in the Capsis kernel. These superclasses in the blue part contain some generic methods which will be redefined in the module subclasses. This way, Capsis knows what to ask to the module (initialize, process an evolution…) but does not need to know exactly what the module does in details when asked.

All the Capsis modules have the same structure. Capsis can run them in graphical user interface mode or in script mode (no user interface, for repetitions or long simulations).

Fig 2. The structure of a module

Fig 2. The structure of a module

What happens during a simulation

In figure 3, we show what happens when the model calculates new scenes for time t+1,… t+n. For each time, a new scene is created and is connected to a new Step in the Project. Each scene has a date.

Fig 3. Evolution

Fig 3. Evolution

The Capsis project manager (gui pilot) can show the whole project: one button for each step, showing the date of the underlying scene and a letter for the scenario (figure 4). This project manager is the main tool of the interactive pilot. It makes it possible to trigger new evolution stages, to remove steps or to open tools and charts on the selected current step.

Fig 4. The project in the Capsis project manager

Fig 4. The project in the Capsis project manager

Various charts may be opened afterward on the different steps of the simulation history (figure 5). Such charts are Capsis extensions. Some of them are generic and may be compatible with the new module directly or with little adaptation (in the module's MethodProvider, see below). Some specific charts may also be built and made compatible only for the new module (if very specific structure or in particular cases).

Fig 5. A chart was opened on step 2a

Fig 5. A chart was opened on step 2a

The main classes in the ''model'' package

You will find here the main things to know about the main classes of a Capsis module. Some complements will be found in the Capsis training you can download from the documentation page.

The Scene class

This class must extend the capsis.kernel.GScene class. Its main properties are a date and a reference to the Step it is linked to.

This is the description of your data structure. For example, for an individual based tree model, it may contain a list of trees. If you handle a tree description, you may have also a class for your trees describing their properties (e.g. dbh, height, crown if any…).

Fig 6. Scene description proposal

Fig 6. Scene description proposal

Capsis proposes few default data structures in the capsis.defaulttype package. These proposals are optional and may be ignored completely. However, it may save time to rely on them and some tools based on them may be directly compatible with the new module (viewers, intervention methods, graphs…).

The main proposals for the scene description are SimpleScene (e.g. for forestry stand-level models) and TreeList (when all the trees are individualized or for dbh classes, then the 'trees' also have a number). The TreeList contains Tree instances: the module may subclass Tree to add its own properties (see figure 6).

The Training module example relies on the TreeList default type.

/* 
 * The Training model.
 *
 * Copyright (C) December 2010: F. de Coligny (INRA).
 * 
 * This file is part of the Training model and is NOT free software.
 * It is the property of its authors and must not be copied without their 
 * permission. 
 * It can be shared by the modellers of the Capsis co-development community 
 * in agreement with the Capsis charter (https://capsis.cirad.fr/capsis/charter).
 * See the license.txt file in the Capsis installation directory 
 * for further information about licenses in Capsis.
 */
 
package training.model;
 
import capsis.defaulttype.DefaultPlot;
import capsis.defaulttype.TreeList;
 
/**	TraScene is the description of the Training scene.
 * 	It is a list of TraTree instances.
 * 
 *	@author F. de Coligny - December 2010
 */
public class TraScene extends TreeList {
 
	/**	Constructor
	 */
	public TraScene () {
		super ();
 
		// Create a default plot
		setPlot (new DefaultPlot ());
		getPlot ().setScene (this);
 
	}
 
	@Override
	public String toString () {
		return "TraScene_" + getCaption ();
	}
 
 
}

The Model class

It extends the capsis.kernel.GModel class.

This is the main class of your module, the first to be instanciated (i.e. its constructor is called first) when you choose your module at new project creation time in Capsis.

It contains methods to build the initial scene. This initial scene can be built by reading a file (e.g. a possibly specific inventory file for your module with a line for each tree in your forest / plantation) or by specific generation methods.

<note>Have a look at the method named loadInitStand () in TraModel, you will see it uses a class to load an initial file and create the initial scene.</note>

It also contains an initializeModel () method which is called at starting time once the first scene generated and before the project appears in the ProjectManager. This optional method makes it possible to generate additional data structures just once and at starting time (e.g. a description of a sky for a radiative balance algorithm). It is also possible to run a process on the first scene (e.g. run the initial radiative balance process).

The model class also contains the main method of your model: the processEvolution method. This processEvolution (Step, EvolutionParameters) method describes the process of jumping from time i to time (i+n). For instance, it may make the present trees grow, some of them die and new ones appear by regeneration. The Step parameter is the initial step on which the evolution starts (time 0) and the EvolutionParameters contain the target to be reached, depending on the module implementation (e.g. number of years or target date).

An evolution stage generally results in the creation of several steps in the project, linked after the initial Step, depending on the module timeStep and the requested target variable (e.g. yearly time step + 10 years evolution → 10 new Steps).

/* 
 * The Training model.
 *
 * Copyright (C) December 2010: F. de Coligny (INRA).
 * 
 * This file is part of the Training model and is NOT free software.
 * It is the property of its authors and must not be copied without their 
 * permission. 
 * It can be shared by the modellers of the Capsis co-development community 
 * in agreement with the Capsis charter (https://capsis.cirad.fr/capsis/charter).
 * See the license.txt file in the Capsis installation directory 
 * for further information about licenses in Capsis.
 */
 
package training.model;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
 
import jeeb.lib.util.ProgressDispatcher;
import jeeb.lib.util.StatusDispatcher;
import jeeb.lib.util.TicketDispenser;
import jeeb.lib.util.Translator;
import jeeb.lib.util.Vertex3d;
import capsis.defaulttype.TreeList;
import capsis.kernel.EvolutionParameters;
import capsis.kernel.GModel;
import capsis.kernel.GScene;
import capsis.kernel.InitialParameters;
import capsis.kernel.MethodProvider;
import capsis.kernel.Step;
import capsis.util.GTreeDbhComparator;
 
 
/**	The main class for Training.
 * 	It usually contains methods to create the initial scene in the project. 
 * 	This can be done by loading a file or by specific generation methods.
 * 	It contains an initializeModel (InitialParameters p) method that is 
 * 	run at project initialisation time. It also contains the main 
 * 	processEvolution (Step s, EvolutionParameters p) method to calculate 
 * 	the evolution of the scene over time, i.e. the growth or dynamics model.
 * 
 *	@author F. de Coligny - December 2010
 */
public class TraModel extends GModel {
 
	// A way to get unique ids for the trees
	private TicketDispenser treeIdDispenser;
	private Random random;
 
 
	/**	Constructor
	 */
	public TraModel () throws Exception {
		super ();
		treeIdDispenser = new TicketDispenser ();
		random = new Random ();
 
		setSettings (new TraInitialParameters());
 
	}
 
 
	/**	Creates a MethodProvider for the module.
	 */
	protected MethodProvider createMethodProvider () {
		return new TraMethodProvider ();
	}
 
 
	/**	Convenient method.
	 */
	public TraInitialParameters getSettings () {
		return (TraInitialParameters) settings;
	}
 
 
	/**	Loads the inventory file with the given fileName.
	 * 	Creates TraTree instances and adds them into the initial scene.
	 */
    public GScene loadInitStand (String fileName) throws Exception {
    	TraInventoryLoader l = new TraInventoryLoader (fileName);
    	TraScene  initScene = (TraScene) l.load(this);
		return initScene;
 
	}
 
 
	/** This method is called for the first scene of the project 
	 * 	at project creation time.
	 */
	@Override
	public Step initializeModel (InitialParameters p) {
		// Optional process at project creation time
		// -> nothing here
 
		// Create a rectangular plot
		TraScene initScene = (TraScene) p.getInitScene();
		initScene.createPlot (this, 10);
 
		return p.getInitScene().getStep();
	}
 
 
	/**	This method is called when a project is loaded from disk.
	 */
	@Override
	protected void projectJustOpened () {
		// Optional process at project opening time
		// -> nothing here
	}
 
	/**	 Evolution loop.
	 */
	@Override
	public Step processEvolution (Step step, EvolutionParameters p) throws Exception {
 
		TraEvolutionParameters ep = (TraEvolutionParameters) p;
 
		int originYear = step.getScene ().getDate ();
		int numberOfYears = ep.getNumberOfYears ();
 
		ProgressDispatcher.setMinMax (0, numberOfYears);
 
		for (int k = 1; k <= numberOfYears; k++) {
			int newYear = originYear + k;
			StatusDispatcher.print (Translator.swap ("TraModel.evolutionForYear") + " " + newYear);
			ProgressDispatcher.setValue (k);
 
			// Create the new scene by partial copy
			TraScene newScene = (TraScene) step.getScene ().getEvolutionBase ();	
			newScene.setDate (newYear);
 
			// Make all the trees grow, add them in newScene
			Collection trees = ((TraScene) step.getScene ()).getTrees ();
			for (Iterator i = trees.iterator (); i.hasNext ();) {
				TraTree t = (TraTree) i.next ();
 
				TraTree newTree = t.processGrowth (getSettings ().growthP1, 
						getSettings ().growthP2, 
						getSettings ().growthP3, 
						getSettings ().growthP4);
				newScene.addTree (newTree);
			}
 
			// Regeneration
			if (getSettings ().dispersionRegeneration) {
				disperse (newScene);
			} else {
				randomRegeneration (newScene);
			}
 
			// Mortality
			runMortality (newScene);
 
			String	reason = "Evolution to year " + newYear;  // a free String
 
			Step newStep = step.getProject ().processNewStep (step, newScene, reason);
			step = newStep;
		}
		StatusDispatcher.print (Translator.swap ("TraModel.evolutionIsOver"));
		ProgressDispatcher.stop ();
 
		// Return the last Step
		return step;
 
	}
 
	/**	Mortality
	 */
	private void runMortality (TraScene scene) {
		List copy = new ArrayList (scene.getTrees ());
		GTreeDbhComparator comparator = new GTreeDbhComparator (true);
		Collections.sort (copy, comparator);
		TraTree biggestTree = (TraTree) copy.get (copy.size () - 1);
		TraTree smallestTree = (TraTree) copy.get (0);
		double maxDbh = biggestTree.getDbh () / 100d / 2d;  // m
		double minDbh = smallestTree.getDbh () / 100d;  // m
//		System.out.println("TraModel minDbh (m) = "+minDbh+" maxDbh (m) = "+maxDbh);
 
		double smallestMotalityProba = 0.2;
		double biggestMortalityProba = 0.01;
 
		for (Iterator i = copy.iterator (); i.hasNext ();) {
			TraTree t = (TraTree) i.next ();
 
			// Calculate mortality proba for this tree
			double d = t.getDbh () / 100d;  // [minDbh, maxDbh] (m)
			double p = (d - minDbh) / (maxDbh - minDbh);  // [0, 1]
			p = (p * (biggestMortalityProba - smallestMotalityProba)) + smallestMotalityProba;  // [smallestMotalityProba, biggestMortalityProba]
 
			if (p > 1) {p = 1;}
			if (p < 0) {p = 0;}
 
//			System.out.println("TraModel dbh = "+t.getDbh ()+" p = "+p);
 
			double r = random.nextDouble ();  // [0, 1[
 
			boolean dead = false;
			if (d < maxDbh) {
				if (r < p ) {dead = true;}
			} else {
				if (r < biggestMortalityProba) {dead = true;}  // big trees die less easily
			}
 
			if (dead) {
				// remove the tree
				((TreeList) scene).removeTree (t);
				// remember this tree has been killed
				((TreeList) scene).storeStatus (t, "dead");
 
			}
		}
 
	}
 
	/**	A dispersion function
	 */
	private void disperse (TraScene scene) {
		int max = getSettings().regenerationMax;
		double regenerationHeight = getSettings ().regenerationHeight;
		Collection copy = new ArrayList (scene.getTrees ());
		for (Object o : copy) {
			TraTree t = (TraTree) o;
			if (t.getHeight () < regenerationHeight) {continue;}  // too small, next tree
 
			double x0 = t.getX ();
			double y0 = t.getY ();
			int n = random.nextInt (max + 1);  // number of children
			for (int i = 0; i < n; i++) {
				double d = random.nextDouble () * 2 * t.getCrownRadius();
				double azimuth = random.nextDouble () * 2 * Math.PI;
 
				double x = x0 + Math.cos (azimuth) * d;
				double y = y0 + Math.sin (azimuth) * d;
				double z = 0;
				int id = treeIdDispenser.getNext();
				int age = 1;
				double height = 1.3;
				double dbh = 3;
				double cbh = 0.7;
				double cr = 0.5;
 
				TraTree newTree = new TraTree (id, scene, age, height, dbh, cbh, cr, x, y, z);
				scene.addTree (newTree);
			}
 
		}
 
	}
 
	/**	A simple regeneration function
	 */
	private void randomRegeneration (TraScene scene) {
		int max = getSettings().regenerationMax;
		int nTrees = 0;
		double regenerationHeight = getSettings ().regenerationHeight;
		for (Object o : scene.getTrees ()) {
			TraTree t = (TraTree) o;
			if (t.getHeight () >= regenerationHeight) {nTrees++;}  // >= regenerationHeight (m)
		}
 
		int n = nTrees * random.nextInt (max + 1);
 
		double xSize = scene.getXSize();
		double ySize = scene.getYSize();
		Vertex3d origin = scene.getOrigin();
 
		for (int i = 0; i < n; i++) {
			double x = origin.x + random.nextDouble () * xSize;
			double y = origin.y + random.nextDouble () * ySize;
			double z = 0;
			int id = treeIdDispenser.getNext();
			int age = 1;
			double height = 1.3;
			double dbh = 3;
			double cbh = 0.7;
			double cr = 0.5;
 
			TraTree t = new TraTree (id, scene, age, height, dbh, cbh, cr, x, y, z);
			scene.addTree (t);
		}
 
	}
 
	/**	Post intervention processing 
	 */
	@Override
	public void processPostIntervention (GScene newScene, GScene oldStand) {
		// Add here if needed an optional process to be run after intervention
		// -> nothing here
	}
 
	public TicketDispenser getTreeIdDispenser() {
		return treeIdDispenser;
	}
 
 
}

The InitialParameters class

It implements the capsis.kernel.InitialParameters interface.

It contains the parameters of your model. They are set at the begining of a simulation (at project creation time) and linked to the model class so they can be read at any time. They should not change during the simulation.

These parameters all have default values and must all be set at simulation start time.

<note>The InitialParameters object may contain some parameters values for your model equations.</note>

The TraInitialParameters which code is below was adapted to be used with the AutoUIs (see the documentation page). This means an automatic dialog box can be created to get user entries for the @Editor tagged variables.

/* 
 * The Training model.
 *
 * Copyright (C) December 2010: F. de Coligny (INRA).
 * 
 * This file is part of the Training model and is NOT free software.
 * It is the property of its authors and must not be copied without their 
 * permission. 
 * It can be shared by the modellers of the Capsis co-development community 
 * in agreement with the Capsis charter (https://capsis.cirad.fr/capsis/charter).
 * See the license.txt file in the Capsis installation directory 
 * for further information about licenses in Capsis.
 */
 
package training.model;
 
import java.util.ArrayList;
import java.util.List;
 
import sapin.model.SapStand;
 
import jeeb.lib.util.Log;
import jeeb.lib.util.annotation.Ignore;
import jeeb.lib.util.autoui.annotations.AutoUI;
import jeeb.lib.util.autoui.annotations.Editor;
import jeeb.lib.util.autoui.editors.FilenameEditor;
import capsis.kernel.AbstractSettings;
import capsis.kernel.GModel;
import capsis.kernel.GScene;
import capsis.kernel.InitialParameters;
import capsis.kernel.automation.Automatable;
 
 
/**	TraInitialParameters are the initial settings for Training. 
 * 	They are set at the project initialization stage and then stay 
 * 	unchanged during all the simulations within this project. 
 * 
 * 	@author F. de Coligny - December 2010
 */
@AutoUI(title="Training : initialisation", translation="TraLabels")
public class TraInitialParameters extends AbstractSettings 
		implements InitialParameters, Automatable {
 
	// This class uses the AutoUIs annotations
 
	// Default values
	public static final double GROWTH_P1 = 4d;
	public static final double GROWTH_P2 = 0.2d;
	public static final double GROWTH_P3 = 0.2d;
	public static final double GROWTH_P4 = 0.1d;
 
	@Editor(group="1-inventory", editorClass=FilenameEditor.class)
	public String inventoryFileName;
 
	@Editor(group="2-growthVariables", min=0)
	public double growthP1 = GROWTH_P1;
 
	@Editor(group="2-growthVariables")
	public double growthP2 = GROWTH_P2;
 
	@Editor(group="2-growthVariables")
	public double growthP3 = GROWTH_P3;
 
	@Editor(group="2-growthVariables")
	public double growthP4 = GROWTH_P4;
 
	@Editor(group="3-regeneration")
	public int regenerationMax = 20;
 
	@Editor(group="3-regeneration")
	public boolean dispersionRegeneration = false;
 
	@Editor(group="3-regeneration")
	public double regenerationHeight = 3;  // m
 
 
 
 
	@Ignore 
	private TraScene initScene;
 
 
 
	/** Default constructor.
	 */
	public TraInitialParameters () throws Exception {}
 
 
	/** A constructor for scripts.
	 */
	public TraInitialParameters (String inventoryFileName) throws Exception {	
		this.inventoryFileName = inventoryFileName;
	}
 
 
    @Override
	public GScene getInitScene () {return initScene;}
 
 
	/** Builds the initial scene.
	 */
	@Override
	public void buildInitScene(GModel model) throws Exception {
		model.setSettings (this);
 
		// Load the inventory file
		try {
			initScene = (TraScene) ((TraModel) model).loadInitStand (inventoryFileName);
 
		} catch (Exception e) {
			Log.println (Log.ERROR, "TraInitialParameters.buildInitScene ()", "Error during inventory load", e);
			throw new Exception ("Error during inventory load, see Log file", e);
		}
 
	}
 
}

The EvolutionParamaters class

It implements the capsis.kernel.EvolutionParameters interface.

This is the description of the parameters needed to run an evolution stage. The most common is a number of years (if the module timestep is a year), but it may be completely different.

/* 
 * The Training model.
 *
 * Copyright (C) December 2010: F. de Coligny (INRA).
 * 
 * This file is part of the Training model and is NOT free software.
 * It is the property of its authors and must not be copied without their 
 * permission. 
 * It can be shared by the modellers of the Capsis co-development community 
 * in agreement with the Capsis charter (https://capsis.cirad.fr/capsis/charter).
 * See the license.txt file in the Capsis installation directory 
 * for further information about licenses in Capsis.
 */
 
package training.model;
 
import capsis.kernel.EvolutionParameters;
import capsis.kernel.automation.Automatable;
 
 
/**	TraEvolutionParameters is a description of the parameters needed to run  
 * 	an evolution stage in the Training. 
 * 	It can be for example a target date, or a target age for a plantation, or 
 * 	a number of time steps.
 * 
 * 	@author F. de Coligny - December 2010
 */
public class TraEvolutionParameters implements EvolutionParameters, Automatable {
 
	private int numberOfYears;  // number of years for an evolution process
 
 
	/**	Constructor
	 */
	public TraEvolutionParameters (int numberOfYears) {
		this.numberOfYears = numberOfYears;
 
	}
 
	public int getNumberOfYears () {return numberOfYears;}
 
}

The module's MethodProvider

It implements some 'somethingProvider' interfaces generally located in the capsis.util.methodprovider package.

Fig 7. N / Time asks the TraMethodProvider

Fig 7. N / Time asks the TraMethodProvider

It can make the link with external tools which will check if it implements some expected methods. For example, if TraMethodProvider implements NProvider, the N / Time graph will be compatible and will use the related getN (GScene stand, Collection trees) method to get the values to be ploted at each date.

/* 
 * The Training model.
 *
 * Copyright (C) December 2010: F. de Coligny (INRA).
 * 
 * This file is part of the Training model and is NOT free software.
 * It is the property of its authors and must not be copied without their 
 * permission. 
 * It can be shared by the modellers of the Capsis co-development community 
 * in agreement with the Capsis charter (https://capsis.cirad.fr/capsis/charter).
 * See the license.txt file in the Capsis installation directory 
 * for further information about licenses in Capsis.
 */
 
package training.model;
 
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
 
import jeeb.lib.util.Log;
import capsis.defaulttype.Tree;
import capsis.kernel.GScene;
import capsis.util.GTreeDbhComparator;
import capsis.util.methodprovider.DgProvider;
import capsis.util.methodprovider.NProvider;
 
 
/**	The method provider for Training.
 *	Contains calculation methods that can be detected by external 
 *	tools. Some extensions may evaluate their compatibility
 *	with Training by requesting this object.
 *
 * 	@author F. de Coligny - December 2010
 */
public class TraMethodProvider implements DgProvider, NProvider {
 
 
	/**	Number of trees.
	*/
	public double getN (GScene stand, Collection trees) {
		if (trees == null) {return -1;}
		return trees.size ();
	}
 
	/**	Dg: Diameter of the mean tree (cm) : sqrt (Sum(d^2) / n).
	 */
	public double getDg (GScene stand, Collection trees) {
		try {
			if (trees == null) {return -1d;}
			if (trees.isEmpty ()) {return 0d;}  // if no trees, return 0
 
			double cum = 0;
			double Dg = 0;
			for (Iterator i = trees.iterator (); i.hasNext ();) {
				Tree t = (Tree) i.next ();
				double d = t.getDbh ();
				cum += d * d;
			}
			if (trees.size () != 0) {
				Dg = Math.sqrt (cum / trees.size ());
			}
			return Dg;
		} catch (Exception e) {
			Log.println (Log.ERROR, "TraMethodProvider.getDg ()", 
					"Error while computing Dg", e);
			return -1d;
		}
	}
 
}