Flexible Cropping Sequences

The previous tutorial section introduced how to represent predetermined crop rotations in APSIM. However, whenever cropping system research aims to simulate likely farmer behaviour in a forward looking manner, the simulation of a fixed, predetermined crop rotation is hardly realistic. Instead, farm managers often have a couple of typical cropping strategies, between which they will shift quite flexibly based on changing context conditions. For example, in south-east Queensland, farmers can flexibly change between summer- and winter-dominant cropping patterns based on seasonally changing water availability and soil moisture storage from preceding months. Another driver of rapid shifts in crop choice can be pest outbreaks (such as the transition out of maize following the introduction and spread of fall armyworm in Australia) or pronounced and sudden changes in price incentives (for example, as a consequence to the 30% tariff imposed on chickpea and lentil imports into India in 2017).

In this section, we will explore how to define such flexible cropping sequences that dynamically determine crop choice based on external conditions.

Case Study Context

This tutorial focusses on representing a flexible cropping strategy within the exemplary agro-ecological context of the Darling Downs growing region. This production region in South-East Queensland (Australia) is characterised by two major crop growing periods, summer and winter. The region is characterised by a summer-dominant rainfall pattern and most farmers correspondingly practice a primarily summer-dominant cropping strategy. However, over the last two decades, the planted area of winter crops has steadily increased. Many farmers have adopted highly climate-responsive, opportunistic cropping strategies, implementing a high cropping intensity whenever adequate water resources are available, regardless of the season. Evidence of this flexible alternation between summer- and winter-dominant cropping is visible even in aggregate government statistics on the seasonal planted area across Queensland (ABARES, 2024).

In the following, we will aim to represent such an opportunistic cropping strategy within the RotationManager of APSIM. It is important to remember, that this scenario serves as an illustrative example only. Our core focus is to demonstrate a workflow in APSIM that can readily be transferred to other agro-ecological settings and applications. For example, in production regions where a wheat-dominant crop rotation is largely predetermined, such as in parts of Western Australia, there is little value in configuring a RotationManager that represents flexible crop choices.

In such contexts, the design logic presented in this tutorial can instead be applied to other dynamic aspects of the local cropping system, for example, simulating flexible soil management decisions rather than flexible crop choices. This could include, for instance, the occasional application of soil amendments such as lime when selected soil properties fall below specified thresholds.

Cropping Scenario

As flexible cropping scenario, we consider a hypothetical broadacre farmer in the Darling Downs region that is generally willing to grow both summer and winter crops, depending on seasonal conditions. As summer crops, the farmer commonly grows sorghum and mungbean, while as winter crops, wheat and chickpea are readily available options.

To keep some realism, but avoid some of the complicated detail that will be involved in simulating a real case study farm, we will consider here that the farmer determines the crop choice on only two subsequent decisions:

  • Absolute water availability

    During the sowing window, a threshold for water availability determines if a crop is sown at all or if the field is left fallow. This is exactly similar to the decision logic presented in the previous tutorial section on basic crop rotations.

  • Crop sequence: Disease pressure and nitrogen management

    The continuous cropping of cereals or legumes can lead to increased disease pressure and suboptimal nitrogen management. Here we will implement the following simple rule: If the previous two cultivated crops were cereals, the next crop must be a legume. In all other cases, the next crop must be a cereal. Thereby, we will simply consider the last cultivated crops, regardless of whether the plot has intermittently been left fallow.

The above rules have been chosen on purpose for this tutorial, as they demonstrate how to implement conditions that depend on both (i) the progression of the simulation (i.e., the previous crops grown), and (ii) the prevailing environmental conditions (i.e., water availability). While the specific rules that you will require for your own study cases are likely to differ, many cases can be represented by the general logic presented here.

Crop Sequence Diagram

The first step in implementing the above flexible cropping strategy is to represent the desired crop sequences within the RotationManager. In other words, we have to generate a suitable bubble chart.

As a starting point for this tutorial section, please utilise the following APSIMX file: CropRotation_flexible_start.apsimx. Using an existing APSIMX file as the starting point, allows to skip over some aspects already covered in previous tutorial sections and instead focus on the new aspects relevant for flexible cropping sequences. If you compare the simulation tree in the provided APSIMX file with the one shown in the previous tutorial section on basic crop rotations (CropRotation_basic.apsimx), you will notice that there are a number of modifications and updates already done:

  • Crop models for a total of four crops are included in the simulation tree (sorghum, mungbean, wheat, chickpea).

  • Draft manager scripts for sowing and harvesting have been created for each crop (as simple adaptations of the previously used manager scripts in CropRotation_basic.apsimx and without any thorough update to our new simulation scenario).

  • Fertiliser manager scripts have been added for each crop.

  • Parameters for the soil-crop interactions, specifically the Plant Available Water Capacity (PAWC), have been added for the new crops (under the Soil node HRS -> Physical).

  • The data reporting notes (both daily reports and at harvest) have been updated to account for the new crops.

  • The graphing nodes have been updated to account for the new crops.

