V1_0.java [src/java/m/multiobj] Revision:   Date:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package m.multiobj;

import csip.ModelDataService;
import csip.annotations.*;
import static csip.annotations.ResourceType.OUTPUT;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.Path;
import org.rythmengine.Rythm;
import org.freshvanilla.compile.CachedCompiler;
import org.freshvanilla.compile.CompilerUtils;
import org.moeaframework.Executor;
import org.moeaframework.core.NondominatedPopulation;
import org.moeaframework.core.Solution;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.moeaframework.core.Variable;
import org.moeaframework.core.variable.EncodingUtils;

/**
 * A playground and testbed for performing multi-objective analysis.
 *
 * @author Andre Dozier
 */
@Name("multiobj")
@Description("Multiobjective playground")
@Resource(file = "/bin/moea.jar", id = "moea")
@Resource(file = "/templates/Problem.template", id = "prob")
@Resource(file = "*.java", type = OUTPUT)
@Path("m/mo/1.0")
public class V1_0 extends ModelDataService {

  private final static DateFormat fmt = new SimpleDateFormat("yyyyMMddHHmmssSSS");

  NondominatedPopulation result;
  Class<?> cls;
  String method; // = "NSGAII"; 
  int max_evals; // = 10000; 
  int population; // = 50;
  int nDecisions, nObjectives, nConstraints;
  JSONArray vars;
  String code;
  String generatedCode;
  Map<String, Object> params;


  private Map<String, Object> getTemplateParams(JSONArray vars, String className, int nDecisions, int nObjectives, int nConstraints, String code) throws JSONException {

    Map<String, Object> retVal = new HashMap<>(6);

    // classname
    retVal.put("classname", className);

    // problem definition
    retVal.put("nDecisions", nDecisions);
    retVal.put("nObjectives", nObjectives);
    retVal.put("nConstraints", nConstraints);

    // variable definitions
    JSONObject o;
    if (vars.length() != nDecisions) {
      throw new IllegalArgumentException("Number of 'vars' must equal 'nDecisions'");
    }
    String[] varType = new String[nDecisions];
    int[] varNbits = new int[nDecisions];
    double[] varLower = new double[nDecisions];
    double[] varUpper = new double[nDecisions];
    for (int i = 0; i < nDecisions; i++) {
      o = vars.getJSONObject(i);

      // variable type 
      varType[i] = o.getString("type").toLowerCase();
      if (!(varType[i].equals("boolean")
          || varType[i].equals("binary")
          || varType[i].equals("permutation")
          || varType[i].equals("discrete")
          || varType[i].equals("real"))) {
        throw new IllegalArgumentException("Type must be 'boolean', 'binary', 'permutation', discrete', or 'real'. Got '" + varType[i] + "'!");
      }

      // binary variable - number of bits
      if (o.has("nbits")) {
        varNbits[i] = o.getInt("nbits");
      } else if (varType[i].equals("binary") || varType[i].equals("permutation")) {
        throw new IllegalArgumentException("binary and permutation variable types require 'nbits' property!");
      }

      // lower bounds
      if (o.has("lower")) {
        varLower[i] = o.getDouble("lower");
      } else if (varType[i].equals("discrete") || varType[i].equals("real")) {
        throw new IllegalArgumentException("discrete or real variables require 'lower' properties!");
      }

      // upper bounds
      if (o.has("upper")) {
        varUpper[i] = o.getDouble("upper");
      } else if (varType[i].equals("discrete") || varType[i].equals("real")) {
        throw new IllegalArgumentException("discrete or real variables require 'upper' properties!");
      }

    }
    retVal.put("varType", varType);
    retVal.put("varNbits", varNbits);
    retVal.put("varLower", varLower);
    retVal.put("varUpper", varUpper);

    // user-supplied function evaluation code (and constraints)
    retVal.put("code", code);

    return retVal;
  }


  private JSONObject objectives() throws JSONException {
    int n = nObjectives;
    int size = result.size();
    JSONObject o = new JSONObject();
    double[][] f = new double[n][size];

    o.put("n", n);
    o.put("size", size);

    int i = -1;
    for (Solution solution : result) {
      i++;
      for (int j = 0; j < n; j++) {
        f[j][i] = solution.getObjective(j);
      }
    }
    for (int j = 0; j < n; j++) {
      String name = "f" + j;
      JSONObject fj = new JSONObject();
      fj.put("name", name);
      fj.put("values", JSON.toJSONArray(f[j]));
      o.put(name, fj);
    }
    return o;
  }


