public static void main(String[] args) throws Exception {
    int status = 127;
    try {
      IloOplFactory.setDebugMode(true);
      IloOplFactory oplF = new IloOplFactory();

      // make master model
      IloOplErrorHandler errHandler = oplF.createOplErrorHandler();
      IloOplSettings settings = oplF.createOplSettings(errHandler);

      IloOplRunConfiguration masterRC =
          oplF.createOplRunConfiguration(
              DATADIR + "/cutstock_change.mod", DATADIR + "/cutstock_change.dat");
      masterRC.setErrorHandler(errHandler);
      IloCplex masterCplex = oplF.createCplex();
      masterCplex.setOut(null);

      masterRC.setCplex(masterCplex);
      IloOplModel masterOpl = masterRC.getOplModel();
      masterOpl.generate();
      IloOplDataElements masterDataElements = masterOpl.makeDataElements();

      int nWdth = masterDataElements.getElement("Amount").asIntMap().getSize();
      ArrayList<IloNumVar> masterVars = new ArrayList<IloNumVar>();
      IloNumVarMap cuts = masterOpl.getElement("Cut").asNumVarMap();
      for (int i = 1; i <= nWdth; i++) {
        masterVars.add(cuts.get(i));
      }

      // prepare sub model source, definition and engine
      IloOplModelSource subSource = oplF.createOplModelSource(DATADIR + "/cutstock-sub.mod");
      IloOplModelDefinition subDef = oplF.createOplModelDefinition(subSource, settings);
      IloCplex subCplex = oplF.createCplex();
      subCplex.setOut(null);

      double best;
      double curr = Double.MAX_VALUE;
      do {
        best = curr;

        // Make master model
        System.out.println("Solve master.");
        if (masterCplex.solve()) {
          curr = masterCplex.getObjValue();
          System.out.println("OBJECTIVE: " + curr);
          status = 0;
        } else {
          System.out.println("No solution!");
          status = 1;
        }

        // prepare data for sub model
        IloOplDataElements subDataElements = oplF.createOplDataElements();
        subDataElements.addElement(masterDataElements.getElement("RollWidth"));
        subDataElements.addElement(masterDataElements.getElement("Size"));
        subDataElements.addElement(masterDataElements.getElement("Duals"));
        // get reduced costs and set them in sub problem
        IloNumMap duals = subDataElements.getElement("Duals").asNumMap();
        for (int i = 1; i < nWdth + 1; i++) {
          IloForAllRange forAll =
              (IloForAllRange) masterOpl.getElement("ctFill").asConstraintMap().get(i);
          duals.set(i, masterCplex.getDual(forAll));
        }
        // make sub model
        IloOplModel subOpl = oplF.createOplModel(subDef, subCplex);
        subOpl.addDataSource(subDataElements);
        subOpl.generate();

        System.out.println("Solve sub.");
        if (subCplex.solve()) {
          System.out.println("OBJECTIVE: " + subCplex.getObjValue());
          status = 0;
        } else {
          System.out.println("No solution!");
          status = 1;
        }
        if (subCplex.getObjValue() > -RC_EPS) break;

        // Add variable in master model
        IloNumVar newVar = masterCplex.numVar(0, Double.MAX_VALUE);
        IloObjective masterObj = masterOpl.getObjective();
        masterCplex.setLinearCoef(masterObj, newVar, 1);
        for (int i = 1; i < nWdth + 1; i++) {
          double coef = subCplex.getValue(subOpl.getElement("Use").asIntVarMap().get(i));
          IloForAllRange forAll =
              (IloForAllRange) masterOpl.getElement("ctFill").asConstraintMap().get(i);
          masterCplex.setLinearCoef(forAll, newVar, coef);
        }
        masterVars.add(newVar);

        subOpl.end();
      } while (best != curr && status == 0);

      IloNumVar[] masterVarsA = new IloNumVar[masterVars.size()];
      masterVars.toArray(masterVarsA);
      masterCplex.add(masterCplex.conversion(masterVarsA, IloNumVarType.Int));
      if (masterCplex.solve()) {
        System.out.println("OBJECTIVE: " + masterCplex.getObjValue());
      }
      oplF.end();
    } catch (IloOplException ex) {
      System.err.println("### OPL exception: " + ex.getMessage());
      ex.printStackTrace();
      status = 2;
    } catch (IloException ex) {
      System.err.println("### CONCERT exception: " + ex.getMessage());
      ex.printStackTrace();
      status = 3;
    } catch (Exception ex) {
      System.err.println("### UNEXPECTED UNKNOWN ERROR ...");
      ex.printStackTrace();
      status = 4;
    }
    System.exit(status);
  }
  public double solve(Instance ins, Solution sol, int timeout) throws IloException {
    IloCplex solver = new IloCplex();
    BiIndexedIntVarMap y = new BiIndexedIntVarMap(solver, 0, 1, "y"); // (k,v)   (cluster,node)
    IndexedNumVarMap d =
        new IndexedNumVarMap(solver, 0, Double.MAX_VALUE, "dp"); // (k)     (cluster)	
    TriIndexedNumVarMap f =
        new TriIndexedNumVarMap(solver, 0, Double.MAX_VALUE, "f"); // (k,i,j) (cluster,tail,head)
    BiIndexedIntVarMap r = new BiIndexedIntVarMap(solver, 0, 1, "r"); // (k,i)   (cluster,node
    BiIndexedNumVarMap w = new BiIndexedNumVarMap(solver, 0, 1, "w"); // (i,j)   (node1<node2)

    int superRoot = ins.getNumNodes();
    // compute the weights of the objective function
    double w1 = ins.getW1() / ins.getNumNodes();
    double w2 = ins.getW2() / (ins.getLambda() * ins.getMaxClusterNumber());
    double w3 =
        2
            * ins.getW3()
            / (ins.getNumNodes() * (ins.getNumNodes() - 1) * (ins.getDMax() - ins.getDMin()));

    Set<OrderedPair> fp = new TreeSet<OrderedPair>(ins.getP());
    if (isActiveExtension(Extension.PFILTER)) {
      fp = ins.filterP();
      System.out.println("PFILTER: " + (ins.getP().size() - fp.size()) + " pairs filtered");
    }

    // compute the objective function expression
    IloNumExpr expr = solver.numExpr();

    // O1
    IloNumExpr exprO1 = solver.numExpr();
    for (int i = 0; i < ins.getNumNodes(); ++i) {
      expr = solver.numExpr();
      expr = solver.sum(expr, 1);
      for (int k = 0; k < ins.getMaxClusterNumber(); ++k) expr = solver.diff(expr, y.get(k, i));
      exprO1 = solver.sum(exprO1, solver.prod(w1, expr));
    }
    // O2
    IloNumExpr exprO2 = solver.numExpr();
    for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
      exprO2 = solver.sum(exprO2, solver.prod(w2, d.get(k)));
    }
    // O3
    IloNumExpr exprO3 = solver.numExpr();
    for (OrderedPair p : fp) {
      double dmax = ins.getDMax();
      double dist = ins.getD(p.i, p.j);
      IloNumVar wvar = w.get(p.i, p.j);
      exprO3 = solver.sum(exprO3, solver.prod(w3 * (dmax - dist), wvar));
    }
    expr = solver.sum(exprO1, solver.sum(exprO2, exprO3));
    solver.add(solver.objective(IloObjectiveSense.Minimize, expr));

    // packing constraints
    for (int i = 0; i < ins.getNumNodes(); ++i) {
      expr = solver.numExpr();
      for (int k = 0; k < ins.getMaxClusterNumber(); ++k) expr = solver.sum(expr, y.get(k, i));
      solver.addLe(expr, 1);
    }

    // root uniqueness constraints
    for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
      expr = solver.numExpr();
      for (int i = 0; i < ins.getNumNodes(); ++i) expr = solver.sum(expr, r.get(k, i));
      solver.addLe(expr, 1);
    }

    // f,y linking constraints

    // OUT
    for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
      for (int i = 0; i < ins.getNumNodes(); ++i) {
        expr = solver.numExpr();
        for (Integer j : ins.getOutcut(i)) {
          expr = solver.sum(expr, f.get(k, i, j));
        }
        expr = solver.diff(expr, solver.prod(ins.getMaxClusterSize() - 2, y.get(k, i)));
        solver.addLe(expr, 0);
      }
    }

    // f,r linking constraints
    for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
      for (int i = 0; i < ins.getNumNodes(); ++i) {
        solver.addLe(f.get(k, superRoot, i), solver.prod(ins.getMaxClusterSize(), r.get(k, i)));
      }
    }

    //		//flow generation constraints
    //		for(int k=0;k<ins.getMaxClusterNumber();++k){
    //			expr=solver.numExpr();
    //			for(int i=0;i<ins.getNumNodes();++i){
    //				expr=solver.sum(expr,f.get(k,superRoot,i));
    //				expr=solver.diff(expr, y.get(k, i));
    //			}
    //			solver.addEq(expr, 0);
    //		}

    // flow conservation constraints
    for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
      for (int i = 0; i < ins.getNumNodes(); ++i) {
        expr = solver.numExpr();
        for (Integer j : ins.getOutcut(i)) {
          expr = solver.diff(expr, f.get(k, i, j));
          expr = solver.sum(expr, f.get(k, j, i));
        }
        // i can receive flow also from the super root
        expr = solver.sum(expr, f.get(k, superRoot, i));
        solver.addEq(expr, y.get(k, i));
      }
    }

    // delta definition constraints
    for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
      expr = solver.numExpr();
      for (int i = 0; i < ins.getNumNodes(); ++i) {
        expr = solver.sum(expr, y.get(k, i));
      }
      solver.addLe(solver.diff(expr, ins.getLambda()), d.get(k));
      solver.addGe(solver.diff(expr, ins.getLambda()), solver.prod(-1, d.get(k)));
    }

    // w var definition constraints
    for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
      for (OrderedPair p : fp) {
        expr = solver.numExpr();
        expr = solver.sum(expr, w.get(p.i, p.j));
        expr = solver.diff(expr, y.get(k, p.i));
        expr = solver.diff(expr, y.get(k, p.j));
        expr = solver.sum(expr, 1);
        solver.addGe(expr, 0);
      }
    }

    if (isActiveExtension(Extension.ROOT_MINIMIZATION_CONSTRAINTS)) {
      for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
        for (OrderedPair p : fp) {
          expr = solver.sum(y.get(k, p.i), r.get(k, p.j));
          solver.addLe(expr, 1);
        }
      }
    }

    if (isActiveExtension(Extension.POLYNOMIAL_ORBITOPE)) {
      // first family
      for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
        expr = solver.numExpr();
        for (int v = 0; v <= k - 1; ++v) {
          expr = solver.sum(expr, y.get(k, v));
        }
        solver.addEq(expr, 0);
      }
      // second family
      for (int k = 1; k < ins.getMaxClusterNumber(); ++k) {
        for (int v = k; v < ins.getNumNodes(); ++v) {
          expr = solver.numExpr();
          expr = solver.sum(expr, y.get(k, v));
          for (int u = k - 1; u <= v - 1; ++u) {
            expr = solver.diff(expr, y.get(k - 1, u));
          }
          solver.addLe(expr, 0);
        }
      }
    }

    if (isActiveExtension(Extension.EXPONENTIAL_ORBITOPE)) {
      System.err.println("Extension " + Extension.EXPONENTIAL_ORBITOPE + " not implemented yet");
      throw new IllegalArgumentException();
    }

    if (isActiveExtension(Extension.FLOW_LB)) {
      // f,x lower bound
      for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
        for (int i = 0; i < ins.getNumNodes(); ++i) {
          expr = solver.numExpr();
          for (Integer j : ins.getOutcut(i)) {
            expr = solver.sum(expr, f.get(k, j, i));
          }
          expr = solver.sum(expr, f.get(k, superRoot, i));
          solver.addGe(expr, y.get(k, i));
          solver.addLe(r.get(k, i), f.get(k, superRoot, i));
        }
      }
    }

    solver.setParam(IloCplex.DoubleParam.TiLim, timeout);
    if (isActiveExtension(Extension.ROOT_BRANCHING_PRIORITY)) {
      // Add branching priorities to the root variables
      for (int k = 0; k < ins.getMaxClusterNumber(); ++k)
        for (int i = 0; i < ins.getNumNodes(); ++i) solver.setPriority(r.get(k, i), 100);
    }
    solver.setParam(IloCplex.IntParam.ParallelMode, 1);
    solver.solve();
    System.out.println("STATE=" + solver.getStatus());
    System.out.println("NN=" + solver.getNnodes());
    System.out.println("LB=" + solver.getBestObjValue());

    // If we have a feasible solution we save it
    if (solver.getStatus() == IloCplex.Status.Feasible
        || solver.getStatus() == IloCplex.Status.Optimal) {
      for (int k = 0; k < ins.getMaxClusterNumber(); ++k) {
        for (int v = 0; v < ins.getNumNodes(); ++v) {
          if (FloatUtils.eq(solver.getValue(y.get(k, v)), 1.0)) {
            sol.insert(k, v);
          }
        }
      }
    }

    return solver.getObjValue();
  }