All these changes require skills and procedures that we have already covered in previous tutorial sections. Therefore, to keep the tutorial focused on the new aspects and save you from some repetitive tasks, we have included these scripts and updates as starting point within the provided APSIMX file.

Currently, the RotationManager canvas is empty. Please take a moment to try and represent the above defined cropping sequence via a suitable bubble chart. If you work on this solution with a colleague, please note that there are many possible ways to represent the desired crop sequence within the RotationManager canvas. At this point, it is useful for you to first try to come up with your own solution, as this will assist you in developing a better conceptual understanding of representing cropping sequences within the RotationManager. Once you have tried to come up with your own solution, please proceed by unhiding the suggested solutions here below.

Show/Hide Solution: Crop Sequence Diagram

The most concise way of representing the cropping sequence (that we could think of) is shown below. It minimises the number of crop nodes and transitions (i.e., arcs) required to represent the system. Thereby, it is important to not that the "Fallow" node is used to represent both: (i) summer as well as winter fallows that last an entire season, and (ii) short break periods in autumn and spring that occur between the cultivation of two directly adjacent summer and winter crops.

BubbleChart_flexible

However, there are many different ways in which one can conceptualise and setup the bubble chart. Here below, we show another commonly used alternative, where a separate node for the off-season periods in autumn and spring is created. While such nodes are not strictly necessary for representing the system in APSIM, they allow to keep the summer and winter seasons more neatly separated within the bubble chart. On the downside, this solution requires slightly more nodes and transitions (i.e., arcs) to represent the system.

BubbleChart_flexible_withBreakPeriod

It is important to note that, while an equivalent APSIM simulation can be generated with different bubble chart designs, they may often require different manager scripts. For the remainder of the tutorial, we will proceed with the first solution shown above. We also recommend that you continue working with the same setup, as this will make it easier to follow along.

At this stage, we can also add descriptive names to the transitions within the bubble chart. When referencing the name of specific transitions, a clear and unambiguous naming convention will avoid any confusion later on.

APSIMscreenshot_BubbleChart_flexible_withTransitionNames


Transitioning between Plot States

The next step in the RotationManager is to specify:

  • the conditions that trigger each transition between nodes, and

  • the actions to be executed whenever a given transition is taken.

For this, we again have to link to manager scripts within the Paddock node of the simulation tree by calling them in the RotationManager. The overall setup of transition conditions and transition actions is quite trivial, as it is identical to the procedures presented in the previous tutorial section on basic crop rotations. Please complete the conditions and action fields in the RotationManager, so that all “Enter” arcs have the condition “CanSow” and the action “SowCrop()”. Instead, all “Exit” arcs should have the condition “CanHarvest” and the action “HarvestCrop()”. Please make sure to always reference the manager script of the intended crop. For example, the completed box of “Conditions” and “Actions” for wheat (i.e., “Enter WH” and “Exit WH”) should look like the following:

TransitionActionConditions_EnterWH
TransitionActionConditions_ExitWH

Now that the overall structure of nodes and transition rules of the cropping sequence has been defined in the RotationManager, the next step is to update these manager scripts that are called upon by the transition rules. This is the actual more tricky part. As specified further above, our current manager scripts for sowing and harvesting were simply copied from the previous tutorial example.

The only modifications made so far in the provided starting APSIMX file were adjustments to the manager script parameters to use reasonable values for each crop (sowing window, planting density, etc.). For example, when you navigate to the manager script SowHarvest_wheat, you will see that the sowing window dates and sowing properties are set to reasonable values for wheat in the Darling Downs region.

SowHarvest_wheat

Instead, we now need to verify that the manager script correctly represents the further above defined cropping sequencing rules. For this, it is insufficient to inspect the Parameters tab only, but we will have to consult and modify the C# code of the manager scripts.

Absolute Water Availability

