APSIMInitiative / ApsimX

ApsimX is the next generation of APSIM
http://www.apsim.info
Other
138 stars 167 forks source link

In 'Reporting Variables' panel, events cannot be chosen from a pop-up list #3305

Closed BrianCollinss closed 5 years ago

BrianCollinss commented 5 years ago

It would be handy if we could choose events like [Wheat].Sowing from a pop-up list (like other objects or traits) when we need to calculate an aggregated value using arithmetic functions. Users are not usually familiar with all the events available. Currently events can only be chosen that way in the Reporting frequency panel in Report objects, not in the Reporting variables panel.

image

hol430 commented 5 years ago

I think it might be easy to confuse users if we start to always show events in the report's completion popup - it would imply that these events are reportable properties. Perhaps we need some mechanism (a keyboard shortcut?) to ask for a list of events, or perhaps it would be possible to show events instead of properties based on the context.

e.g. after typing Sum of [Wheat]. we would get a list of properties, but after typing Sum of [Wheat].Leaf.Transpiration from [Clock]. we would get a list of events.

BrianCollinss commented 5 years ago

Agree with your point. Maybe a new column like the type of the item (variable/event) or (printed?) or a different color/icon could be useful and easier to implement in the popup list.

JJguri commented 4 years ago

I am trying to generate the actual evapotranspiration variable in a manager-script. I cannot call the Wheat.Leaf.Transpiration variable as a part of the script. However, it can be called from the DailyReport. This is the error:

System.Exception: Errors found in manager model Volatilisation1
Line 99: 'Models.Interfaces.ICanopy' does not contain a definition for 'Transpiration' and no extension method 'Transpiration' accepting a first argument of type 'Models.Interfaces.ICanopy' could be found (are you missing a using directive or an assembly reference?)

   at Models.Manager.RebuildScriptModel()
   at UserInterface.Presenters.ManagerPresenter.BuildScript()

and this is the script:

using Models.Climate;
using Models.Interfaces;
using System;
using Models.Core;
using Models.PMF;
using Models.Soils;
using Models.Utilities;
using Models.Surface;
using APSIM.Shared.Utilities;
using System.Xml.Serialization;
using Models.Soils.Nutrients;

namespace Models
{   
    [Serializable]
    public class Script : Model
    {
        public double FASW { get; set; }
        public double WaterDeficit  { get; set; }

        [Link] private Clock Clock;
        [Link] private Zone myZone;
        [Link] private Summary Summary;
        [Link] private Fertiliser Fertiliser;
        [Link] private SoilNitrogen SoilN;
        [Link] private Irrigation irrigation;
        [Link] private Weather Met;
        [Link] private Soil soil;
        [Link] private SoilNitrogenNH4 SoilNH4;
        [Link] private Weather Weather;
        [Link] private Plant Wheat;

        [Link(ByName = true)] private ISolute Urea;
        [Link(ByName = true)] private ISolute NO3;
        [Link(ByName = true)] private ISolute NH4;

        [XmlIgnore] public double NH4N { get; set; }

        [Description("Reset SoilNH4 due to volatilisation (Yes or No)")]
        public string ResetN {get;set;}

        [Description("Reset Method (Schwenke or Johnson)")]
        public string ResetMethod {get;set;}

        [Description("Fertilisation date (dd-mmm)")]
        public string FertDate { get; set; }

        [Description("End of volatilisation period (dd-mmm)")]
        public string EndDate { get; set; }

        [Description("Volatilisation period (days)")]
        public double Days { get; set; }

        [Description("Average coarse sand in the soil profile (%)")]
        public double CoarseSand { get; set; }

        // Variables available for reporting
        public double ESW {get; set;}
        public double LL15 {get; set;}
        public double SW {get; set;}
        public double Wind {get; set;}
        public double vol_red {get; set;}
        public double Eo {get; set;}
        public double Ea {get; set;}
        public double rain {get; set;}
        public double currentNH4 {get; set;}
        public double newNH4 {get; set;}
        public double[] myNewNH4 {get; set;}

