Initializing help system before first use

Capital budgeting - Using multi-objective optimization


Type: Capital budgeting
Rating: 2 (easy-medium)
Description: Capital budgeting example, solved using three multi-objective approaches:
  • Lexicographic approach, solving first to minimize capital expended and second to maximize return
  • Blended approach, solving a weighted combination of both objectives simultaneously
  • Lexicographic approach, with the objective priorities reversed
The model version Capbgt2l demonstrates the formuation of logical constraints.
File(s): CapitalBudgeting.cpp, Capbgt2l.cpp


CapitalBudgeting.cpp
// (c) 2024-2025 Fair Isaac Corporation
#include <stdexcept> // For throwing exceptions
#include <xpress.hpp>

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

/*
 * Capital budgeting example, showing multi-objective optimization.
 * The problem is solved using three multi-objective approaches:
 *  - Lexicographic approach, solving first to minimize capital expended and
 * second to maximize return
 *  - Blended approach, solving a weighted combination of both objectives
 * simultaneously
 *  - Lexicographic approach, with the objective priorities reversed
 */

class CapitalBudgeting {
public:
  // Required capital for each project
  const std::vector<double> CAPITAL = {104000, 53000, 29000, 187000,
                                       98000,  32000, 75000, 200000};
  // Required personnel for each project
  const std::vector<double> PERSONNEL = {22, 12, 7, 36, 24, 10, 20, 41};
  // Expected return of each project
  const std::vector<double> RETURN = {124000, 74000, 42000, 188000,
                                      108000, 56000, 88000, 225000};

  const double CAPITAL_TARGET = 478000; // Target capital to invest
  const double ROI_TARGET = 550000;     // Target return on investment
  const int PERSONNEL_MAX = 106;        // Available personnel

  CapitalBudgeting(){}; // Class constructor
  void model(); // Function to make variables, constraints, and objective
  void solveThreeTimes(); // To optimize with different variations of objectives
  void printSolution(std::string title); // To print the solution to console

private:
  XpressProblem prob;

  // Binary decision variables indicating which projects will be implemented
  std::vector<Variable> selectProject;

  Inequality personnel;  // Constraint for the number of personnel available
  LinExpression capital; // Primary objective: minimize capital expended
  LinExpression roi;     // Secondary objective: maximize return on investment
};

void CapitalBudgeting::model() {

  /* VARIABLES */

  // Binary decision variables indicating which projects will be implemented
  selectProject = prob.addVariables(CAPITAL.size())
                      .withType(ColumnType::Binary)
                      .withName("selectProject_%d")
                      .toArray();

  /* CONSTRAINTS */

  // Constraint: at least 3 projects must be implemented
  prob.addConstraint(sum(selectProject) >= 3);

  // Constraint for the number of personnel available
  personnel = prob.addConstraint(scalarProduct(selectProject, PERSONNEL) <=
                                 PERSONNEL_MAX);

  /* OBJECTIVES */

  // Primary objective: minimize capital expended
  capital = scalarProduct(selectProject, CAPITAL);
  prob.setObjective(capital, ObjSense::Minimize);

  // Secondary objective: maximize return on investment
  roi = scalarProduct(selectProject, RETURN);
  // We add the second objective with priority=0 (same as default given
  // to the first objective) and weight=-1 (to maximize this expression)
  prob.addObjective(roi, 0, -1);
};

void CapitalBudgeting::solveThreeTimes() {
  // Set the first objective (with id=0) to priority=1 to give higher priority
  // than 2nd objective
  prob.setObjIntControl(0, ObjControl::Priority, 1);
  // Optimize & print
  prob.optimize();
  printSolution("*** Higher priority for 'Minimize Capital' objective ***");

  // Now set the same priority for both objectives (i.e. we set the the second
  // objective (with id=1) to have priority=1). This will result in a single
  // solve using the weighted sum of the two objectives.
  prob.setObjIntControl(1, ObjControl::Priority, 1);
  // Optimize & print
  prob.optimize();
  printSolution("*** Equal priority for Both objectives ***");

  // Finally, give the first objective (with id=0) a lower priority (=0)
  prob.setObjIntControl(0, ObjControl::Priority, 0);
  // Optimize & print
  prob.optimize();
  printSolution("*** Higher priority for 'Maximize Return' objective ***");
}

void CapitalBudgeting::printSolution(std::string title) {
  std::cout << std::endl << title << std::endl;

  // Check the solution status
  if (prob.attributes.getSolveStatus() != SolveStatus::Completed) {
    std::cout << "Problem not solved" << std::endl;
  } else if (prob.attributes.getSolStatus() != SolStatus::Optimal &&
             prob.attributes.getSolStatus() != SolStatus::Feasible) {
    throw std::runtime_error("Optimization failed with status " +
                             to_string(prob.attributes.getSolStatus()));
  }

  std::cout << "  Objectives: " << prob.attributes.getObjectives() << "\t";
  std::cout << "solved objectives: " << prob.attributes.getSolvedObjs()
            << std::endl;

  std::vector<double> sol = prob.getSolution();
  double capitalUsed = capital.evaluate(sol);
  double roiGained = roi.evaluate(sol);

  if (capitalUsed > CAPITAL_TARGET)
    std::cout << "  Unable to meet Capital target" << std::endl;
  if (roiGained < ROI_TARGET)
    std::cout << "  Unable to meet Return target" << std::endl;

  std::cout << "  Projects undertaken:";
  for (std::size_t p = 0; p < selectProject.size(); p++) {
    if (selectProject[p].getSolution() == 1.0) {
      std::cout << " " << p;
    }
  }
  std::cout << std::endl;

  std::cout << "  Used capital: $" << capitalUsed
            << (CAPITAL_TARGET >= capitalUsed ? " unused: $" : " overused: $")
            << std::abs(CAPITAL_TARGET - capitalUsed) << std::endl;
  std::cout << "  Total return: $" << roiGained << std::endl;
  std::cout << "  Unused personnel: " << int(personnel.getSlack()) << " persons"
            << std::endl;
};