As identified in the section Cropping Scenario above, the first decision rule to be implemented is that a crop is only sown if sufficient water is available during the sowing window, while otherwise the plot is left in fallow. When you select any of the current manager scripts for sowing and harvesting, e.g. SowHarvest_wheat, you can identify that they already contain a water availability check identical to the one presented in the previous tutorial section on basic crop rotations. Accordingly, a crop is only sown if sufficient water resources are available during the sowing window. No further changes are required from our side. When you click through the various four sowing and harvest manager scripts, you will notice that the sowing windows slightly vary by crop, while the water thresholds differ between summer and winter crops. Specifically, as we anticipate more in-season rainfall during the summer growing period, the water threshold for summer crops (sorghum and mungbean) is slightly lower than for winter crops (wheat and chickpea). Generally, the water thresholds used here are lower than in the previous tutorial section and will more frequently result in the sowing of a crop rather than leaving the plot fallow.

Crop Sequence: Disease Pressure and Nitrogen Management

When now shifting to the issue of representing the crop sequencing rules, you will notice that no corresponding variables and drop-down menus are available in the current manager scripts for sowing and harvesting. Accordingly, we will need to make dedicated modifications to the C# code.

In the previous cases, when we worked with C# code in APSIM manager scripts, we predominantly accessed the namespaces, classes, and properties that are defined within the APSIM source code. We accessed those APSIM components by copying using directives (i.e., namespace imports) from existing manager scripts and by exploring available methods and properties through IntelliSense in the APSIM code editor. In the current case, we will instead also define some simple variables ourselves to keep track of the previously grown crops.

Default Manager Script

As a first step, let us generate a new manager script by right-clicking on the Paddock node, selecting Add model..., and then choosing Manager. Rename the new node to CropSequenceEnforcer, as it will enforce the desired crop sequence rules. The default manager script contains some useful placeholder code that is a good starting point for many purposes.

Default “Manager” script provided in APSIM
 1 using System;
 2 using Models.Core;
 3 using Models.Interfaces;
 4 using Models.PMF;
 5 using APSIM.Shared.Utilities;
 6
 7 namespace Models
 8 {
 9     [Serializable]
10     public class Script : Model
11     {
12         [Link] IClock Clock;
13         [Link] ISummary Summary;
14
15         [EventSubscribe("DoManagement")]
16         private void DoDailyCalculations(object sender, EventArgs e)
17         {
18             // Called once per day
19         }
20     }
21 }

The default manager script begins with several using directives (i.e., namespace imports). These imports make the types defined in those namespaces directly available within the script. Now, you can refer to them with short names instead of writing their full namespace paths every time. Subsequently, the script declares the namespace Models, which is the standard namespace for all APSIM manager scripts. In a nutshell, any manager script that we are creating in the form of user-written C# classes is dynamically compiled at runtime. For this to work, APSIM expects all classes of manager scripts to:

  • Be defined within the Models namespace.

  • Inherit from the base class Model.

  • Be decorated with the [Serializable] attribute.

In C#, [Serializable] is an attribute of a class. As you know, APSIMX files are written in JSON, while APSIM is written in C#. When APSIM loads your APSIMX file, it deserializes the JSON code into C# objects. When you save a modified simulation to file, it serializes the C# objects back into JSON. This only works if all APSIM model classes are serializable.

Subsequently, the code declares a C# class named Script that inherits from APSIM’s base class Model. By inheriting from Model, the class is included in the simulation tree, gains the ability to link to other APSIM models, and is able to receive events from the APSIM event system.

[Link] allows us to link to other APSIM models and access their properties and methods at runtime. For example, through the use of “[Link] ISummary Summary;” we gain access to the Summary node in APSIM, allowing us to write messages to the summary log. Similarly, “[Link] IClock Clock;” provides access to the simulation clock, which allows us to dynamically retrieve the current simulation date via “Clock.Today”.

Finally, the script defines an empty placeholder method named DoDailyCalculations that is decorated with the [EventSubscribe(“DoManagement”)] attribute. This attribute indicates that the method should be called whenever the DoManagement event is raised. The DoManagement event is raised once per day in APSIM, allowing us to perform daily calculations and updates. Manager scripts typically hook into DoManagement because it fires once per day and is designed for management actions (e.g., sowing, fertilizing, irrigation).

Understanding the structure and functionality of the default manager script gives us a good first foundation to build customised manager scripts.

Helper-Script: Crop Sequence Enforcer

For our example, we will first generate a helper script that will allow our individual crop sowing and harvesting manager scripts to enforce the desired crop sequencing rules. The overall purpose of the helper script is:

  • To remember the two most recently harvested crops (no matter which sowHarvest_[Crop] script initiated them).

  • To decide which next crop is allowed to be sown based on the previous plot history (cereal vs. legume).

  • To record the crop history after each harvest.

