V5_0.java [src/java/m/prms/model] Revision:   Date:
package m.prms.model;

import csip.api.server.Executable;
import csip.ModelDataService;
import csip.api.server.ServiceException;
import csip.annotations.*;
import static csip.annotations.ResourceType.*;
import java.io.File;
import java.io.FileWriter;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import javax.ws.rs.Path;
import m.prms.model.Utils.*;
import org.apache.commons.io.FileUtils;
import static m.prms.model.V5_0.PRMS5_EXE;
import ngmf.util.cosu.luca.of.NS;
import ngmf.util.cosu.luca.of.NS2LOG;
import ngmf.util.cosu.luca.of.RMSE;
import ngmf.util.cosu.luca.of.TRMSE;
import oms3.Conversions;
import oms3.ObjectiveFunction;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;

/**
 * PRMS Service. Execution of the original PRMS model.
 *
 * @author od
 */
@Name("PRMS")
@Description("Precipitation Runoff Modeling System.")
@State(State.UNSTABLE)
@Author(org = "USGS")
@Author(org = "CSU")
@Category("Hydrology")
@Path("m/prms/5.0")
@Polling(first = 5000, next = 2000)
@Resource(type = EXECUTABLE, file = "/bin/lin-amd64/prms-5.0", id = PRMS5_EXE)
@Resource(type = OUTPUT, file = "*-stdout.txt *-stderr.txt recharge.* output/*.statvar")
public class V5_0 extends ModelDataService {

  static final String PRMS5_EXE = "prms";
//  static final String CONTROL_FILE_VM = "m/prms/model/control_1.vm";
  static final String CONTROL_FILE_VM = "m/prms/model/animas-PRMS.control";
  static final String PARAM_FILE_VM = "m/prms/model/animas5test.params";

  private static VelocityEngine velocity;

  Calendar st;
  Calendar en;

  static final Map<String, ObjectiveFunction> OF = new HashMap<>();

  double missing;

  // parameter to calibrate
  double den_max;
  double cecn_coef;
  double jh_coef;
  double rain_cbh_adj;
  double snow_cbh_adj;
  double tmax_allsnow;


  static {
    OF.put("kge", new KGE());
    OF.put("ns", new NS());
    OF.put("nslog", new NS2LOG());
    OF.put("nslog1p", new NSLOG1P());
    OF.put("nslog2", new NSLOG2());
    OF.put("rmse", new RMSE());
    OF.put("trmse", new TRMSE());
    OF.put("pbias", new PBIAS());
  }


  @Override
  protected void doProcess() throws Exception {

    missing = parameter().getDouble("missing", -9999d);

    // Parameter
    den_max = parameter().getDouble("den_max", 0.6);  // range 0.4-0.7
    cecn_coef = parameter().getDouble("cecn_coef", 0.782); // range 0.6 - 1.0
    jh_coef = parameter().getDouble("jh_coef", 0.007); // range 0.004 - 0.014
    rain_cbh_adj = parameter().getDouble("rain_cbh_adj", 1.0); // range 0.8 - 1.5
    snow_cbh_adj = parameter().getDouble("snow_cbh_adj", 1.3); //  range 0.8 - 1.5
    tmax_allsnow = parameter().getDouble("tmax_allsnow", 36.0); //  range 28.0 - 37.0

    createControlFile(workspace().getFile("run.control"));
    createParamFile(workspace().getFile("run.params"));

    workspace().getFile("output").mkdirs();

    Executable e = resources().getExe(PRMS5_EXE);
    e.setArguments("run.control");

    int ret = e.exec();
    if (ret != 0) 
      throw new ServiceException("Error executing prms: "
          + FileUtils.readFileToString(e.stderr(), "UTF-8"));

    for (String pname : parameter().getNames()) {
      for (String ofNameM : OF.keySet()) {
        if (pname.toLowerCase().startsWith(ofNameM)) {
          String[] data = parameter().getStringArray(pname);
          double v = calc_of(OF.get(ofNameM), data[0], data[1], st, en);
          results().put(pname, v);
          if (LOG.isLoggable(Level.INFO))
            LOG.info("of: " + pname + " " + v);
        }
      }
    }
    // cleanup files even before the workspace get's wiped.
    Arrays.stream(workspace().getFiles("*.cbh")).forEach(f -> f.delete());
  }


