ObjFunc.java [src/csip/cosu] Revision:   Date:
/*
 * $Id$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API and application suite.
 *
 * 2012-2022, Olaf David and others, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package csip.cosu;

import csip.utils.SimpleCache;
import java.util.Arrays;
import java.util.List;

/**
 * Objective Functions.
 *
 * @author od
 */
public abstract class ObjFunc {

    /**
     * The name of the OF.
     *
     * @return the OF name
     */
    abstract public String name();

    /**
     * evaluate the OF
     *
     * @param obs observed values
     * @param sim simulated values
     * @param missing missing value
     * @return the of value
     */
    abstract public double eval(double[] obs, double[] sim, double missing);

    /**
     * In which direction is the OF progressing (1 positive, -1 negative
     * direction)
     *
     * @return the direction of improvement
     */
    abstract public int direction();

    /**
     * What is the optimal OF optimum value.
     *
     * @return the target value
     */
    abstract public int optimum();

    /**
     * Nash-Sutcliffe Efficiency
     */
    public static final String NS = "ns";
    /**
     * Modified Nash-Sutcliffe Efficiency
     */
    public static final String MNS = "mns";

    /**
     * Klinge-Gupta Efficiency; 2012
     */
    public static final String KGE = "kge";

    /**
     * Klinge-Gupta Efficiency; 2009
     */
    public static final String KGE09 = "kge09";

    public static final String NSLOG1P = "nslog1p";
    public static final String NSLOG2 = "nslog2";
    public static final String NS2LOG = "ns2log";
    /**
     * Transformed RMSE
     */
    public static final String TRMSE = "trmse";
    /*
   * Fenicia high flow
     */
    public static final String FHF = "fhf";
    /*
   * Fenicia low flow
     */
    public static final String FLF = "flf";

    /**
     * Percent Bias
     */
    public static final String PBIAS = "pbias";
    /**
     * Bias
     */
    public static final String BIAS = "bias";
    /**
     * Root Mean Square Error
     */
    public static final String RMSE = "rmse";
    /**
     * Mean Square Error
     */
    public static final String MSE = "mse";
    public static final String PMCC = "pmcc";

    /**
     * Index of Agreement
     */
    public static final String IOA = "ioa";

    public static final String IOA2 = "ioa2";

    /**
     * Average Volume Error
     */
    public static final String AVE = "ave";

    /**
     * Absolute Difference
     */
    public static final String ABSDIFF = "absdiff";

    /**
     * Log of Absolute Difference
     */
    public static final String ABSDIFFLOG = "absdifflog";

    private static final List<String> OF_LIST
            = Arrays.asList(KGE, NS, MNS, NSLOG1P, NSLOG2, NS2LOG, RMSE, TRMSE, MSE, PBIAS, FLF, FHF, BIAS, PMCC, IOA, IOA2, AVE, ABSDIFF, ABSDIFFLOG, KGE09);

    private static final SimpleCache<String, ObjFunc> OFS = new SimpleCache<>();

    /**
     * Get an OF instance.
     *
     * @param name the OF name to get .
     * @return The OF instance
     */
    public static ObjFunc of(String name) {
        return OFS.get(name, o -> create(name, null));
    }

    /**
     * Fetch an OF instance or supply your own OF
     *
     * @param name the OF name to get .
     * @param f a custom OF if the name fails to resolve
     * @return the OF instance.
     */
    public static ObjFunc of(String name, ObjFunc f) {
        return OFS.get(name, o -> create(name, f));
    }

    /**
     * List available OFs.
     *
     * @return the list of registered OF names
     */
    public static List<String> list() {
        return OF_LIST;
    }

    ///////////////
    private static ObjFunc create(String name, ObjFunc f) {
        switch (name.toLowerCase().trim()) {
            case KGE:
                return new KGE();
            case NS:
                return new NS();
            case MNS:
                return new MNS();
            case KGE09:
                return new KGE09();
            case NS2LOG:
                return new NS2LOG();
            case NSLOG1P:
                return new NSLOG1P();
            case NSLOG2:
                return new NSLOG2();
            case MSE:
                return new MSE();
            case RMSE:
                return new RMSE();
            case TRMSE:
                return new TRMSE();
            case PBIAS:
                return new PBIAS();
            case BIAS:
                return new BIAS();
            case PMCC:
                return new PMCC();
            case IOA:
                return new IOA();
            case IOA2:
                return new IOA2();
            case FHF:
                return new FHF();
            case FLF:
                return new FLF();
            case AVE:
                return new AVE();
            case ABSDIFF:
                return new ABSDIFF();
            case ABSDIFFLOG:
                return new ABSDIFFLOG();
            default:
                if (f != null) {
                    return f;
                } else {
                    throw new IllegalArgumentException(name);
                }
        }
    }

    /**
     * Check if the arrays have the same length.
     *
     * @param sim the simulated values
     * @param obs the observed values
     * @return the array len
     */
    static int checkArrays(double[] sim, double[] obs) {
        if (obs == null || obs.length == 0) {
            throw new IllegalArgumentException("obs null or empty.");
        }
        if (sim == null || sim.length == 0) {
            throw new IllegalArgumentException("sim null or empty.");
        }
        if (obs.length != sim.length) {
            throw new IllegalArgumentException("obs/sim not same size: (" + obs.length + "!=" + sim.length + ")");
        }
        return obs.length;
    }

    static double mse(double[] obs, double[] sim, double missing) {
        checkArrays(obs, sim);
        double error = 0;
        int len = 0;
        for (int i = 0; i < sim.length; i++) {
            if (obs[i] > missing) {
                double diff = obs[i] - sim[i];
                error += diff * diff;
                len++;
            }
        }
        if (len == 0) {
            throw new RuntimeException("No valid len value.");
        }
        error /= len;
        return error;
    }

    static double ioa(double[] obs, double[] sim, double pow, double missing) {
        checkArrays(obs, sim);
        int steps = sim.length;

        double sum_obs = 0;
        double len = 0.0;
        for (int i = 0; i < steps; i++) {
            if (obs[i] > missing) {
                sum_obs += obs[i];
                len++;
            }
        }

        if (len == 0) {
            throw new RuntimeException("No valid len value.");
        }
        // calculating mean values for both data sets
        double mean_obs = sum_obs / len;

        // calculating absolute squared sum of deviations from verification mean
        double td_vd = 0;
        double abs_sqDevi = 0;
        for (int i = 0; i < steps; i++) {
            if (obs[i] > missing) {
                td_vd += (Math.pow((Math.abs(obs[i] - sim[i])), pow));
                abs_sqDevi += Math.pow(Math.abs(sim[i] - mean_obs) + Math.abs(obs[i] - mean_obs), pow);
            }
        }
        if (Double.compare(abs_sqDevi, 0.0) == 0) {
            throw new RuntimeException("No valid abs_sqDevi value.");
        }
        return 1.0 - (td_vd / abs_sqDevi);
    }

    static double nbias(double[] obs, double[] sim, double missing) {
        checkArrays(sim, obs);
        double sum = 0;
        double sum1 = 0;
        for (int i = 0; i < sim.length; i++) {
            if (obs[i] > missing) {
                sum += sim[i] - obs[i];
                sum1 += obs[i];
            }
        }
        if (Double.compare(sum, 0.0) == 0) {
            throw new RuntimeException("No valid sum value.");
        }
        return sum / sum1;
    }

}