Besides defining variables to keep track of the last two harvested crops, the helper script does not schedule sowing or harvesting directly. Instead, this continues to be carried out by the various sowHarvest_[Crop] scripts that rely on CropSequenceEnforcer as a utility model.

As first step, let us review the using directives: Usually, it is a good idea to keep all the standard using directives provided in the default manager script. After all, we usually do not know at the start from which of the most widely used namespaces we will need to access classes and methods. For our current purpose instead, we know that we will generate a very simple and reduced helper script that will only require to access System and Models.Core. Please remove the other namespace imports from the top of the script.

Subsequently, please rename the placeholder class Script to CropSequenceEnforcer, as this clearly documents the purpose of our class.

Next, we will define two private string variables to keep track of the last two harvested crops. Initially, these variables should be set to null, as we consider that no crops have been grown prior to the simulation start date.

Storage variables for previously harvested crops
1     // Crop history
2     private string previousCrop1 = null;   // Most recent harvested crop
3     private string previousCrop2 = null;   // Second most recent harvested crop

Subsequently, we define a public method AllowsSowing() that takes the name of a crop as input and returns a boolean value. Based on the history of the previous crops harvested, it indicates if the crop in question can be sown, given our crop sequencing rules.

Method “AllowsSowing”: Enforcing alternation between cereals and legumes
 1     // Check if the proposed crop is allowed under given crop sequencing rules
 2     public bool AllowsSowing(string crop)
 3     {
 4         crop = crop.ToLower();
 5
 6         bool isCereal  = crop == "sorghum" || crop == "wheat";
 7         bool isLegume  = crop == "mungbean" || crop == "chickpea";
 8
 9         if (!isCereal && !isLegume)
10             throw new Exception($"CropSequenceEnforcer: Unknown crop '{crop}'.");
11
12         // No history yet → allow only cereals
13         if (previousCrop1 == null || previousCrop2 == null)
14             return isCereal;
15
16         bool previous_1_wasCereal =
17             previousCrop1 == "sorghum" || previousCrop1 == "wheat";
18
19         bool previous_2_wasCereal =
20             previousCrop2 == "sorghum" || previousCrop2 == "wheat";
21
22         // Two cereals in a row → enforce a legume
23         if (previous_1_wasCereal && previous_2_wasCereal)
24             return isLegume;
25
26         // Otherwise → enforce a cereal
27         return isCereal;
28     }

In more detail, the method AllowsSowing() first converts the input crop name to lowercase to ensure case-insensitive comparisons. It then checks if the proposed crop is a cereal (sorghum or wheat) or a legume (mungbean or chickpea). If the crop is neither, an exception is thrown indicating an unknown crop. The method then evaluates the previous two harvested crops:

  • If there is no history yet (i.e., both previous crops are null), only cereals are allowed to be sown.

  • If the last two harvested crops were cereals, only legumes are allowed to be sown.

  • In all other cases, only cereals are allowed to be sown.

Then, we define a public method RecordHarvest() that takes the name of a harvested crop as input and updates the history of previous crops.

Method “RecordHarvest”: Updating crop sequence history
 1     // Called at harvest time to update crop sequence history
 2     public void RecordHarvest(string crop)
 3     {
 4         crop = crop.ToLower();
 5
 6         bool isRotationCrop =
 7             crop == "sorghum" ||
 8             crop == "wheat"   ||
 9             crop == "mungbean"||
10             crop == "chickpea";
11
12         if (isRotationCrop)
13         {
14             previousCrop2 = previousCrop1;
15             previousCrop1 = crop;
16         }
17     }

Finally, we add two public read-only accessors that allow our other scripts to retrieve the names of the previously harvested crops. This may come in handy for logging purposes, although the heavy lifting will be done by the AllowsSowing() and RecordHarvest() methods.

Creating public accessors for previous crops
1     // Public read-only accessors
2     public string PreviousCrop1 => previousCrop1;
3     public string PreviousCrop2 => previousCrop2;

With these changes in place, the CropSequenceEnforcer script is now complete. In case that you are uncertain if you implemented all changes correctly, you can compare your solution to the final version here: CropSequenceEnforcer.cs. Please note, in case that you copy and paste all of the above code into the CropSequenceEnforcer node in the APSIM GUI, you may need to close and reopen the APSIM code editor to ensure that all changes are correctly reflected.

