News About Us Support Bugs & Tasks Links
dklfgj dfgjmpsdklg dfgjdsklfg dfjglksdfjg sdfgjokdfjg dfgjoksdfjg sdfgjksdlfgjsdf gdgjopksdfgj

Writing code for APSIM

RSS
The .NET languages are used to write models for APSIM. Either C#.NET or VB.NET can be used. What follows is a guide that shows the various language features that can be used to build an APSIM Model or script.

Static example

Consider the following code example that calculates a gross margin:

VB.NET
Imports System
Imports ModelFramework

Public Class GrossMargin
   Inherits Instance

   Private ResetBank As Boolean
   <Input()> Private Fertiliser As Double = 0
   <Input()> Private irrig_tot As Double = 0
   <Input()> Private Yield As Double = 0
   <Input()> Private grain_protein As Double = 0
   <Input()> Private StageName As String = ""
   <Input()> Private plant_status As String = ""
   <Input()> Private maxt As Double
   <Param()> Private NCost As Double = 0
   <Param()> Private NApplicationCost As Double = 0
   <Param()> Private WaterCost As Double = 0
   <Param()> Private Price As Double = 0
   <Param()> Private MinimumProtein As Double = 0
   <Param()> Private ProteinIncrement As Double = 0
   <Output()> <Units("$")> Public Bank As Double
   <[Event]()> Public Event NegativeBank()


   <EventHandler()> Public Sub OnPost()

      ' Reset the bank to zero the day after harvest.
      If (ResetBank) Then
         ResetBank = False
         Bank = 0
      End If

      ' Subtract fertiliser cost from the bank
      If (Fertiliser > 0) Then
         Bank = Bank - (NCost * Fertiliser + NApplicationCost)
      End If

      ' Subtract irrigation costs.
      If (irrig_tot > 0) Then
         Bank = Bank - WaterCost * (irrig_tot / 100)
      End If

      ' Add harvest return to the bank
      If (StageName = "harvest_ripe" Or plant_status = "dead") Then
         Dim FullPrice As Double = Price + (grain_protein - MinimumProtein) _
                                    * 2 * ProteinIncrement
         Bank = Bank + FullPrice * (Yield / 1000)
         ResetBank = True
      End If

      ' Publish event if bank falls below zero.
      If (Bank < 0) Then
         RaiseEvent NegativeBank()
      End If
   End Sub
End Class

C#.NET
using System;
using ModelFramework;

class GrossMargin : Instance
    {
    private bool ResetBank;
    [Input]              private double Fertiliser = 0;
    [Input]              private double irrig_tot = 0;
    [Input]              private double Yield = 0;
    [Input]              private double grain_protein = 0;
    [Input]              private string StageName = "";
    [Input]              private string plant_status = "";
    [Param]              private double NCost = 0;
    [Param]              private double NApplicationCost = 0;
    [Param]              private double WaterCost = 0;
    [Param]              private double Price = 0;
    [Param]              private double MinimumProtein = 0;
    [Param]              private double ProteinIncrement = 0;
    [Output][Units("$")] public double Bank;
    [Event]              public event NullTypeDelegate NegativeBank;
    [EventHandler]       public void OnProcess()
        {
        // reset the bank to zero the day after harvest.
        if (ResetBank)
            {
            ResetBank = false;
            Bank = 0;
            }
        // Subtract fertiliser cost from the bank
        if (Fertiliser > 0)
            Bank = Bank - (NCost * Fertiliser + NApplicationCost);
        // subtract irrigation cost.
        if (irrig_tot > 0)
            Bank = Bank - WaterCost * (irrig_tot / 100);
        // Add harvest returnto the bank
        if (StageName == "harvest_ripe" || plant_status == "dead")
            {
            double FullPrice = Price + (grain_protein - MinimumProtein)
                                        * 2 * ProteinIncrement;
            Bank = Bank + FullPrice * (Yield / 1000);
            ResetBank = true;
            }
        // Publish event if bank falls bellow zero.
        if (Bank < 0)
            NegativeBank.Invoke();
        }
    }

The above example calculates a simple gross margin based on a bank balance. It subtracts the cost of fertiliser and irrigation water from this bank balance and adds in income based on simulated yield and grain protein.

Two things to note when creating your models. Firstly, make sure your class is declared as public. Secondly, ensure that your class is not inside a namespace. This will help ensure that APSIM is able to locate your class.

