/** Solves a standard form LP problem in the form of min(c) s.t. A.x = b lb <= x <= ub */
  protected int optimizeStandardLP(int nOfSlackVariables) throws Exception {
    log.info("optimizeStandardLP");

    LPOptimizationRequest lpRequest = getLPOptimizationRequest();
    if (log.isDebugEnabled() && lpRequest.isDumpProblem()) {
      log.debug("LP problem: " + lpRequest.toString());
    }

    LPOptimizationResponse lpResponse;
    if (lpRequest.isPresolvingDisabled()) {
      // optimization
      LPPrimalDualMethod opt = new LPPrimalDualMethod(minLBValue, maxUBValue);
      opt.setLPOptimizationRequest(lpRequest);
      if (opt.optimizePresolvedStandardLP() == OptimizationResponse.FAILED) {
        return OptimizationResponse.FAILED;
      }
      lpResponse = opt.getLPOptimizationResponse();
      setLPOptimizationResponse(lpResponse);
    } else {
      // presolving
      LPPresolver lpPresolver = new LPPresolver();
      lpPresolver.setAvoidScaling(lpRequest.isRescalingDisabled());
      lpPresolver.setAvoidFillIn(lpRequest.isAvoidPresolvingFillIn());
      lpPresolver.setAvoidIncreaseSparsity(lpRequest.isAvoidPresolvingIncreaseSparsity());
      testPresolver = lpPresolver; // just for testing
      lpPresolver.setNOfSlackVariables((short) nOfSlackVariables);
      lpPresolver.presolve(getC(), getA(), getB(), getLb(), getUb());
      int presolvedDim = lpPresolver.getPresolvedN();

      if (presolvedDim == 0) {
        // deterministic problem
        log.debug("presolvedDim : " + presolvedDim);
        log.debug("deterministic LP problem");
        lpResponse = new LPOptimizationResponse();
        lpResponse.setReturnCode(OptimizationResponse.SUCCESS);
        lpResponse.setSolution(new double[] {});
      } else {
        // solving the presolved problem
        DoubleMatrix1D presolvedC = lpPresolver.getPresolvedC();
        DoubleMatrix2D presolvedA = lpPresolver.getPresolvedA();
        DoubleMatrix1D presolvedB = lpPresolver.getPresolvedB();
        if (log.isDebugEnabled()) {
          if (lpPresolver.getPresolvedYlb() != null) {
            log.debug("Ylb: " + ArrayUtils.toString(lpPresolver.getPresolvedYlb().toArray()));
            log.debug("Yub: " + ArrayUtils.toString(lpPresolver.getPresolvedYub().toArray()));
          }
          if (lpPresolver.getPresolvedZlb() != null) {
            log.debug("Zlb: " + ArrayUtils.toString(lpPresolver.getPresolvedZlb().toArray()));
            log.debug("Zub: " + ArrayUtils.toString(lpPresolver.getPresolvedZub().toArray()));
          }
        }

        // new LP problem (the presolved problem)
        LPOptimizationRequest presolvedLPRequest = lpRequest.cloneMe();
        presolvedLPRequest.setC(presolvedC);
        presolvedLPRequest.setA(presolvedA);
        presolvedLPRequest.setB(presolvedB);
        presolvedLPRequest.setLb(lpPresolver.getPresolvedLB());
        presolvedLPRequest.setUb(lpPresolver.getPresolvedUB());
        presolvedLPRequest.setYlb(lpPresolver.getPresolvedYlb());
        presolvedLPRequest.setYub(lpPresolver.getPresolvedYub());
        presolvedLPRequest.setZlb(lpPresolver.getPresolvedZlb());
        presolvedLPRequest.setZub(lpPresolver.getPresolvedZub());
        if (getInitialPoint() != null) {
          presolvedLPRequest.setInitialPoint(lpPresolver.presolve(getInitialPoint().toArray()));
        }
        if (getNotFeasibleInitialPoint() != null) {
          presolvedLPRequest.setNotFeasibleInitialPoint(
              lpPresolver.presolve(getNotFeasibleInitialPoint().toArray()));
        }

        // optimization
        // NB: because of rescaling during the presolving phase, minLB and maxUB could have been
        // rescaled
        double rescaledMinLBValue =
            (Double.isNaN(lpPresolver.getMinRescaledLB()))
                ? this.minLBValue
                : lpPresolver.getMinRescaledLB();
        double rescaledMaxUBValue =
            (Double.isNaN(lpPresolver.getMaxRescaledUB()))
                ? this.maxUBValue
                : lpPresolver.getMaxRescaledUB();
        LPPrimalDualMethod opt = new LPPrimalDualMethod(rescaledMinLBValue, rescaledMaxUBValue);
        opt.setLPOptimizationRequest(presolvedLPRequest);
        if (opt.optimizePresolvedStandardLP() == OptimizationResponse.FAILED) {
          return OptimizationResponse.FAILED;
        }
        lpResponse = opt.getLPOptimizationResponse();
      }

      // postsolving
      double[] postsolvedSolution = lpPresolver.postsolve(lpResponse.getSolution());
      lpResponse.setSolution(postsolvedSolution);
      setLPOptimizationResponse(lpResponse);
    }

    return lpResponse.getReturnCode();
  }
    // formulate and generate the solution, if necessary
    private void solve() {
      if (solved) return;

      // min obj.x s.t. a.x=b, lb <= x <= ub
      // x is energy use per block for size hours, b is slack var per block.
      // Block is a shift, or portion of shift with constant price.
      // For multi-hour blocks, energy use is evenly distributed across hours
      // after solution.
      Date start = new Date();
      int shifts = needs.length;

      // Create blocks that break on both shift boundaries and tariff price
      // boundaries.
      ShiftBlock[] blocks = makeBlocks(shifts);
      int columns = blocks.length;
      int blockIndex = -1;

      double[] obj = new double[columns + shifts];
      double[][] a = new double[shifts][columns + shifts];
      double[] b = new double[shifts];
      double[] lb = new double[columns + shifts];
      double[] ub = new double[columns + shifts];
      int column = 0;
      double cumulativeMin = 0.0; // this is the primary constraint
      // construct the problem
      for (int i = 0; i < shifts; i++) {
        // one iteration per shift
        // double kwh =
        //    needs[i].getEnergyNeeded() + needs[i].getMaxSurplus();
        // for (int j = 0; j < needs[i].getDuration(); j++) {
        while ((blockIndex < blocks.length - 1)
            && (blocks[blockIndex + 1].getShiftEnergy() == needs[i])) {
          blockIndex += 1;
          // one iteration per block within a shift
          // fill in objective function
          obj[column] = blocks[blockIndex].getCost();
          lb[column] = 0.0;
          ub[column] =
              (needs[i].getEnergyNeeded() + needs[i].getMaxSurplus())
                  * (double) blocks[blockIndex].getDuration()
                  / needs[i].getDuration();
          column += 1;
          // construct cumulative usage constraints
          // a[i][column] = -1.0;
          // time = time.plus(TimeService.HOUR);
        }
        // fill a row up to column
        for (int j = 0; j < column; j++) {
          a[i][j] = -1.0;
        }
        // b vector - one entry per constraint
        double need = needs[i].getEnergyNeeded();
        if (needs[i].getMaxSurplus() < 0.0) need += needs[i].getMaxSurplus();
        cumulativeMin += need;
        b[i] = -cumulativeMin;
        // fill in slack values, one per constraint
        obj[columns + i] = 0.0;
        a[i][columns + i] = 1.0;
        lb[columns + i] = 0.0;
        // upper bound is max possible energy for shift
        ub[columns + i] = (needs[i].getEnergyNeeded() + needs[i].getMaxSurplus());
      }
      // run the optimization
      LPOptimizationRequest or = new LPOptimizationRequest();
      log.debug("Obj: " + Arrays.toString(obj));
      or.setC(obj);
      log.debug("a:");
      for (int i = 0; i < a.length; i++) log.debug(Arrays.toString(a[i]));
      or.setA(a);
      log.debug("b: " + Arrays.toString(b));
      or.setB(b);
      or.setLb(lb);
      log.debug("ub: " + Arrays.toString(ub));
      or.setUb(ub);
      or.setTolerance(1.0e-2);
      LPPrimalDualMethod opt = new LPPrimalDualMethod();
      opt.setLPOptimizationRequest(or);
      try {
        int returnCode = opt.optimize();
        if (returnCode != OptimizationResponse.SUCCESS) {
          log.error(getName() + "bad optimization return code " + returnCode);
        }
        double[] sol = opt.getOptimizationResponse().getSolution();
        Date end = new Date();
        log.info("Solution time: " + (end.getTime() - start.getTime()));
        log.debug("Solution = " + Arrays.toString(sol));
        recordSolution(sol, blocks);
      } catch (Exception e) {
        log.error(e.toString());
      }
      // we call it solved whether or not the solution was successful
      solved = true;
    }
  /** Solves an LP in the form of: min(c) s.t. A.x = b G.x < h lb <= x <= ub */
  @Override
  public int optimize() throws Exception {
    log.info("optimize");

    LPOptimizationRequest lpRequest = getLPOptimizationRequest();
    if (log.isDebugEnabled() && lpRequest.isDumpProblem()) {
      log.debug("LP problem: " + lpRequest.toString());
    }

    // standard form conversion
    LPStandardConverter lpConverter =
        new LPStandardConverter(); // the slack variables will have default unboundedUBValue

    lpConverter.toStandardForm(getC(), getG(), getH(), getA(), getB(), getLb(), getUb());
    int nOfSlackVariables = lpConverter.getStandardS();
    log.debug("nOfSlackVariables: " + nOfSlackVariables);
    DoubleMatrix1D standardC = lpConverter.getStandardC();
    DoubleMatrix2D standardA = lpConverter.getStandardA();
    DoubleMatrix1D standardB = lpConverter.getStandardB();
    DoubleMatrix1D standardLb = lpConverter.getStandardLB();
    DoubleMatrix1D standardUb = lpConverter.getStandardUB();

    // solve the standard form problem
    LPOptimizationRequest standardLPRequest = lpRequest.cloneMe();
    standardLPRequest.setC(standardC);
    standardLPRequest.setA(standardA);
    standardLPRequest.setB(standardB);
    standardLPRequest.setLb(
        ColtUtils.replaceValues(
            standardLb,
            lpConverter.getUnboundedLBValue(),
            minLBValue)); // substitute not-double numbers
    standardLPRequest.setUb(
        ColtUtils.replaceValues(
            standardUb,
            lpConverter.getUnboundedUBValue(),
            maxUBValue)); // substitute not-double numbers
    if (getInitialPoint() != null) {
      standardLPRequest.setInitialPoint(
          lpConverter.getStandardComponents(getInitialPoint().toArray()));
    }
    if (getNotFeasibleInitialPoint() != null) {
      standardLPRequest.setNotFeasibleInitialPoint(
          lpConverter.getStandardComponents(getNotFeasibleInitialPoint().toArray()));
    }

    // optimization
    LPPrimalDualMethod opt = new LPPrimalDualMethod(minLBValue, maxUBValue);
    opt.setLPOptimizationRequest(standardLPRequest);
    if (opt.optimizeStandardLP(nOfSlackVariables) == OptimizationResponse.FAILED) {
      return OptimizationResponse.FAILED;
    }

    // back to original form
    LPOptimizationResponse lpResponse = opt.getLPOptimizationResponse();
    double[] standardSolution = lpResponse.getSolution();
    double[] originalSol = lpConverter.postConvert(standardSolution);
    lpResponse.setSolution(originalSol);
    setLPOptimizationResponse(lpResponse);
    return lpResponse.getReturnCode();
  }