This is a good opportunity to review standard C# naming conventions. When sharing code with other APSIM users (who may have limited C# experience), conventions may not have always been strictly followed. Still, understanding the main aspects of typical naming conventions can be very helpful when trying to understand existing C# scripts as a starting point before making own modifications. In this example, it is particularly useful to know that:

  • camelCase is used for local variables, private fields, and parameters. E.g., the private fields previousCrop1 and previousCrop2, the method parameter crop, and local variables such as isCereal and isLegume.

  • PascalCase is used for public types, properties, and methods. E.g., the class name CropSequenceEnforcer, the method names AllowsSowing and RecordHarvest, and the public properties PreviousCrop1 and PreviousCrop2.

Said simply, previousCrop1 is used only within CropSequenceEnforcer, while PreviousCrop1 is accessible within our other scripts.

Updating Sowing and Harvesting Manager Scripts

Now that we have the helper script in place, we can start modifying the sowing and harvesting manager scripts. Specifially, we want to rely on the CropSequenceEnforcer to ensure that the various CanSow properties enforce the crop sequencing rules, and that the HarvestCrop() method records all crops that have been harvested. Arbitrarily, let us here jointly modify the SowHarvest_wheat manager (while modifications to the three other manager scripts are identical). Please select the Script tab to access the C# code.

As a first change, we define the private variable cropSequenceEnforcer in the script and link it to the existing CropSequenceEnforcer node in the simulation tree, allowing access to its properties and methods. Please add the following line to the existing [Link] statements in SowHarvest_wheat.

Linking the CropSequenceEnforcer model
1     [Link] private CropSequenceEnforcer cropSequenceEnforcer;

We do not introduce any changes to the subsequent secion in the manager script that defines the variables exposed in the Parameters tab. Instead, directly below that code section, let us define for pure convenience a private variable that identifies the current crop name in lowercase (which will save us from repeatedly typing the same code).

Private variable of the current crop name (lowercase format)
1     private string GetCropName()
2     {
3         return (Crop as Model).Name.ToLower();
4     }

We keep the methods OnSimulationCommencing() and DoManagement() unchanged. Instead, we need to update the CanSow property to enforce the crop sequencing rules. The main change we need to implement is that CanSow should return 1 only if, in addition to the previous conditions, the rules defined in the AllowsSowing() method of the cropSequenceEnforcer are also satisfied. In practice, this means that we have to:

  • Access the name of the current crop:

string cropName = GetCropName();
  • Add the following condition to all statements returning 1:

cropSequenceEnforcer.AllowsSowing(cropName)

However, since the simulation workflow may not necessarily function without errors, it is a good practice to also log core information within Summary, APSIM’s built-in logging interface. Here, the key information we need to keep track of is which crops were previously harvested on the plot (“PreviousCrop1” and “PreviousCrop2”). We are adding a print statement that records the field history recognised by our active script:

  • Logging core information within the Summary logging interface:

Summary.WriteMessage(this,
    $"Field history → PreviousCrop1={cropSequenceEnforcer.PreviousCrop1 ?? "null"}, PreviousCrop2={cropSequenceEnforcer.PreviousCrop2 ?? "null"}",
    MessageType.Diagnostic);

The updated code of the CanSow property should look as follows:

CanSow property with enforcement of crop sequencing
 1     // Test whether we can sow a crop today
 2     // +ve number - yes
 3     // 0          - no
 4     // -ve number - no, out of scope (planting window)
 5     [Units("0-1")]
 6     public int CanSow
 7     {
 8         get
 9         {
10             if (!afterInit)
11                 return 0;
12
13             bool isPossibleToday = false;
14             bool inWindow = DateUtilities.WithinDates(StartDate, Clock.Today, EndDate);
15             bool endOfWindow = DateUtilities.DatesEqual(EndDate, Clock.Today);
16
17             if (!Crop.IsAlive && inWindow &&
18                 accumulatedRain.Sum > MinRain &&
19                 MathUtilities.Sum(waterBalance.ESW) > MinESW)
20                 isPossibleToday = true;
21
22             string cropName = GetCropName();
23
24             // Case 1: normal sowing
25             if (isPossibleToday && cropSequenceEnforcer.AllowsSowing(cropName))
26             {
27                 Summary.WriteMessage(this,
28                     $"Field history → PreviousCrop1={cropSequenceEnforcer.PreviousCrop1 ?? "null"}, PreviousCrop2={cropSequenceEnforcer.PreviousCrop2 ?? "null"}",
29                     MessageType.Diagnostic);
30
31                 return 1;
32             }
33
34             // Case 2: Must sow on last day of window
35             if (!Crop.IsAlive && endOfWindow && MustSow &&
36                 cropSequenceEnforcer.AllowsSowing(cropName))
37             {
38                 Summary.WriteMessage(this,
39                     $"Field history (end window) → PreviousCrop1={cropSequenceEnforcer.PreviousCrop1 ?? "null"}, PreviousCrop2={cropSequenceEnforcer.PreviousCrop2 ?? "null"}",
40                     MessageType.Diagnostic);
41
42                 return 1;
43             }
44
45             // Out of window
46             if (!Crop.IsAlive && !inWindow)
47                 return -1;
48
49             return 0;
50         }
51     }

