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
#include <xpress.hpp>
#include <stdexcept>  // For throwing exceptions

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

/*
 * 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(static_cast<int>(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
#include <xpress.hpp>
#include <stdexcept>  // For throwing exceptions

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

/*
 * Capital budgeting problem. Illustrates: Logical conditions,
   Formulation using logic 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(static_cast<int>(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(static_cast<int>(projectArray.size()), [&](int 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(static_cast<int>(projectArray.size()), [&](int 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.0) {
                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-2024 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.