APSIM relies heavily on reflection tags to analyse the model source code to locate variables and methods.
  • The Input tag denotes that a value for this variable needs to be supplied by APSIM. In the example, APSIM will locate a variable called Fertiliser in another model and retrieve its value. APSIM will do this every day. If APSIM cannot find the specified variable, then it will throw a fatal error. This error can be prevented by specifying the Input as optional with a true parameter e.g. Input(true).


  • The Param tag denotes that the variable is a parameter. Like the Input tag, APSIM will supply a value but only at the beginning of the simulation. APSIM looks for parameters in the XML configuration for this model. When the code is part of a script component, the parameter values will be on the "Properties" tab. If APSIM cannot find the specified parameter, then it will throw a fatal error. This error can be prevented by specifying the Param as optional with a true parameter e.g. Param(true). An alias can also be specified for the parameter. For example, Param("NitrogenCost") double NCost tells APSIM to look for a parameter called "NitrogenCost" rather than "NCost". This is sometimes useful.


  • The Output tag denotes a variable that APSIM can supply to other APSIM models when requested. Like parameters, an alias can be specified for the output e.g. Output("GrossMargin") public double Bank


  • The Units("$") tag supplies metadata to APSIM. The REPORT module writes the units to the output file.


  • The EventHandler tag signals to APSIM that the following method is an event handler that needs to be called whenever the APSIM event is published. The convention is that all handlers will have an On prefix. The name of the method (minus the On prefix) denotes the APSIM event name that will be trapped.


  • The Event tag signals that the following .NET event should be published to the whole APSIM simulation. Other components in APSIM will be able to subscribe to this event. The name of the event (NegativeBank in this example) will be the name of the event that APSIM sees. Events must be declared using one of the APSIM types. See the description of APSIM types below.

To retrieve a list of APSIM variables and events, use the "Variables" and "Events" components under the "Output file" in the simulation tree in the APSIM User Interface.

Dynamic Example

In a lot of cases, the static approach above provides sufficient functionality. In some cases though it is necessary to discover elements of a simulation as it is running and to then communicate directly with specific components. This section shows how to dynamically communicate with an APSIM simulation.

Links

Links are the starting point for access to all dynamic simulation functionality. To discover what other models are running in the same paddock as the model you're writing then insert the following line into the class variables section:

VB.NET
<Link()> Private MyPaddock As Paddock
C#.NET
[Link] private Paddock MyPaddock;

This will then give you a "variable" that represents the paddock that your component is running in. You can then call methods on the variable to determine the paddock name, loop through all sub (child) paddocks etc.

Some examples (all in VB.NET):

To loop through all sub paddocks:
For Each SubPaddock As Paddock In MyPaddock.SubPaddocks
   ' Do something with the SubPaddock variable.
Next

To loop through all crop models in the paddock:
For Each Crop As Component In MyPaddock.Crops
   ' Do something with the Crop variable.
Next

Once you have a paddock variable you can then get a variable that represents another model in the simulation:

To tell the fertiliser model to apply some fertiliser:
Dim Fertiliser As Fertiliser = MyPaddock.ComponentByType("Fertiliser")
Fertiliser.Apply(50, 10, "NO3_N")

To tell the wheat model to sow:
Dim Wheat As Wheat = MyPaddock.ComponentByType("Wheat")
Wheat.Sow("Hartog", 100, 30, 250, 0, 0, "", 0, 0, 0, 0, "", 0, 0, 0, 0, 0)


Links can also be used to retrieve links to other components. So rather than using the ComponentByType method in the above examples, a link can be set up to point to a specific component:
<Link()> Dim WheatModel As Wheat
<Link()> Dim SoilWaterModel As SoilWat
APSIM will look for these models within the current 'scope' of the component that is running. Currently, this will be in the current paddock or the parent paddock.

nb. In your Solution you need to add a reference to DotNetProxies.dll (under your Model directory). This will soon change to use CSDotNetProxies.dll.

Variables

Once you have a link or variable that represents another model in the simulation you can then query that model for the values of its variables.
<Link()> Dim SoilWaterModel As SoilWat
...
Dim sw As Single() = SoilWaterModel.sw

You can also set the values of some APSIM variables:
Dim sw As Single() = SoilWaterModel.sw
sw(1) = 0.29
sw(2) = 0.28
...
SoilWaterModel.sw = sw

SiteSearch, Login/Logout, Account Settings, Create a new Page