        [EventSubscribe("DoManagement")]
        private void OnDoManagement(object sender, EventArgs e)
        {
              if (ResetN == "Yes" && ResetMethod == "Schwenke")
            {   
                // Calculate the extractable soil water for layer 1 
                LL15 = soil.LL15[0];
                SW = soil.SoilWater.SW[0];
                ESW = SW - LL15;
                Wind = Weather.Wind;

                if (DateUtilities.WithinDates(FertDate, Clock.Today, EndDate))
                {

                Summary.WriteMessage(this, "Reset Nitrogen");
                // SoilN.Reset();
                // SoilNH4.AddKgHaDelta(SoluteSetterType.Other,myNH4);  //delta values cannot be negative

                CalcNewNH4();
                   Summary.WriteMessage(this, "About to set myNewNH4[0] to: " + myNewNH4[0].ToString());
                SoilNH4.SetKgHa(SoluteSetterType.Other, myNewNH4);

                }
            }
               if (ResetN == "Yes" && ResetMethod == "Johnson")
            {   
                // Calculate the extractable soil water for layer 1 
                Eo = soil.SoilWater.Eo;
                Ea = soil.SoilWater.Es + Wheat.Leaf.Transpiration;
                rain = Weather.Rain;

                if (DateUtilities.WithinDates(FertDate, Clock.Today, EndDate))
                {

                Summary.WriteMessage(this, "Reset Nitrogen");
                // SoilN.Reset();
                // SoilNH4.AddKgHaDelta(SoluteSetterType.Other,myNH4);  //delta values cannot be negative

                CalcNewNH4();
                   Summary.WriteMessage(this, "About to set myNewNH4[0] to: " + myNewNH4[0].ToString());
                SoilNH4.SetKgHa(SoluteSetterType.Other, myNewNH4);

                }
            }
         }

        // Calculate the adjusted values of the NH4 by soil layer
        private void CalcNewNH4()
        {
            // Calculate daily reduction (%) of SoilNH4 
            vol_red = (Ea-rain)/Eo;

            // Create reports about variables and reset function
            currentNH4 = SoilNH4.kgha[0];
               Summary.WriteMessage(this, string.Format("SoilNH4.kgha[0] = {0}, SoilNH4.kgha[1] = {1}, SoilNH4.kgha[2] = {2}", SoilNH4.kgha[0], SoilNH4.kgha[1], SoilNH4.kgha[2]));
            newNH4 = currentNH4 - (currentNH4 * 10000 * vol_red *0.001);

            myNewNH4 = new double[] {newNH4};
                 Summary.WriteMessage(this, string.Format("newNH4 = {0}, vol_red = {1}, currentNH4 = {2}, ESW = {3}", newNH4, vol_red, currentNH4, ESW));

        }
    }
}
hol430 commented 4 years ago

Yeah manager scripts don't integrate with the simulation tree structure very well. I think there is another issue which tracks that problem but we haven't come up with a solution yet. In the meantime you should be able to replicate report functionality using the FindByPath() function.

Ea = soil.SoilWater.Es + (double)this.FindByPath("[Wheat].Leaf.Transpiration").Value;
JJguri commented 4 years ago

thanks @hol430 . Now I just want to restrict the following equation vol_red = (Ea-rain)/Eo to positive values, i.e. vol_red should range from 0 to any positive value. How could I do that? If the result is negative the model should consider vol_red=0.

hol430 commented 4 years ago

You could do:

vol_red = Math.Max(0, (Ea - rain) / Eo);
JJguri commented 4 years ago

You could do:

vol_red = Math.Max(0, (Ea - rain) / Eo);

I applied that and I got this error:

System.Exception: Errors found in manager model Volatilisation
Line 104: Cannot implicitly convert type 'double' to 'System.EventArgs'

   at Models.Manager.RebuildScriptModel()
   at UserInterface.Presenters.ManagerPresenter.BuildScript()
hol430 commented 4 years ago

Hmm that is a strange error...did you put the line of code inside a function? If so, can you paste your script contents?

JJguri commented 4 years ago

It is in a function but at the same time I want to report it as e in the summary report.

using Models.Climate;
using Models.Interfaces;
using System;
using Models.Core;
using Models.PMF;
using Models.Soils;
using Models.Utilities;
using Models.Surface;
using APSIM.Shared.Utilities;
using System.Xml.Serialization;
using Models.Soils.Nutrients;