The method SowCrop() and property CanHarvest remain unchanged. Within the method HarvestCrop() we need to once more invoke our helper script CropSequenceEnforcer to ensure that all harvested crops are stored in a central point. If, instead, we tried to store and access the previously harvested crops within each individual manager script (e.g., within SowHarvest_wheat, etc.), that script would only be able to track previous wheat crops, but not any of the other crops grown in the same field. We conduct the following steps:

  • Access the name of the just harvested crop:

string harvested = GetCropName();
  • Send the name of the harvested crop for storage to the CropSequenceEnforcer:

cropSequenceEnforcer.RecordHarvest(harvested);
  • Logging core information within the Summary logging interface:

Summary.WriteMessage(this,
    $"Updated field history → PreviousCrop1={cropSequenceEnforcer.PreviousCrop1 ?? "null"}, PreviousCrop2={cropSequenceEnforcer.PreviousCrop2 ?? "null"}",
    MessageType.Diagnostic);

The updated code of the HarvestCrop() method should look as follows:

HarvestCrop() method with tracking of harvested crops
 1     public void HarvestCrop()
 2     {
 3         Summary.WriteMessage(this,
 4             this.FullPath + " - harvesting " + (Crop as Model).Name,
 5             MessageType.Diagnostic);
 6
 7         MonthlyHarvestedWt =
 8             (Structure.FindChild<IModel>("Grain", relativeTo: (INodeModel)Crop)
 9             as ReproductiveOrgan).Wt;
10
11         Crop.Harvest();
12         Crop.EndCrop();
13
14         string harvested = GetCropName();
15
16         cropSequenceEnforcer.RecordHarvest(harvested);
17
18         Summary.WriteMessage(this,
19             $"Updated field history → PreviousCrop1={cropSequenceEnforcer.PreviousCrop1 ?? "null"}, PreviousCrop2={cropSequenceEnforcer.PreviousCrop2 ?? "null"}",
20             MessageType.Diagnostic);
21     }

With these modifications, the SowHarvest_wheat script is completed. If you are uncertain if you implemented all changes correctly, you can compare your solution to the final version here: SowHarvest_wheat.cs. The C# code for SowHarvest_chickpea and SowHarvest_mungbean is identical. Due to the different variables considered under the Parameters tab, the C# code for SowHarvest_sorghum is slightly different (though there are no differences in the here modified code sections).

An APSIMX file showing the tutorial’s state at this point in the exercise is available here: CropRotation_flexible_mid.apsimx.

Inspecting Simulation Results

With this, we have fully represented our cropping scenario in APSIM and can now select the Run button in the top menu to conduct the simulation. We have added quite some new rules about which crop sequences are allowed. Our first step should be to check whether these rules were correctly being implemented in the simulation as intended. To get an idea of the overall progression of the simulated crops, let us first inspect the RotationRugplot.

Rugplot_flexibleRotation_StuckInSorghum

Rugplot of simulation results.

From the rugplot, we can identify that there is the following progression of field occupations:

  • Summer 1985: Fallow

  • Winter 1985: Wheat

  • Summer 1986: Sorghum

  • Winter 1986: Fallow

  • Summer 1987: Mungbean

  • Winter 1987: Fallow

  • Summer 1988: Sorghum

  • Winter 1988: Fallow

  • Summer 1989: Sorghum

  • Winter 1989: Fallow

  • Summer 1990: Mungbean

  • Winter 1990: Wheat

  • Summer 1991: Sorghum

  • Winter 1991: Fallow

  • Summer 1992: Mungbean

  • Winter 1992: Fallow

  • From Summer 1993: Sorghum