  private JSONObject constraints() throws JSONException {
    int n = nConstraints;
    int size = result.size();
    JSONObject o = new JSONObject();
    double[][] c = new double[n][size];

    o.put("n", n);
    o.put("size", size);

    int i = -1;
    for (Solution solution : result) {
      i++;
      for (int j = 0; j < n; j++) {
        c[j][i] = solution.getConstraint(j);
      }
    }
    for (int j = 0; j < n; j++) {
      String name = "c" + j;
      JSONObject cj = new JSONObject();
      cj.put("name", name);
      cj.put("values", JSON.toJSONArray(c[j]));
      o.put(name, cj);
    }
    return o;
  }


  private JSONObject decisions() throws JSONException {

    int n = nDecisions;
    int size = result.size();
    JSONObject o = new JSONObject();
    String[][] x = new String[n][size];

    o.put("n", n);
    o.put("size", size);

    int i = -1;
    for (Solution solution : result) {
      i++;
      for (int j = 0; j < n; j++) {
        Variable v = solution.getVariable(j);
        String varType = vars.getJSONObject(j).getString("type").toLowerCase();
        switch (varType) {
          case "boolean":
            x[j][i] = String.valueOf(EncodingUtils.getBoolean(v));
            break;
          case "binary":
            x[j][i] = EasyArray.toBinaryString(EncodingUtils.getBinary(v));
            break;
          case "permutation":
            x[j][i] = String.join(" ", EasyArray.toString(EncodingUtils.getPermutation(v)));
            break;
          case "discrete":
            x[j][i] = String.valueOf(EncodingUtils.getInt(v));
            break;
          case "real":
            x[j][i] = String.valueOf(EncodingUtils.getReal(v));
            break;
          default:
            throw new IllegalArgumentException("Variable type is not recognized '" + varType + "'!");
        }
      }
    }
    for (int j = 0; j < n; j++) {
      String name = "x" + j;
      JSONObject xj = JSON.copy(vars.getJSONObject(j));
      xj.put("name", name);
      xj.put("values", JSON.toJSONArray(x[j]));
      o.put(name, xj);
    }
    return o;
  }


  private JSONObject output() throws JSONException {
    JSONObject o = new JSONObject();
    o.put("size", result.size());
    o.put("nObjectives", nObjectives);
    o.put("nConstraints", nConstraints);
    o.put("nDecisions", nDecisions);
    o.put("objectives", objectives());
    o.put("constraints", constraints());
    o.put("decisions", decisions());
    return o;
  }


  /**
   * Compile the code into a Java class
   *
   * @throws Exception
   */
  @Override
  protected void preProcess() throws Exception {

    // add moea to class path
    File f = getResourceFile("moea");
    CompilerUtils.addClassPath(f.toString());
    CachedCompiler cc = CompilerUtils.CACHED_COMPILER;

    // get algorithm parameters 
    method = getStringParam("method");
    max_evals = getIntParam("max_evals");
    population = getIntParam("population");

    // problem parameters
    nDecisions = getIntParam("nDecisions");
    nObjectives = getIntParam("nObjectives");
    nConstraints = getIntParam("nConstraints");
    vars = getJSONArrayParam("vars");
    code = getStringParam("code");

    // generate the code
    String className = "Prob_" + fmt.format(new Date());
    params = getTemplateParams(vars, className, nDecisions, nObjectives, nConstraints, code);

    // render the code
    File p = getResourceFile("prob");
    generatedCode = Rythm.render(p, params);

    // save code to file 
//        File genCodeFile = new File(getWorkspaceDir(), "Problem.java");
//        FileUtils.writeStringToFile(genCodeFile, generatedCode);
    // LOG.info(code);
    // compile the code
    cls = cc.loadFromJava(className, generatedCode);
  }


  /**
   * Execute the multi-objective problem
   *
   * @return
   * @throws Exception
   */
  @Override
  protected void doProcess() throws Exception {
    result = new Executor()
        .withProblemClass(cls)
        .withAlgorithm(method)
        .withMaxEvaluations(max_evals)
        .withProperty("populationSize", population)
        .run();
  }


  /**
   * Place the multi-objective optimization output into the result
   *
   * @throws Exception
   */
  @Override
  protected void postProcess() throws Exception {
    // add summary info
    putResult("output", output());
    putResult("Problem.java", generatedCode);
  }
}