FICO
FICO Xpress Optimization Examples Repository
FICO Optimization Community FICO Xpress Optimization Home
Back to examples browserPrevious exampleNext example

Solving a non-linear problem by recursion

Description
A non-linear problem (quadratic terms in the constraints) is solved via successive linear programming (SLP, also referred to as recursion). The constraint coefficients are changed iteratively.

recurse_cpp.zip[download all files]

Source Files
By clicking on a file name, a preview is opened at the bottom of this page.
RecursiveFinancialPlanning.cpp[download]





RecursiveFinancialPlanning.cpp

// (c) 2024-2024 Fair Isaac Corporation

/**
 * Recursion solving a non-linear financial planning problem. The problem is to
 * solve
 * <pre>
 *     net(t) = Payments(t) - interest(t)
 *     balance(t) = balance(t-1) - net(t)
 *     interest(t) = (92/365) * balance(t) * interest_rate
 *   where
 *     balance(0) = 0
 *     balance[T] = 0
 *   for interest_rate
 * </pre>
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::sum;

int const T = 6;

/* Data */
/* An INITIAL GUESS as to interest rate x */
double const X = 0.00;
/* An INITIAL GUESS as to balances b(t) */
std::vector<double> B{1, 1, 1, 1, 1, 1};
std::vector<double> P{-1000, 0, 0, 0, 0, 0};                 /* Payments */
std::vector<double> R{206.6, 206.6, 206.6, 206.6, 206.6, 0}; /* " */
std::vector<double> V{-2.95, 0, 0, 0, 0, 0};                 /* " */

struct RecursiveFinancialPlanning {
  /** The optimizer instance. */
  XpressProblem prob;
  // Variables and constraints
  std::vector<Variable> b; /**< Balance */
  Variable x;              /**< Interest rate */
  Variable dx;             /**< Change to x */
  // Constraints that will be modified.
  std::vector<Inequality> interest;
  Inequality ctrd;

  void printIteration(int it, double variation) {
    auto sol = prob.getSolution();
    std::cout << "---------------- Iteration " << it << " ----------------"
              << std::endl;
    std::cout << "Objective: " << prob.attributes.getObjVal() << std::endl;
    std::cout << "Variation: " << variation << std::endl;
    std::cout << "x: " << x.getValue(sol) << std::endl;
    std::cout << "----------------------------------------------" << std::endl;
  }

  void printProblemSolution() {
    auto sol = prob.getSolution();
    std::cout << "Objective: " << prob.attributes.getObjVal() << std::endl;
    std::cout << "Interest rate: " << (x.getValue(sol) * 100) << " percent"
              << std::endl;
    std::cout << "Variables:" << std::endl << "t";
    for (Variable const &v : prob.getVariables()) {
      std::cout << "[" << v.getName() << ": " << v.getValue(sol) << "] ";
    }
    std::cout << std::endl;
  }

  /***********************************************************************/
  void modFinNLP() {
    interest = std::vector<Inequality>(T);

    // Balance
    b = prob.addVariables(T)
            .withName("b_%d")
            .withLB(XPRS_MINUSINFINITY)
            .toArray();

    // Interest rate
    x = prob.addVariable("x");

    // Interest rate change
    dx = prob.addVariable(XPRS_MINUSINFINITY, XPRS_PLUSINFINITY,
                          ColumnType::Continuous, "dx");

    std::vector<Variable> i = prob.addVariables(T).withName("i_%d").toArray();

    std::vector<Variable> n = prob.addVariables(T)
                                  .withName("n_%d")
                                  .withLB(XPRS_MINUSINFINITY)
                                  .toArray();

    std::vector<Variable> epl =
        prob.addVariables(T).withName("epl_%d").toArray();

    std::vector<Variable> emn =
        prob.addVariables(T).withName("emn_%d").toArray();

    // Fixed variable values
    i[0].fix(0);
    b[T - 1].fix(0);

    // Objective
    prob.setObjective(sum(epl) + sum(emn), ObjSense::Minimize);

    // Constraints
    // net = payments - interest
    prob.addConstraints(T, [&](auto t) {
      return (n[t] == (P[t] + R[t] + V[t]) - i[t])
          .setName(xpress::format("net_%d", t));
    });

    // Money balance across periods
    prob.addConstraints(T, [&](auto t) {
      if (t > 0)
        return (b[t] == b[t - 1]).setName(xpress::format("bal_%d", t));
      else
        return (b[t] == 0.0).setName(xpress::format("bal_%d", t));
    });

    // i(t) = (92/365)*( b(t-1)*X + B(t-1)*dx ) approx.
    for (int t = 1; t < T; ++t) {
      LinExpression iepx = LinExpression::create();
      iepx.addTerm(b[t - 1], X);
      iepx.addTerm(dx, B[t - 1]);
      iepx.addTerm(epl[t], 1.0);
      iepx.addTerm(emn[t], 1.0);
      interest[t] = prob.addConstraint((365 / 92.0) * i[t] == iepx)
                        .setName(xpress::format("int_%d", t));
    }

    // x = dx + X
    ctrd = prob.addConstraint((x == dx + X).setName("def"));
    prob.writeProb("Recur.lp", "l");
  }

  /**************************************************************************/
  /* Recursion loop (repeat until variation of x converges to 0): */
  /* save the current basis and the solutions for variables b[t] and x */
  /* set the balance estimates B[t] to the value of b[t] */
  /* set the interest rate estimate X to the value of x */
  /* reload the problem and the saved basis */
  /* solve the LP and calculate the variation of x */
  /**************************************************************************/
  void solveFinNLP() {
    double variation = 1.0;

    prob.callbacks.addMessageCallback(XpressProblem::console);
    prob.controls.setMipLog(0);

    // Switch automatic cut generation off
    prob.controls.setCutStrategy(XPRS_CUTSTRATEGY_NONE);
    // Solve the problem
    prob.optimize();
    if (prob.attributes.getSolStatus() != SolStatus::Optimal)
      throw std::runtime_error("failed to optimize with status " +
                               to_string(prob.attributes.getSolStatus()));

    for (int it = 1; variation > 1e-6; ++it) {
      // Optimization solution
      auto sol = prob.getSolution();

      printIteration(it, variation);
      printProblemSolution();
      // Change coefficients in interest[t]
      // Note: when inequalities are added to a problem then all variables are
      // moved to the left-hand side and all constants are moved to the
      // right-hand side. Since we are changing these extracted inequalities
      // directly, we have to use negative coefficients below.
      for (int t = 1; t < T; ++t) {
        prob.chgCoef(interest[t], dx, -b[t - 1].getValue(sol));
        prob.chgCoef(interest[t], b[t - 1], -x.getValue(sol));
      }

      // Change constant term of ctrd
      ctrd.setRhs(x.getValue(sol));

      // Solve the problem
      prob.optimize();
      auto newsol = prob.getSolution();
      if (prob.attributes.getSolStatus() != SolStatus::Optimal)
        throw std::runtime_error("failed to optimize with status " +
                                 to_string(prob.attributes.getSolStatus()));
      variation = fabs(x.getValue(newsol) - x.getValue(sol));
    }
    printProblemSolution();
  }
};

int main() {
  RecursiveFinancialPlanning planning;
  planning.modFinNLP();
  planning.solveFinNLP();
  return 0;
}

Back to examples browserPrevious exampleNext example