There are a couple of major takeaway messages that we can derive from the rugplot:

  • The crop sequencing rule seems to be correctly implemented. (“If the previous two cultivated crops were cereals, the next crop must be a legume. In all other cases, the next crop must be a cereal.”)

  • The low sowing threshold for the summer season is met in all but the first year (which is a special case, as we started the simulation only on 1 January, meaning that we have missed all early-season rainfall).

  • The higher sowing threshold for the winter season is only satisfied in two seasons, while six winter seasons remain in fallow.

  • And finally, the glaring issue: from summer 1993 onward, our field has remained continuously occupied by sorghum.

This last aspect is obviously unintended. Whatever the cause of APSIM stalling in that crop state, it conflicts with our intended simulation scenario and has to be modified. After all, our objective was to simulate an approximatively realistic progression of crops on a field in the Darling Downs (with some simplifications for the sake of the tutorial). A field that remains occupied by the same crop over multiple years is certainly not meeting this condition.

As another piece of evidence, we can have a look at the Data tab of the report node HarvestReport_sorghum. This allows to identify, that the sorghum crop planted in the summer season of 1993 never actually produced any crop yield (i.e., did not reach harvest).

HarvestReport_sorghum_StuckInSorghum

Harvest report for sorghum.

The rugplot and harvest report give us a first good insight. However, to get a more mechanistic understanding of what is happening to our simulation we have to look at the more detailed logging results that are accessible in the Summary node. When not knowing what to look for, scrolling through the simulation log can be quite a lenghty process, particularly when dealing with long-term simulations. However, in our case, we already know that things turn sour starting from the summer season 1993, which allows us to use the vertical scroll bar to fastly move over the first years of the simulation until we reach the end of 1992. From then, we want to closely inspect the simulation log and find the reason why our simulation stalls in sorghum. Please independently take a moment to inspect the simulation log yourself. What is the reason our simulation is not continuing as intended?

Show/Hide Solution: Inspecting Simulation Log

On 1992-11-19 a sorghum crop is sown and the simulation state changes from "Fallow" to "Sorghum". On 1992-11-22 a sorghum crop is emerging. So far, the simulation seems to progress in a standard manner. On 1992-12-16 sorghum reaches floral initiation, on 1993-01-10 the flag-leaf stage is reached, and on 1993-01-22 the sorghum field is flowering. On 1993-01-27 we record the start of grain filling, but sadly on 1993-02-27 the sorghum crop fails due to a loss of leaf area.

StuckInSorghum_Log

Figure: APSIM simulation log of the day recording crop failure of sorghum.



While a crop failure is truly bad whenever it happens in reality, a single failed crop in an APSIM simulation is not the end of the world and should neither be the end of our flexible cropping sequence. As you can see from the above screenshot, after the crop failure, the log records the following info:

""Paddock.RotationManager: Transition from "Sorghum" to "Fallow" by Exit SG is not possible. Weight = 0"

In other words: The RotationManager that we created, evaluates that the condition for leaving the "Sorghum" state are never fulfilled. In fact, if we remember the "Conditions" that we specified as part of the "Exit SG" transition (i.e., arc), we indicated that we can only transition back to "Fallow" from a crop, when the conditions for "CanHarvest" are fulfilled. Instead, we did not cater for other possibilities, such as that a crop dies or does never germinate. In those cases, our scripts should be flexible enough to intervene and transition the simulation state back to "Fallow".

A usual step when a crop failure occurs in a simulation is to further investigate the mechanistic cause: Which kind of stress or other factor led to the total loss of leaf area? Did this stress build up gradually or over a short amount of time? Did our management choices contribute to the crop failure? While these analytical questions fall outside the scope of our tutorial, which focuses on the technical handling of crop rotations, you may wish to explore them on your own time.



Finetuning the Simulation

The inspection of simulation results highlighted the need to add logic to the RotationManager to prevent the simulation from becoming stalled in a single state when a crop is not harvested. Two likely causes of such situations are that the crop is sown but fails to germinate, or that it dies before reaching harvest.

To take care of this issue in a comprehensive manner, let us return to the RotationManager. For each crop, add another transition arc that returns from the crop to fallow and call it Abandon [Crop]. The updated bubble chart should look as follows:

RotationManager_CropFailureArcs

Updated RotationManager accounting for crop failure.

For each transition, please add the following Conditions and Actions (here illustrated for sorghum):

RotationManager_AbandonCrop_ConditionsActions

Abandoning crops: “Conditions” and “Actions”.

Of course, we have to define the corresponding conditions and actions within the various manager scripts for sowing and harvesting. For this, the following C# code needs to be added to all four manager scripts of sowing and harvesting (SowHarvest_sorghum, SowHarvest_wheat, …).