int main() {
  try {
    CapitalBudgeting capbudget_problem;
    capbudget_problem.model();
    capbudget_problem.solveThreeTimes();
    return 0;
  } catch (std::exception &e) {
    std::cout << "Exception: " << e.what() << std::endl;
    return -1;
  }
}

Capbgt2l.cpp
// (c) 2024-2025 Fair Isaac Corporation
#include <stdexcept> // For throwing exceptions
#include <xpress.hpp>

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

/** Capital budgeting problem.
 * Illustrates logical conditions, formulation using logic constraints
 * and indicator constraints.
 */

// A project.
class Project {
public:
  const std::string name;  // The name of this project.
  const double payout;     // Payout for the project.
  const double investment; // Capital investment for the project.
  const int personnel;     // Number of personnel required for the project.

  Project(std::string name, double payout, double investment, int personnel)
      : name(name), payout(payout), investment(investment),
        personnel(personnel) {}

  // For printing
  std::string toString() const { return name; }
};

// The projects used in this example.
const std::vector<Project> projectArray = {
    Project("Alpha", 124000.0, 104000.0, 22),
    Project("Beta", 74000.0, 53000.0, 12),
    Project("Gamma", 42000.0, 29000.0, 7),
    Project("Delta", 188000.0, 187000.0, 36),
    Project("Epsilon", 108000.0, 98000.0, 24),
    Project("Zeta", 56000.0, 32000.0, 10),
    Project("Eta", 88000.0, 75000.0, 20),
    Project("Theta", 225000.0, 200000.0, 41)};

// The resource constraints used in this example.
const double budget = 478000.0;
const int workforce = 106;

int main() {
  try {
    // Create a problem instance with or without verbose messages printed to
    // Console
    XpressProblem prob;
    // prob.callbacks.addMessageCallback(XpressProblem::console);

    /* VARIABLES */

    // Whether each project should be invested in or not
    std::vector<Variable> x =
        prob.addVariables(projectArray.size())
            .withType(ColumnType::Binary)
            .withName("x%d")
            .toArray();

    /* RESOURCE AVAILABILITY CONSTRAINTS */

    // Investment limit: sum of investments of all undertaken projects should
    // not exceed budget
    Expression requiredInvestment =
        sum(projectArray.size(),
            [&](auto i) { return x[i].mul(projectArray[i].investment); });
    Inequality investmentLimit =
        prob.addConstraint(requiredInvestment <= budget);

    // Workforce limit: sum of personnel committed of all undertaken projects
    // should not exceed workforce
    LinExpression requiredWorkforce = LinExpression::create();
    for (std::size_t i = 0; i < projectArray.size(); i++) {
      requiredWorkforce.addTerm(x[i], projectArray[i].personnel);
    }
    Inequality workforceLimit =
        prob.addConstraint(requiredWorkforce <= workforce);

    // Project alpha can only be done if both gamma and zeta happen
    prob.addConstraint(x[0] <= x[2]);
    prob.addConstraint(x[0] <= x[5]);

    // Project zeta can only be done if project epsilon happens
    prob.addConstraint(x[5] <= x[4]);

    // Projects alpha and beta as well as gamma and delta can only happen
    // together
    prob.addConstraint(x[0] == x[1]);
    prob.addConstraint(x[2] == x[3]);

    // Exactly one of those pairs should be invested in, i.e., if project alpha
    // is performed, neither gamma nor delta can be invested in, and if project
    // alpha does not happen, then projects gamma and delta have to be performed
    prob.addConstraint(x[0].ifThen(sum(x[2], x[3]) == 0.0));
    prob.addConstraint(x[0].ifNotThen(sum(x[2], x[3]) == 2.0));

    /* OBJECTIVE */

    // Objective function: sum of payouts of all undertaken projects
    Expression totalProfit =
        sum(projectArray.size(),
            [&](auto i) { return x[i].mul(projectArray[i].payout); });
    prob.setObjective(totalProfit, xpress::ObjSense::Maximize);

    /* INSPECT, SOLVE & PRINT */

    // Dump the problem to disk so that we can inspect it.
    prob.writeProb("capbgt2l.lp");

    // Solve
    prob.optimize();

    // Check the solution status
    if (prob.attributes.getSolStatus() != SolStatus::Optimal &&
        prob.attributes.getSolStatus() != SolStatus::Feasible) {
      std::ostringstream oss;
      oss << prob.attributes
                 .getSolStatus(); // Convert xpress::SolStatus to String
      throw std::runtime_error("Optimization failed with status " + oss.str());
    }

    // Get the solution and print it
    std::vector<double> sol = prob.getSolution();
    std::cout << std::endl << "*** Solution ***" << std::endl;
    std::cout << "Objective: " << prob.attributes.getObjVal() << std::endl;

    // Print the interesting slacks
    std::cout << "Remaining Budget: " << investmentLimit.getSlack()
              << " (out of " << budget << ")" << std::endl;
    std::cout << "Remaining Workers: " << workforceLimit.getSlack()
              << " (out of " << workforce << ")" << std::endl;

    // Print out the variables
    for (std::size_t i = 0; i < projectArray.size(); ++i) {
      if (x[i].getValue(sol) > 0.5) {
        std::cout << "Undertaking project " << projectArray[i].toString()
                  << std::endl;
      }
    }
    return 0;
  } catch (std::exception &e) {
    std::cout << "Exception: " << e.what() << std::endl;
    return -1;
  }
}

© 2001-2025 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.