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