C# code additions for crop abandoning
 1     [Units("0-1")]
 2     public int MustAbandon
 3     {
 4         get
 5         {
 6             // check if crop is dead
 7             if (afterInit && !Crop.IsAlive)
 8             {
 9                 Summary.WriteMessage(this,
10                     $"Abandoning crop: Crop is dead after initialization.",
11                     MessageType.Diagnostic);
12                 return 1;
13             }
14
15             // check if crop did not emerge
16             else if (((Crop as Plant).DaysAfterSowing > 50) && (!(Crop as Plant).IsEmerged))
17             {
18                 Summary.WriteMessage(this,
19                     $"Abandoning crop: Crop did not emerge within 50 days after sowing.",
20                     MessageType.Diagnostic);
21                 return 1;
22             }
23
24             // check if crop is late for harvest
25             else if (Crop.IsAlive && (Crop as Plant).DaysAfterSowing > 180)
26             {
27                 Summary.WriteMessage(this,
28                     $"Abandoning crop: Crop is late for harvest (180 days after sowing).",
29                     MessageType.Diagnostic);
30                 return 1;
31             }
32
33             // crop is neither dead nor late for harvest
34             return 0;
35         }
36     }
37
38     // Abandon crop
39     public void AbandonCrop()
40     {
41         if (Crop.IsAlive)
42         {
43             Crop.EndCrop();
44         }
45     }

The above defined property MustAbandon checks for three eventualities:

  • If the crop died.

  • If the crop did not emerge (until day 50 after sowing).

  • If the crop is late for harvest (i.e., the simulation reached day 180 after sowing, but no harvest has occured).

In all three cases, we decide to abandon the crop in favour of continuing on with our regular rotation sequence. The method AbandonCrop() checks for the unlikely case that the crop should still be alive, in which case it ends the crop. Otherwise, it does not do any action as part of conducting the transion back to fallow.

After having added the above C# code to all four manager scripts for sowing and harvesting, rerun the simulation and inspect the Rugplot.

Rugplot_flexibleRotation_updated

Rugplot of updated simulation results.

As can be seen in the above rugplot: The simulation no longer stalls in sorghum from the summer season 1993 onwards. In fact, we see that on 28 February 1993, the state of our simulation transitions from sorghum to fallow because the property MustAbandon returns 1.

From the rugplot we can also see another particularity: Since our crop sequencing logic accounts only for harvested crops, the sorghum crop that failed in the summer of 1993 is not considered when checking that no more than two cereals occur consecutively. As a consequence, three sorghum crops were sown consecutively on the field. While we will stick to this logic for our tutorial, you could easily shift the crop counting to consider all sown instead of harvested crops.

The final APSIMX file that corresponds to the end of this tutorial section is available here: CropRotation_flexible_final.apsimx.

Conclusion

This tutorial section has shown how to use the RotationManager to represent flexible cropping scenarios. We have shown that the RotationManager can accommodate a large diversity of cropping sequences. The empty canvas provides a starting point that allows representing crop rotations of different lengths, and the shift between alternative field occupations based on clearly identified conditions. The Conditions and Actions fields within the RotationManager are the central vehicles to link any manager script of your choice to control and steer how a cropping sequence shall progress. The flexibility of the empty canvas also means that there is not only one way to implement any given scenario.

This tutorial has introduced the default manager script that is distributed with APSIM. We have shown, how understanding its components is a good starting point for building own, customised manager scripts.

Subsequently, we have given a specific example, of how manager scripts can interact with one another. In our specific case, they operated at two different levels: The CropSequenceEnforcer controlled the overall progression of crops for the entire simulation, while the various SowHarvest_[Crop] scripts for sowing and harvesting were focused on managing individual crops.

The analysis of our results have provided a practical example of how the inspection of the rugplot, report nodes, and the summary log complement one another in comprehensively diagnosing the simulation outcomes. We have further seen, how a careful inspection of simulation results can often lead to uncover unintended simulation results that require targeted corrections.

While this tutorial focuses on these issues of how to correctly represent cropping sequences within APSIM, this is often only the starting point of actual analytical work. Such a classical focus on interpreting results of APSIM simulations, understanding drivers of certain simulation outcomes and investigating the relationship between targeted variables is outside of the scope of this tutorial.

References

ABARES. (2024). Snapshot of Australian Agriculture 2024. Australian Bureau of Agricultural and Resource Economics and Science (ABARES). https://doi.org/10.25814/473z-7187