namespace Models
{   
    [Serializable]
    public class Script : Model
    {
        public double FASW { get; set; }
        public double WaterDeficit  { get; set; }

        [Link] private Clock Clock;
        [Link] private Zone myZone;
        [Link] private Summary Summary;
        [Link] private Fertiliser Fertiliser;
        [Link] private SoilNitrogen SoilN;
        [Link] private Irrigation irrigation;
        [Link] private Weather Met;
        [Link] private Soil soil;
        [Link] private SoilNitrogenNH4 SoilNH4;
        [Link] private Weather Weather;
        [Link] private Plant Wheat;

        [Link(ByName = true)] private ISolute Urea;
        [Link(ByName = true)] private ISolute NO3;
        [Link(ByName = true)] private ISolute NH4;

        [XmlIgnore] public double NH4N { get; set; }

        [Description("Reset SoilNH4 due to volatilisation (Yes or No)")]
        public string ResetN {get;set;}

        [Description("Reset Method (Schwenke or Johnson)")]
        public string ResetMethod {get;set;}

        [Description("Fertilisation date (dd-mmm)")]
        public string FertDate { get; set; }

        [Description("End of volatilisation period (dd-mmm)")]
        public string EndDate { get; set; }

        [Description("Volatilisation period (days)")]
        public double Days { get; set; }

        [Description("Average coarse sand in the soil profile (%)")]
        public double CoarseSand { get; set; }

        // Variables available for reporting
        public double ESW {get; set;}
        public double LL15 {get; set;}
        public double SW {get; set;}
        public double Wind {get; set;}
        public double vol_red {get; set;}
        public double NH4reduction {get; set;}
        public double e {get; set;}
        public double Eo {get; set;}
        public double Ea {get; set;}
        public double rain {get; set;}
        public double currentNH4 {get; set;}
        public double newNH4 {get; set;}
        public double[] myNewNH4 {get; set;}

        [EventSubscribe("DoManagement")]
        private void OnDoManagement(object sender, EventArgs e)
        {
              //Set up the Schwenke method to reduce SoilNH4  
              if (ResetN == "Yes" && ResetMethod == "Schwenke")
            {   
                // Calculate the extractable soil water for layer 1 
                LL15 = soil.LL15[0];
                SW = soil.SoilWater.SW[0];
                ESW = SW - LL15;
                Wind = Weather.Wind;

                if (DateUtilities.WithinDates(FertDate, Clock.Today, EndDate))
                {

                Summary.WriteMessage(this, "Reset Nitrogen by Schwenke's method");
                // SoilN.Reset();
                // SoilNH4.AddKgHaDelta(SoluteSetterType.Other,myNH4);  //delta values cannot be negative

                CalcNewNH4();
                   Summary.WriteMessage(this, "About to set myNewNH4[0] to: " + myNewNH4[0].ToString());
                SoilNH4.SetKgHa(SoluteSetterType.Other, myNewNH4);

                }
            }
                //Set up the Schwenke method to reduce SoilNH4  
               if (ResetN == "Yes" && ResetMethod == "Johnson")
            {   
                // Calculate the extractable soil water for layer 1 
                Eo = soil.SoilWater.Eo;
                Ea = soil.SoilWater.Es + (double)this.FindByPath("[Wheat].Leaf.Transpiration").Value;
                rain = Weather.Rain;
                e = Math.Max(0, (Ea - rain) / Eo);

                if (DateUtilities.WithinDates(FertDate, Clock.Today, EndDate))
                {

                Summary.WriteMessage(this, "Reset Nitrogen by Johnson's method");
                // SoilN.Reset();
                // SoilNH4.AddKgHaDelta(SoluteSetterType.Other,myNH4);  //delta values cannot be negative

                CalcNewNH4();
                   Summary.WriteMessage(this, "About to set myNewNH4[0] to: " + myNewNH4[0].ToString());
                SoilNH4.SetKgHa(SoluteSetterType.Other, myNewNH4);

                }
            }
         }

