From 5bd310ee80465b3886a64128fb083b5e9a2f6ffe Mon Sep 17 00:00:00 2001 From: Tor-Einar Skog <tor-einar.skog@nibio.no> Date: Mon, 20 Dec 2021 12:29:36 +0100 Subject: [PATCH] First working version with separate cutoffs per phase --- .../yellowstemborertempmodel/DataMatrix.java | 8 +- .../YellowStemborerTempModel.java | 130 +++++++++++++----- .../YellowStemborerTempModelTest.java | 40 ++++-- 3 files changed, 130 insertions(+), 48 deletions(-) diff --git a/src/main/java/no/nibio/vips/model/yellowstemborertempmodel/DataMatrix.java b/src/main/java/no/nibio/vips/model/yellowstemborertempmodel/DataMatrix.java index f4dcd06..17d2e76 100644 --- a/src/main/java/no/nibio/vips/model/yellowstemborertempmodel/DataMatrix.java +++ b/src/main/java/no/nibio/vips/model/yellowstemborertempmodel/DataMatrix.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 NIBIO <http://www.nibio.no/>. + * Copyright (c) 2021 NIBIO <http://www.nibio.no/>. * * This file is part of PsilaRosaeTempModel. * PsilaRosaeTempModel is free software: you can redistribute it and/or modify @@ -22,7 +22,7 @@ package no.nibio.vips.model.yellowstemborertempmodel; import no.nibio.vips.util.DateMap; /** - * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a> + * @copyright 2021 <a href="http://www.nibio.no/">NIBIO</a> * @author Tor-Einar Skog <tor-einar.skog@nibio.no> */ public class DataMatrix extends DateMap{ @@ -31,4 +31,8 @@ public class DataMatrix extends DateMap{ public final static String DAILY_CONTRIB = "DC"; public final static String HEAT_SUM = "HEAT_SUM"; public final static String PHASE = "PHASE"; + public final static String PHASE_EGG ="EGG"; + public final static String PHASE_LARVAE ="LARVAE"; + public final static String PHASE_PUPA ="PUPA"; + public final static String PHASE_ADULT ="ADULT"; } diff --git a/src/main/java/no/nibio/vips/model/yellowstemborertempmodel/YellowStemborerTempModel.java b/src/main/java/no/nibio/vips/model/yellowstemborertempmodel/YellowStemborerTempModel.java index 7244505..4180168 100644 --- a/src/main/java/no/nibio/vips/model/yellowstemborertempmodel/YellowStemborerTempModel.java +++ b/src/main/java/no/nibio/vips/model/yellowstemborertempmodel/YellowStemborerTempModel.java @@ -26,6 +26,7 @@ import java.text.DecimalFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.List; @@ -58,17 +59,21 @@ public class YellowStemborerTempModel extends I18nImpl implements Model { private TimeZone timeZone; private DataMatrix dataMatrix; private Date biofixDate; - private String[] phases = {"EGG","LARVAE","PUPA","ADULT"}; + private final String[] phases = { + DataMatrix.PHASE_EGG, + DataMatrix.PHASE_LARVAE, + DataMatrix.PHASE_PUPA, + DataMatrix.PHASE_ADULT + }; + // DD aggregation cutoff values for each phase + private Double[] dd_lower = {13.0,12.0,15.0,13.0}; + private Double[] dd_upper = {36.0, 34.0,36.0,36.0}; - // Threshold values - // TODO: ADJUST with actual biologically sensible defaults - private Double dd_lower = 0.0; - private Double dd_upper = 40.0; + // Heat sum thresholds for each phase + private Double[] heatRequirements = {113.0, 370.0, 188.0, 25.0}; - private Double THRESHOLD_1 = 260.0; // Egg to larvae - private Double THRESHOLD_2 = 360.0; // Larvae to pupa - private Double THRESHOLD_3 = 560.0; // Pupa to adult + private String observedPhase = DataMatrix.PHASE_EGG; public YellowStemborerTempModel() { @@ -79,7 +84,7 @@ public class YellowStemborerTempModel extends I18nImpl implements Model { @Override public List<Result> getResult() throws ModelExcecutionException { this.calculateTemperatureSum(); - + //System.out.println(this.dataMatrix.toCSV()); List<Result> retVal = new ArrayList<>(); Date currentDate = this.dataMatrix.getFirstDateWithParameterValue(DataMatrix.HEAT_SUM); Calendar cal = Calendar.getInstance(this.timeZone); @@ -94,35 +99,35 @@ public class YellowStemborerTempModel extends I18nImpl implements Model { Double DAILY_CONTRIB = this.dataMatrix.getParamDoubleValueForDate(currentDate, DataMatrix.DAILY_CONTRIB); Double HEAT_SUM = this.dataMatrix.getParamDoubleValueForDate(currentDate, DataMatrix.HEAT_SUM); + String PHASE = this.dataMatrix.getParamStringValueForDate(currentDate, DataMatrix.PHASE); result.setValue(CommonNamespaces.NS_WEATHER, DataMatrix.TND, dFormat.format(TNDCurrentDate)); result.setValue(CommonNamespaces.NS_WEATHER, DataMatrix.TXD, dFormat.format(TXDCurrentDate)); result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), DataMatrix.DAILY_CONTRIB, dFormat.format(DAILY_CONTRIB)); result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), DataMatrix.HEAT_SUM, dFormat.format(HEAT_SUM)); - result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), "THRESHOLD_1", dFormat.format(this.THRESHOLD_1)); - result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), "THRESHOLD_2", dFormat.format(this.THRESHOLD_2)); - result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), "THRESHOLD_3", dFormat.format(this.THRESHOLD_3)); + result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), DataMatrix.PHASE, PHASE); + result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), "thresholdLarvae", dFormat.format(this.heatRequirements[0])); + result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), "thresholdPupa", dFormat.format(this.heatRequirements[0] + this.heatRequirements[1])); + result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), "thresholdAdult", dFormat.format(this.heatRequirements[0] + this.heatRequirements[1] + this.heatRequirements[2])); + result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), DataMatrix.PHASE, PHASE); + Integer warningStatus = Result.WARNING_STATUS_NO_WARNING; - Integer warningStatus = Result.WARNING_STATUS_NO_RISK; - if(HEAT_SUM < this.THRESHOLD_1) + if(PHASE.equals(DataMatrix.PHASE_EGG)) { - result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), DataMatrix.PHASE, this.phases[0]); + warningStatus = Result.WARNING_STATUS_NO_RISK; } - if(HEAT_SUM >= this.THRESHOLD_1) + if(PHASE.equals(DataMatrix.PHASE_LARVAE)) { - result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), DataMatrix.PHASE, this.phases[1]); warningStatus = Result.WARNING_STATUS_MINOR_RISK; } - if(HEAT_SUM >= this.THRESHOLD_2) + if(PHASE.equals(DataMatrix.PHASE_PUPA)) { - result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), DataMatrix.PHASE, this.phases[2]); - warningStatus = Result.WARNING_STATUS_HIGH_RISK; + warningStatus = Result.WARNING_STATUS_MINOR_RISK; } - if(HEAT_SUM >= this.THRESHOLD_3) + if(PHASE.equals(DataMatrix.PHASE_ADULT)) { - result.setValue(YellowStemborerTempModel.MODEL_ID.toString(), DataMatrix.PHASE, this.phases[3]); - warningStatus = Result.WARNING_STATUS_NO_WARNING; + warningStatus = Result.WARNING_STATUS_HIGH_RISK; } result.setWarningStatus(warningStatus); retVal.add(result); @@ -236,6 +241,8 @@ public class YellowStemborerTempModel extends I18nImpl implements Model { "\t\t}\n" + "]}\n"; } + + @Override public void setConfiguration(ModelConfiguration config) throws ConfigValidationException { @@ -243,26 +250,47 @@ public class YellowStemborerTempModel extends I18nImpl implements Model { // Adjusting defaults try { - if(config.getConfigParameter("THRESHOLD_1") != null) + if(config.getConfigParameter("heatRequirements") != null) { - this.THRESHOLD_1 = this.modelUtil.getDouble(config.getConfigParameter("THRESHOLD_1")); - } - if(config.getConfigParameter("THRESHOLD_2") != null) - { - this.THRESHOLD_2 = this.modelUtil.getDouble(config.getConfigParameter("THRESHOLD_2")); - } - if(config.getConfigParameter("THRESHOLD_3") != null) - { - this.THRESHOLD_3 = this.modelUtil.getDouble(config.getConfigParameter("THRESHOLD_3")); + this.heatRequirements = this.modelUtil.getDoubleArray(config.getConfigParameter("heatRequirements")); + if(this.heatRequirements.length != 4) + { + throw new ConfigValidationException("ERROR: Array of heat requirements has " + this.dd_lower.length + " elements, should be 4"); + } } if(config.getConfigParameter("dd_lower") != null) { - this.dd_lower = this.modelUtil.getDouble(config.getConfigParameter("dd_lower")); + this.dd_lower = this.modelUtil.getDoubleArray(config.getConfigParameter("dd_lower")); + if(this.dd_lower.length != 4) + { + throw new ConfigValidationException("ERROR: Array of lower cutoffs has " + this.dd_lower.length + " elements, should be 4"); + } } if(config.getConfigParameter("dd_upper") != null) { - this.dd_upper = this.modelUtil.getDouble(config.getConfigParameter("dd_upper")); + this.dd_upper = this.modelUtil.getDoubleArray(config.getConfigParameter("dd_upper")); + if(this.dd_upper.length != 4) + { + throw new ConfigValidationException("ERROR: Array of upper cutoffs has " + this.dd_upper.length + " elements, should be 4"); + } } + if(config.getConfigParameter("observedPhase") != null) + { + if(!(config.getConfigParameter("observedPhase") instanceof String)) + { + throw new ConfigValidationException("ERROR: Input parameter \"configParameter\" is " + config.getConfigParameter("observedPhase").getClass().getName() + ", must be a String"); + } + String inputObservedPhase = (String) config.getConfigParameter("observedPhase"); + if(Arrays.asList(phases).contains(inputObservedPhase)) + { + this.observedPhase = inputObservedPhase; + } + else + { + throw new ConfigValidationException("ERROR: observedPhase \"" + inputObservedPhase + "\" is not among the valid phases (" + String.join(",", phases) + ")"); + } + + } } catch(ClassCastException ex) { @@ -338,7 +366,15 @@ public class YellowStemborerTempModel extends I18nImpl implements Model { Date lastDate = this.dataMatrix.getLastDateWithParameterValue(DataMatrix.TND); Calendar cal = Calendar.getInstance(this.timeZone); - Double sum = 0.0; + String currentPhase = this.observedPhase; + + Double sum = currentPhase.equals(DataMatrix.PHASE_EGG) ? 0.0 + : currentPhase.equals(DataMatrix.PHASE_LARVAE) ? this.heatRequirements[0] + : currentPhase.equals(DataMatrix.PHASE_PUPA) ? this.heatRequirements[0] + this.heatRequirements[1] + : this.heatRequirements[0] + this.heatRequirements[1] + this.heatRequirements[2]; + + List<String> phasesList = Arrays.asList(this.phases); + while(today.compareTo(lastDate) <= 0) { WeatherObservation todayMinTemp = (WeatherObservation)this.dataMatrix.getParamValueForDate(today, DataMatrix.TND); @@ -348,10 +384,30 @@ public class YellowStemborerTempModel extends I18nImpl implements Model { throw new ModelExcecutionException("Missing weather data at " + today + ": " + (todayMinTemp == null ? "TND ": "") + (todayMaxTemp == null ? "TXD ": "")); } //System.out.println("today=" + today + ",todayTemp=" + todayTemp); - Double dailyContribution = nlc.calculateSingleSineWaveWithCutoff(todayMinTemp.getValue(), todayMaxTemp.getValue(), this.dd_lower, this.dd_upper); + Double current_dd_lower = this.dd_lower[phasesList.indexOf(currentPhase)]; + Double current_dd_upper = this.dd_upper[phasesList.indexOf(currentPhase)]; + Double dailyContribution = nlc.calculateSingleSineWaveWithCutoff(todayMinTemp.getValue(), todayMaxTemp.getValue(), current_dd_lower, current_dd_upper); this.dataMatrix.setParamDoubleValueForDate(today, DataMatrix.DAILY_CONTRIB, dailyContribution); sum += dailyContribution; this.dataMatrix.setParamDoubleValueForDate(today, DataMatrix.HEAT_SUM, sum); + // Check phase transition + if(sum >= this.heatRequirements[0] + this.heatRequirements[1] + this.heatRequirements[2]) + { + currentPhase = DataMatrix.PHASE_ADULT; + } + else if(sum >= this.heatRequirements[0] + this.heatRequirements[1]) + { + currentPhase = DataMatrix.PHASE_PUPA; + } + else if(sum >= this.heatRequirements[0]) + { + currentPhase = DataMatrix.PHASE_LARVAE; + } + else + { + currentPhase = DataMatrix.PHASE_EGG; + } + this.dataMatrix.setParamStringValueForDate(today, DataMatrix.PHASE, currentPhase); cal.setTime(today); cal.add(Calendar.DATE, 1); today = cal.getTime(); diff --git a/src/test/java/no/nibio/vips/model/yellowstemborertempmodel/YellowStemborerTempModelTest.java b/src/test/java/no/nibio/vips/model/yellowstemborertempmodel/YellowStemborerTempModelTest.java index 1d85d40..a37f366 100644 --- a/src/test/java/no/nibio/vips/model/yellowstemborertempmodel/YellowStemborerTempModelTest.java +++ b/src/test/java/no/nibio/vips/model/yellowstemborertempmodel/YellowStemborerTempModelTest.java @@ -50,26 +50,48 @@ public class YellowStemborerTempModelTest { @Test public void testGetResult() throws Exception { System.out.println("getResult"); + + // REQUIRED WeatherDataFileReader wfr = new WeatherDataFileReader(); + // Weather data files can be placed in ("src/test/resources") ModelConfiguration config = wfr.getModelConfigurationWithWeatherData("/test_data_2019.json", YellowStemborerTempModel.MODEL_ID.toString()); + // The timezone is used to set daily temperatures and biofix date correctly config.setConfigParameter("timeZone", "GMT+05:30"); + // The date for when to start calculating heat sums config.setConfigParameter("biofixDate", "2019-01-05"); - config.setConfigParameter("THRESHOLD_1", 290.0); - config.setConfigParameter("THRESHOLD_2", 390.0); - config.setConfigParameter("THRESHOLD_3", 590); - config.setConfigParameter("dd_lower", 15); - config.setConfigParameter("dd_upper", 31.1); + + // OPTIONAL + // The observed phase at biofix date. Default is EGG + config.setConfigParameter("observedPhase", DataMatrix.PHASE_EGG); + + // Heat requirement for transitioning out of [EGG,LARVAE,PUPA,ADULT] phases + Double[] heatRequirements = {113.0, 370.0, 188.0, 25.0}; + // Lower and upper cutoffs for [EGG,LARVAE,PUPA,ADULT] phases + Double[] dd_lower = {13.0,12.0,15.0,13.0}; + Double[] dd_upper = {36.0, 34.0,36.0,36.0}; + config.setConfigParameter("heatRequirements", heatRequirements); + config.setConfigParameter("dd_lower", dd_lower); + config.setConfigParameter("dd_upper", dd_upper); + + + + + //config.setConfigParameter("observedPhase", "Donald Duck"); + // Print the config - System.out.println(config.toJSON()); + //System.out.println(config.toJSON()); + + // Run the test YellowStemborerTempModel instance = new YellowStemborerTempModel(); instance.setConfiguration(config); List<Result> result = instance.getResult(); assertNotNull(result); + // Print the result - //ObjectMapper mapper = new ObjectMapper(); - //System.out.println(mapper.writeValueAsString(result)); - //result.forEach(r->System.out.println(r)); + /*ObjectMapper mapper = new ObjectMapper(); + System.out.println(mapper.writeValueAsString(result)); + result.forEach(r->System.out.println(r));*/ } -- GitLab