PhenologicalModelType.java
/*
* This file is part of Indicators.
*
* Indicators is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Indicators is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Indicators. If not, see <https://www.gnu.org/licenses/>.
*/
package fr.inrae.agroclim.indicators.model.data.phenology;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import fr.inrae.agroclim.indicators.resources.Resources;
import lombok.extern.log4j.Log4j2;
/**
* Type of phenological model.
*
* @author Olivier Maury
*/
@Log4j2
public enum PhenologicalModelType {
/**
* Type for curve phenological model.
*/
curve {
@Override
PhaseEnd getEndPhase(final PhenologyCalculator calc, final List<Double> tmeans, final int startDate,
final double cumjvi, final int stageNb) {
final Double tMax = calc.getMaxTemperature();
final Double tMin = calc.getMinTemperature();
final Double tOpt = calc.getOptimalTemperature();
Objects.requireNonNull(tMax, "PhenologyCalculator.maxTemperature must not be null for curve model!");
Objects.requireNonNull(tMin, "PhenologyCalculator.minTemperature must not be null for curve model!");
Objects.requireNonNull(tOpt, "PhenologyCalculator.optimalTemperature must not be null for curve model!");
return getCurveEndPhase(tMax, tMin, tOpt, calc, tmeans, startDate, cumjvi, stageNb);
}
},
/**
* Alternative for grapevine.
*
* - S0 : Aout
*
* - S1 : Dormance
*
* - S2 : Débourrement (BB)
*
* - S3 : Floraison (FLO)
*
* - S4 : Véraison (VER)
*
* - S5 : Véraison + 35 jours
*/
curve_grapevine {
@Override
public PhaseEnd getEndPhase(final PhenologyCalculator calc, final List<Double> tmeans, final int startDate,
final double cumjvi, final int stageNb) {
LOGGER.traceEntry("curve_grapevine StageNb={}", stageNb);
double tMax;
double tMin;
double tOpt;
switch (stageNb) {
case Stage.ONE:
// Phase 1 -- Aout - Dormance
// Chuine function
final double chuineA = calc.getChuineA();
final double chuineB = calc.getChuineB();
final double chuineC = calc.getChuineC();
final int nbOfDays = tmeans.size();
final double threshold = calc.getPhaseSum(stageNb);
Double cum = 0.;
Integer phaseEnd = null;
for (int day = startDate; day < nbOfDays; day++) {
final double tmean = tmeans.get(day - 1);
final double fa1 = chuineA * Math.pow(tmean - chuineC, 2);
final double fa2 = chuineB * (tmean - chuineC);
final double efficient = 1 / (1 + Math.exp(fa1 + fa2));
cum += efficient;
if (cum >= threshold) {
phaseEnd = day;
break;
}
}
return new PhaseEnd(phaseEnd, cum);
case Stage.TWO:
// Phase 2 -- DOR - BB
// same as 'curve' model, with special temperatures
// (no photoperiod effect, no vernalization effect)
tMax = calc.getMaxTemperatureDeb();
tMin = calc.getMinTemperatureDeb();
tOpt = calc.getOptimalTemperatureDeb();
return getCurveEndPhase(tMax, tMin, tOpt, calc, tmeans, startDate, cumjvi, stageNb);
case Stage.FOUR:
// Phase 4 -- Floraison - Véraison
// same as 'curve' model, with special temperatures
// (no photoperiod effect, no vernalization effect)
tMax = calc.getMaxTemperatureVer();
tMin = calc.getMinTemperatureVer();
tOpt = calc.getOptimalTemperatureVer();
return getCurveEndPhase(tMax, tMin, tOpt, calc, tmeans, startDate, cumjvi, stageNb);
case Stage.THREE:
// Phase 3 -- BB - Floraison
// same as 'curve' model, with special temperatures
// (no photoperiod effect, no vernalization effect)
return curve.getEndPhase(calc, tmeans, startDate, cumjvi, stageNb);
case Stage.FIVE:
// simply adding 35 days
final int end = calc.getS3ToS4() - 1;
return new PhaseEnd(startDate + end, cumjvi);
default:
throw new RuntimeException("curve_grapevine not tested with more than 5 stages.");
}
}
},
/**
* Alternative for grapevine to compute stages for water balance.
*
* - S1 = (BB-365), so == S2 in curve_grapevine
*
* - S2 = (FLO-365), so == S3 in curve_grapevine
*
* - S3 = (VER+35-365), so == S4 in curve_grapevine
*
* - S4 = max((S3+1),330)
*/
curve_grapevine_sw {
@Override
PhaseEnd getEndPhase(final PhenologyCalculator calc, final List<Double> tmeans, final int startDate,
final double cumjvi, final int stageNb) {
LOGGER.traceEntry("curve_grapevine_sw StageNb={}", stageNb);
final int end = 330 + 365;
PhaseEnd pe;
switch (stageNb) {
case Stage.ONE:
LOGGER.trace("DEB");
pe = curve_grapevine.getEndPhase(calc, tmeans, startDate, cumjvi, 1);
if (pe.getDay() == null) {
return new PhaseEnd(null, cumjvi);
}
return curve_grapevine.getEndPhase(calc, tmeans, pe.getDay() + 1, pe.getCumjvi(), Stage.TWO);
case Stage.TWO, Stage.THREE:
if (stageNb == Stage.TWO) {
LOGGER.trace("FLO");
}
if (stageNb == Stage.THREE) {
LOGGER.trace("VER");
}
pe = curve_grapevine.getEndPhase(calc, tmeans, startDate, cumjvi, stageNb + 1);
if (pe.getDay() == null) {
return new PhaseEnd(null, cumjvi);
}
int day = pe.getDay();
if (stageNb == Stage.THREE) {
day += calc.getS3ToS4();
}
if (day > end) {
day = end;
}
return new PhaseEnd(day, pe.getCumjvi());
case Stage.FOUR:
if (startDate > end) {
return new PhaseEnd(startDate, cumjvi);
} else {
return new PhaseEnd(end, cumjvi);
}
default:
break;
}
throw new UnsupportedOperationException("No stage > 4 for curve_grapevine_sw");
}
},
/**
* Type for linear phenological model.
*/
linear {
@Override
public PhaseEnd getEndPhase(final PhenologyCalculator calc, final List<Double> tmeans, final int startDate,
final double cumjvi, final int stageNb) {
final double threshold = calc.getPhaseSum(stageNb);
final boolean photoperiod;
final boolean vernalization;
final Double baseTemperature = calc.getBaseTemperature();
if (baseTemperature == null) {
throw new RuntimeException("calc.baseTemperature must not be null!");
}
// never use photoperiod and vernalisation
// for first stage or last stage
if (stageNb == 1 || stageNb == calc.getNbOfStages()) {
photoperiod = false;
vernalization = false;
} else {
photoperiod = calc.isPhotoperiodToCompute();
vernalization = calc.isVernalizationToCompute();
}
Integer phaseEnd = null;
double newCumjvi = 0;
Double cum = 0.;
final int nbOfDays = tmeans.size();
double[] cumjvivalues = null;
if (vernalization) {
final double optiVernTemp = calc.getOptimalVernalizationTemperature();
final double optiVernAmp = calc.getOptimalVernalizationAmplitude();
cumjvivalues = PhenologyCalculator.cumjvi(optiVernAmp, optiVernTemp, tmeans, startDate, cumjvi);
}
for (int day = startDate; day < nbOfDays; day++) {
final Double tmean = tmeans.get(day - 1);
if (tmean == null) {
throw new RuntimeException("TMEAN of day " + (day - 1) + " must not be null!");
}
double efficient = tmean - baseTemperature;
if (efficient <= 0) {
continue;
}
if (photoperiod) {
final double rfpi = PhenologyCalculator.rfpi(day, calc.getSiteLatitude(),
calc.getPhotoperiodSensitivity(), calc.getPhotoperiodSaturating(),
calc.getPhotoperiodBase());
efficient *= rfpi;
}
if (vernalization && cumjvivalues != null) {
newCumjvi = cumjvivalues[day - startDate];
final int nbOfVerDays = calc.getNbOfVernalizationDays();
final int minNbOfVerDays = calc.getMinNbOfVernalizationDays();
final double rfvi = PhenologyCalculator.rfvi(nbOfVerDays, minNbOfVerDays,
cumjvivalues[day - startDate]);
efficient *= rfvi;
}
cum += efficient;
if (cum >= threshold) {
phaseEnd = day;
break;
}
}
return new PhaseEnd(phaseEnd, newCumjvi);
}
},
/**
* Type for the grassland model.
*
* 1 year, no vernalization, no photoperiod.
*/
richardson {
@Override
PhaseEnd getEndPhase(final PhenologyCalculator calc, final List<Double> tmeans, final int startDate,
final double cumjvi, final int stageNb) {
final Double baseTemperature = calc.getBaseTemperature();
final double threshold = calc.getPhaseSum(stageNb);
if (baseTemperature == null) {
throw new RuntimeException("calc.baseTemperature must not be null!");
}
final Double maxTemperature = calc.getMaxTemperature();
if (maxTemperature == null) {
throw new RuntimeException("calc.maxTemperature must not be null!");
}
final int nbOfDays = tmeans.size();
Double cum = 0.;
Integer phaseEnd = null;
for (int day = startDate; day < nbOfDays; day++) {
final Double tmean = tmeans.get(day - 1);
if (tmean == null) {
throw new RuntimeException("TMEAN of day " + (day - 1) + " must not be null!");
}
if (tmean < baseTemperature) {
continue;
}
if (tmean > maxTemperature) {
cum += maxTemperature;
} else {
cum += tmean;
}
if (cum >= threshold) {
phaseEnd = day;
break;
}
}
return new PhaseEnd(phaseEnd, 0);
}
};
/**
* Compute end of phase according to start of phase and sum of temperature.
*
* @param tMax Maximal temperature for development (°C) used in the
* curvilinear model.
* @param tMin Minimal temperature for development (°C) used in the
* curvilinear model.
* @param tOpt Optimal temperature for development (°C) used in the
* curvilinear model.
* @param calc phenological calculator for this type of model
* @param tmeans average temperatures for the whole year (or 2 years)
* @param startDate start of phase (day of year)
* @param cumjvi Cumulative value of vernalization effects
* @param stageNb stage number
* @return day of year and cumjvi
*/
protected PhaseEnd getCurveEndPhase(final double tMax, final double tMin, final double tOpt,
final PhenologyCalculator calc, final List<Double> tmeans, final int startDate, final double cumjvi,
final int stageNb) {
final boolean photoperiod;
final boolean vernalization;
if (calc.getModel() == curve && calc.getNbOfStages() != Stage.FOUR) {
LOGGER.warn("Curve model with {} stages was not tested!", calc.getNbOfStages());
}
if (stageNb == Stage.ONE || stageNb == calc.getNbOfStages()) {
photoperiod = false;
vernalization = false;
} else {
photoperiod = calc.isPhotoperiodToCompute();
vernalization = calc.isVernalizationToCompute();
}
final double threshold = calc.getPhaseSum(stageNb);
Double cum = 0.;
// calcul du coefficient alpha qui est fonction des trois paramètres
// de la fonction (tmin, topt et tmax)
final double alpha = Math.log(2) / Math.log((tMax - tMin) / (tOpt - tMin));
Integer phaseEnd = null;
double newCumjvi = 0;
final int nbOfDays = tmeans.size();
double[] cumjvivalues = null;
if (vernalization) {
final double optiVernTemp = calc.getOptimalVernalizationTemperature();
final double optiVernAmp = calc.getOptimalVernalizationAmplitude();
cumjvivalues = PhenologyCalculator.cumjvi(optiVernAmp, optiVernTemp, tmeans, startDate, cumjvi);
}
for (int day = startDate; day < nbOfDays; day++) {
final double tmean = tmeans.get(day - 1);
if (tmean - tMin <= 0 || tMax - tmean <= 0) {
continue;
}
// calcul du numérateur de la fonction de Wang and Engel
// (1998). On utilise les trois paramètres de la fonction et
// la température moyenne journalière.
final double numerator = 2 * Math.pow(tmean - tMin, alpha) * Math.pow(tOpt - tMin, alpha)
- Math.pow(tmean - tMin, 2 * alpha);
// Calcul du dénominateur de la fonction qui utilise la
// valeur d'alpha
final double denominator = Math.pow(tOpt - tMin, 2 * alpha);
// valeur journalière d'unités de développement calculés avec la
// fonction de Wang and Engel
final double ratio = numerator / denominator;
double efficient = ratio;
if (photoperiod) {
efficient *= PhenologyCalculator.rfpi(day, calc.getSiteLatitude(), calc.getPhotoperiodSensitivity(),
calc.getPhotoperiodSaturating(), calc.getBaseTemperature());
}
if (vernalization && cumjvivalues != null) {
newCumjvi = cumjvivalues[day - startDate];
final int nbOfVerDays = calc.getNbOfVernalizationDays();
final int minNbOfVerDays = calc.getMinNbOfVernalizationDays();
final double rfvi = PhenologyCalculator.rfvi(nbOfVerDays, minNbOfVerDays, newCumjvi);
efficient *= rfvi;
}
cum += efficient;
if (cum >= threshold) {
phaseEnd = day;
break;
}
}
return new PhaseEnd(phaseEnd, newCumjvi);
}
/**
* Compute end of phase according to start of phase and sum of temperature.
*
* @param calc phenological calculator for this type of model
* @param tmeans average temperatures for the whole year (or 2 years)
* @param startDate start of phase (day of year)
* @param stage stage number
* @param cumjvi Cumulative value of vernalization effects
* @return day of year and cumjvi
*/
abstract PhaseEnd getEndPhase(PhenologyCalculator calc, List<Double> tmeans, int startDate, double cumjvi,
int stage);
/**
* @param locale local for the localized name
* @return localized name for the model type
*/
public String getName(final Locale locale) {
final Resources res = new Resources("fr.inrae.agroclim.indicators.resources.messages", locale);
return res.getString("PhenologicalModelType." + name() + ".name");
}
}