        // Calculate the adjusted values of the NH4 in the first layer
        private void CalcNewNH4()
        {
            currentNH4 = SoilNH4.kgha[0];
                 // Create reports about variables and reset function
                 Summary.WriteMessage(this, string.Format("SoilNH4.kgha[0] = {0}", SoilNH4.kgha[0]));

            NH4reduction = (Math.Max(0, (Ea - rain) / Eo)) * currentNH4 * 0.01;
            newNH4 = currentNH4 - NH4reduction;

            myNewNH4 = new double[] {newNH4};
                 // Create reports about variables and reset function
                 Summary.WriteMessage(this, string.Format("currentNH4 = {0}, newNH4 = {1}, e = {2}, Ea = {3}, Eo = {4}, rain = {5}", currentNH4, newNH4, e, Ea, Eo, rain));

        }
    }
}

here is the problem: e = Math.Max(0, (Ea - rain) / Eo);

hol430 commented 4 years ago

Ah, ok I see. One of the arguments in the OnDoManagement function is called e, and its type is EventArgs. So when you try to assign a double to an object of type EventArgs, you get an error. An easy fix would be to rename the EventArgs argument to something else (e.g. args):

...
private void OnDoManagement(object sender, EventArgs args)
...
JJguri commented 4 years ago

perfect I did not realize that. Thanks @hol430 !

JJguri commented 4 years ago

@hol430 just a single question, is the variable SoilNH4.kgha in kgN/ha or in kgNH4/ha.

hol430 commented 4 years ago

If the description in the code is accurate, then it's in kgN/ha

JJguri commented 4 years ago

I want to create a variable hosting the currentUrea (kgHa) in the surface soil layer to calculates the N volatilisation in this pool. Which will be a similar variable than SoilNH4.kgha[0] but for Urea? I found the following variable [Soil].SoilNitrogen.ureappm but I need it in kgHa. Below is part of the script which was described below in the discussion:

currentNH4 = SoilNH4.kgha[0];
// Create reports about variables and reset function
Summary.WriteMessage(this, string.Format("SoilNH4.kgha[0] = {0}", SoilNH4.kgha[0]));

NH4reduction = (Math.Max(0, (Ea - rain) / Eo)) * currentNH4 * 0.01;
newNH4 = currentNH4 - NH4reduction;
hol353 commented 4 years ago

[Link(ByName = true)] private ISolute Urea;

Urea.kgha[0]?

JJguri commented 4 years ago

Urea.kgha[0]

It's working well but, is it kgUreaN/ha or kgUrea/ha? I need kgUreaN/ha.

hol353 commented 4 years ago

It is urea N. The only place in APSIM that allows the user to specify urea is in Fertiliser. Everywhere else it is the N in urea.

JJguri commented 4 years ago

Do the conversion from urea to NH4 happen at the start of day or at the end of day?

hol353 commented 4 years ago

The conversion only happens when fertiliser is applied. It happens immediately the Fertiliser.Apply method is called.

JJguri commented 4 years ago

The conversion only happens when fertiliser is applied. It happens immediately the Fertiliser.Apply method is called.

I think you are referring to the conversion of fertiliser Urea to the Urea-N pool in the soil. I was asking about the conversion of the soil Urea-N pool to the soil NH4 pool

hol353 commented 4 years ago

Ahh ok. That happens between start of day and end of day.

JJguri commented 4 years ago

Ahh ok. That happens between start of day and end of day.

@hol353 thanks. @Keith-Pembleton

JJguri commented 4 years ago

@hol353 We implemented a reset function to estimate volatilisation. This is calculated by the “DoManagement” event , which will be done early in the day. We think that by resetting the urea pool each day we are no then allowing the urea pool to convert to the NH4. We need a way of depleting the urea pool through volatilisation and also allowing the conversion to NH4 to happen at the same time. Could you please check if we can implement this to happen at the end of the day?

using Models.Climate;
using Models.Interfaces;
using System;
using Models.Core;
using Models.PMF;
using Models.Soils;
using Models.Utilities;
using Models.Surface;
using APSIM.Shared.Utilities;
using System.Xml.Serialization;
using Models.Soils.Nutrients;

namespace Models
{   
    [Serializable]
    public class Script : Model
    {
        [Link] private Clock Clock;
        [Link] private Zone myZone;
        [Link] private Summary Summary;
        [Link] private Fertiliser Fertiliser;
        [Link] private SoilNitrogen SoilN;
        [Link] private Irrigation irrigation;
        [Link] private Weather Met;
        [Link] private Soil soil;
        [Link] private SoilNitrogenNH4 SoilNH4;
        [Link] private Weather Weather;

