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";

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

  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, NSLOG1P, NSLOG2, NS2LOG, RMSE, TRMSE, MSE, PBIAS, FLF, FHF, BIAS, PMCC, IOA, IOA2, AVE, ABSDIFF, ABSDIFFLOG);

  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 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++;
      }
    }
    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++;
      }
    }

    // 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);
      }
    }
    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];
      }
    }
    return sum / sum1;
  }

}