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