        [Link(ByName = true)] private ISolute Urea;
        [Link(ByName = true)] private ISolute NO3;
        [Link(ByName = true)] private ISolute NH4;

        [XmlIgnore] public double NH4N { get; set; }

        [Separator("Both volatilisation methods (Schwenke or Johnson)")]

        [Description("Reset SoilNH4 due to volatilisation (Yes or No)")]
        public string ResetN {get;set;}

        [Description("Reset Method (Schwenke or Johnson)")]
        public string ResetMethod {get;set;}

        [Description("Fertilisation date (dd-mmm)")]
        public string FertDate { get; set; }

        [Description("End of volatilisation period (dd-mmm)")]
        public string EndDate { get; set; }

        [Separator("Only for Schwenke's method")]

        [Description("Coarse sand in the surface soil layer (%)")]
        public double CoarseSand { get; set; }

        [Description("Is WindSpeed the average for the period? If variable leave 0 the next field (FixedWind or VariableWind)")]
        public string Wind { get; set; }

        [Description("Average Wind Speed? (m/s)")]
        public double WindSpeed { get; set; }

        // Variables available for reporting
        public double ESW {get; set;}
        public double LL15 {get; set;}
        public double SW {get; set;}
        public double VariableWind {get; set;}
        public double FixedWind {get; set;}
        public double vol_red {get; set;}
        public double epsilon {get; set;}
        public double Eo {get; set;}
        public double Ea {get; set;}
        public double rain {get; set;}
        public double currentNH4 {get; set;}
        public double NH4reduction {get; set;}
        public double newNH4 {get; set;}
        public double[] myNewNH4 {get; set;}
        public double currentUrea {get; set;}
        public double UreaReduction {get; set;}
        public double newUrea {get; set;}
        public double[] myNewUrea {get; set;}