  private double calc_of(ObjectiveFunction of, String obs,
      String sim, Calendar start, Calendar end) throws Exception {

    // get observed data
    // e.g. obs_data02_14.csv/obs/orun[1]
    String o[] = obs.split("\\s+");
    double[] obsData = o[0].endsWith("csv")
        ? Utils.extractCSVColumn(workspace().getFile(o[0]), o[1], 1)
        : Utils.extractStatvarColumn(workspace().getFile(o[0]), o[1]);

    // e.g. output/csip_run/out/Outlet.csv/output/catchmentSimRunoff
    String s[] = sim.split("\\s+");
    double[] simData = s[0].endsWith("csv")
        ? Utils.extractCSVColumn(workspace().getFile(s[0]), s[1], 1)
        : Utils.extractStatvarColumn(workspace().getFile(s[0]), s[1]);

    double result = of.calculate(obsData, simData, missing);
    return checkForNaN(result);
  }


  double checkForNaN(double result) {
    double checkedResult = missing;
    if (!Double.isNaN(result)) 
      checkedResult = result;
    return checkedResult;
  }


  void createControlFile(File controlfile) throws Exception {
    st = Conversions.convert(parameter().getString("start_time"), Calendar.class);
    en = Conversions.convert(parameter().getString("end_time"), Calendar.class);

    VelocityContext context = new VelocityContext();
    context.put("service", this);

    FileWriter w = new FileWriter(controlfile);
    _velocity().getTemplate(CONTROL_FILE_VM, "UTF-8").merge(context, w);
    w.close();

    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine("Created: " + context);
      LOG.fine("\n" + FileUtils.readFileToString(controlfile, "UTF-8"));
    }
  }


  void createParamFile(File paramfile) throws Exception {
    VelocityContext context = new VelocityContext();
    context.put("param", this);

    FileWriter w = new FileWriter(paramfile);
    _velocity().getTemplate(PARAM_FILE_VM, "UTF-8").merge(context, w);
    w.close();

    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine("Created: " + context);
      LOG.fine("\n" + FileUtils.readFileToString(paramfile, "UTF-8"));
    }
  }


  static synchronized VelocityEngine _velocity() {
    if (velocity == null) {
      velocity = new VelocityEngine();
      velocity.setProperty("file.resource.loader.class",
          ClasspathResourceLoader.class.getName());
      velocity.setProperty("runtime.log", "/tmp/velocity.log");
      velocity.init();
    }
    return velocity;
  }


  //parameter
  public double getDen_max() {
    return den_max;
  }


  public double getCecn_coef() {
    return cecn_coef;
  }


  public double getJh_coef() {
    return jh_coef;
  }


  public double getRain_cbh_adj() {
    return rain_cbh_adj;
  }


  public double getSnow_cbh_adj() {
    return snow_cbh_adj;
  }


  public double getTmax_allsnow() {
    return tmax_allsnow;
  }


  /////////  for velocity
  public int getStartYear() {
    return st.get(Calendar.YEAR);
  }


  public int getStartMonth() {
    return st.get(Calendar.MONTH) + 1;
  }


  public int getStartDay() {
    return st.get(Calendar.DAY_OF_MONTH);
  }


  public int getEndYear() {
    return en.get(Calendar.YEAR);
  }


  public int getEndMonth() {
    return en.get(Calendar.MONTH) + 1;
  }


  public int getEndDay() {
    return en.get(Calendar.DAY_OF_MONTH);
  }


 

}