In this tutorial we will explain how Plant Modelling Framework (PMF) functions work. We will use the wheat leaf photosynthesis model as an example.
Prerequisite: It is suggested you read how to build a model first.
There are multiple PMF leaf organs that different crops use. For this tutorial we will examine the organ Leaf in Leaf.cs. This is the organ that wheat uses. The other leaf organs work the same way with respect to photosynthesis so the information in this tutorial is relevant for other crop models in APSIM.
/// <summary>The photosynthesis</summary>
[Link(Type = LinkType.Child, ByName = true)]
IFunction Photosynthesis = null;
The leaf organ has a link to a photosynthesis model that is of type IFunction. Leaf has a single call to this Photosynthesis function to get the amount of dry matter fixed for the day (g/m2):
DMSupply.Fixation = Photosynthesis.Value();
The leaf organ knows nothing of the implementation of Photosynthesis other than it is an IFunction.
As we saw in the previous code block, the Photosynthesis IFunction has a Value method that returns a double. All PMF functions (models) implement IFunction and so must supply an implementation of this method.
public interface IFunction
{
/// <summary>Gets the value of the function.</summary>
double Value(int arrayIndex = -1);
}
Most crop models in APSIM use the same implementation of photosynthesis but parameterise it in different ways. The flexibility exists though for the model developer to use a different implementation.
To determine what implementation and parameterisation are used for a particular crop model:
The image above shows Photosynthesis selected (under wheat) and the tooltip showing RUEModel. This tells us the c# class being used is RUEModel. The image also shows us that there are 7 child models of RUEModel, RUE, FT, FN, FW, FVPD, FCO2 and RandInt.
The user interface wheat visualisation comes from the wheat.json file in the resources, which is another way of determining what functions (models) wheat photosynthesis is using.
The source code of the RUEModel looks like this:
public class RUEModel : Model, IFunction
{
/// <summary>The RUE function</summary>
[Link(Type = LinkType.Child, ByName = true)]
IFunction RUE = null;
/// <summary>The FCO2 function</summary>
[Link(Type = LinkType.Child, ByName = true)]
IFunction FCO2 = null;
/// <summary>The FN function</summary>
[Link(Type = LinkType.Child, ByName = true)]
IFunction FN = null;
/// <summary>The FT function</summary>
[Link(Type = LinkType.Child, ByName = true)]
public IFunction FT = null;
/// <summary>The FW function</summary>
[Link(Type = LinkType.Child, ByName = true)]
IFunction FW = null;
/// <summary>The FVPD function</summary>
[Link(Type = LinkType.Child, ByName = true)]
public IFunction FVPD = null;
/// <summary>The radiation interception function.</summary>
[Link(Type = LinkType.Child, ByName = true)]
public IFunction RadnInt = null;
/// <summary>Total plant "actual" radiation use efficiency.</summary>
[Units("gDM/MJ")]
public double RueAct
{
get
{
double RueReductionFactor = Math.Min(FT.Value(), Math.Min(FN.Value(), FVPD.Value()))
* FW.Value() * FCO2.Value();
return RUE.Value() * RueReductionFactor;
}
}
/// <summary>Daily growth increment of total plant biomass</summary>
/// <returns>g dry matter/m2 soil/day</returns>
public double Value(int arrayIndex = -1)
{
double radiationInterception = RadnInt.Value(arrayIndex);
if (Double.IsNaN(radiationInterception))
throw new Exception("NaN Radiation interception value supplied to RUE model");
if (radiationInterception < -0.000000000001)
throw new Exception("Negative Radiation interception value supplied to RUE model");
return radiationInterception * RueAct;
}
}
The above image shows the visualisation of the FN linear interpolation. To determine what the X variable is you need to click on XValue in the simulation tree:
The image above shows the model developer has specified [Leaf].Fn which means the FN linear interpolation will call the *Fn*property in Leaf to get the x value for the linear interpolation. The Fn property in leaf looks like this:
[Units("0-1")]
public double Fn
{
get
{
double f;
double functionalNConc = (CohortParameters.CriticalNConc.Value()
- CohortParameters.MinimumNConc.Value() * CohortParameters.StructuralFraction.Value())
* (1 / (1 - CohortParameters.StructuralFraction.Value()));
if (functionalNConc <= 0)
f = 1;
else
f = Math.Max(0.0, Math.Min(Live.MetabolicNConc / functionalNConc, 1.0));
return f;
}
}
The implementation of Fn then calls other functions: CriticalNConc, MinimumNConc and StructuralFraction which are all defined under Leaf.
This level of indirection (where one function calls another function which calls somewhere else) makes it very difficult to follow the logic of how photosynthesis works. The advantage though is that it is very flexible for the model developer to create models visually using the user interface. To help understand the PMF structure, it is recommended that you run the APSIM user interface showing the model structure beside the source code.