        [EventSubscribe("DoManagement")]
        private void OnDoManagement(object sender, EventArgs e)
        {
              //Set up the Schwenke method to reduce SoilNH4  
              if (ResetN == "Yes" && ResetMethod == "Schwenke")
              {   
                  // Calculate the extractable soil water for layer 1 
                  LL15 = soil.LL15[0];
                  SW = soil.SoilWater.SW[0];
                  ESW = SW - LL15;
                  VariableWind = Weather.Wind;
                  FixedWind = WindSpeed;

                  if (Wind == "VariableWind" && (DateUtilities.WithinDates(FertDate, Clock.Today, EndDate)))
                  {
                   Summary.WriteMessage(this, "Reset Nitrogen by Schwenke's method");
                   // SoilN.Reset();
                   // SoilNH4.AddKgHaDelta(SoluteSetterType.Other,myNH4);  //delta values cannot be negative

                   CalcNewNH4SchwenkeVariableWind();
                   Summary.WriteMessage(this, "About to set myNewNH4[0] to: " + myNewNH4[0].ToString());
                   SoilNH4.SetKgHa(SoluteSetterType.Other, myNewNH4);
                   }
                   else
                   {
                   Summary.WriteMessage(this, "Reset Nitrogen by Schwenke's method");
                   // SoilN.Reset();
                   // SoilNH4.AddKgHaDelta(SoluteSetterType.Other,myNH4);  //delta values cannot be negative

                   CalcNewNH4SchwenkeFixedWind();
                   Summary.WriteMessage(this, "About to set myNewNH4[0] to: " + myNewNH4[0].ToString());
                   SoilNH4.SetKgHa(SoluteSetterType.Other, myNewNH4);
                   }
               }
                //Set up the Schwenke method to reduce SoilNH4  
               if (ResetN == "Yes" && ResetMethod == "Johnson")
               {   
                  // Calculate the extractable soil water for layer 1 
                  Eo = soil.SoilWater.Eo;
                  Ea = soil.SoilWater.Es;
                  rain = Weather.Rain;
                  epsilon = Math.Max(0, (Ea - rain) / Eo);

                  if (DateUtilities.WithinDates(FertDate, Clock.Today, EndDate))
                  {

                  Summary.WriteMessage(this, "Reset Nitrogen by Johnson's method");
                  // SoilN.Reset();
                  // SoilNH4.AddKgHaDelta(SoluteSetterType.Other,myNH4);  //delta values cannot be negative

                  CalcNewNH4Johnson();
                  Summary.WriteMessage(this, "About to set myNewNH4[0] to: " + myNewNH4[0].ToString());
                  SoilNH4.SetKgHa(SoluteSetterType.Other, myNewNH4);
                  }
               }
         }
         // Calculate the adjusted values of the NH4 by Schwenke's equation in the first layer
         private void CalcNewNH4SchwenkeVariableWind()
         {
            currentNH4 = SoilNH4.kgha[0];
            // Create reports about variables and reset function
            Summary.WriteMessage(this, string.Format("SoilNH4.kgha[0] = {0}", SoilNH4.kgha[0]));

            // Calculate daily reduction (%) of SoilNH4 
            vol_red = (Math.Max(0, (11.29 + (0.31 * CoarseSand) - (2.78 * VariableWind) + (ESW * 18.65))))/30;
            newNH4 = currentNH4 - (currentNH4 * (vol_red/100));

            myNewNH4 = new double[] {newNH4};
            // Create reports about variables and reset function
            Summary.WriteMessage(this, string.Format("newNH4 = {0}, vol_red = {1}, currentNH4 = {2}, ESW = {3}", newNH4, vol_red, currentNH4, ESW));
         }
            // Calculate the adjusted values of the NH4 by Schwenke's equation in the first layer
         private void CalcNewNH4SchwenkeFixedWind()
         {
            currentNH4 = SoilNH4.kgha[0];
            // Create reports about variables and reset function
            Summary.WriteMessage(this, string.Format("SoilNH4.kgha[0] = {0}", SoilNH4.kgha[0]));

            // Calculate daily reduction (%) of SoilNH4 
            vol_red = (Math.Max(0, (11.29 + (0.31 * CoarseSand) - (2.78 * WindSpeed) + (ESW * 18.65))))/30;
            newNH4 = currentNH4 - (currentNH4 * (vol_red/100));

            myNewNH4 = new double[] {newNH4};
            // Create reports about variables and reset function
            Summary.WriteMessage(this, string.Format("newNH4 = {0}, vol_red = {1}, currentNH4 = {2}, ESW = {3}", newNH4, vol_red, currentNH4, ESW));
         }
         // Calculate the adjusted values of the NH4 by Johnson's equation in the first layer
         private void CalcNewNH4Johnson()
         {
            currentNH4 = SoilNH4.kgha[0];
            currentUrea = Urea.kgha[0];
            // Create reports about variables and reset function
            Summary.WriteMessage(this, string.Format("SoilNH4.kgha[0] = {0}", SoilNH4.kgha[0]));

            //Calculates the NH4 reduction (kg/ha)
            NH4reduction = epsilon * currentNH4 * 0.01;
            newNH4 = currentNH4 - NH4reduction;

            //Calculates the Urea reduction (kg/ha)
            UreaReduction = epsilon * currentUrea * 0.2;
            newUrea = currentUrea - UreaReduction;

            myNewNH4 = new double[] {newNH4};
            myNewUrea = new double[] {newUrea};
            // Create reports about variables and reset function
            Summary.WriteMessage(this, string.Format("currentNH4 = {0}, newNH4 = {1}, currentUrea = {2}, newUrea = {3}, epsilon = {4}, Ea = {5}, Eo = {6}, rain = {7}", currentNH4, newNH4, currentUrea, newUrea, epsilon, Ea, Eo, rain));

         }
    }
}
hol353 commented 4 years ago

If you want this to happen after the nutrient model has done it's thing then change [EventSubscribe("DoManagement")] to [EventSubscribe("DoManagementCalculations")]

I suspect this won't make any difference though because essentially you're doing the same thing. You would just be resetting the NH4 pool before the next days nutrient calculations instead of the current days calculations.

I'm unsure why you're not seeing the behaviour you expect. I'm not an expert in the intricacies of nutrient modelling.

JJguri commented 4 years ago

Thanks @hol353 it's really appreciated.