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

Burglar - Formulating logical constraints

Description
This example shows how to setup a simple knapsack type problem in which an item selection should be made maximally profitable under some weight restriction. We first solve a pure knapsack problem. Then, we refine the constraints by explicitly forbidding certain item combinations, thereby showing multiple ways how to create indicator constraints.

burglar_cpp.zip[download all files]

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





BinBurglar.cpp

#include <xpress.hpp>
#include <stdexcept>  // For throwing exceptions

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

/*
 * Knapsack problem. This example shows how to setup a simple knapsack type
 * problem in which an item selection should be made maximally profitable under
 * some weight restriction. We first solve a pure knapsack problem. Then, we
 * add more constraints by explicitly forbidding certain item combinations,
 * thereby showing multiple ways how to create indicator constraints.
*/

std::vector<double> values = { 15, 100, 90, 60, 40, 15, 10, 1 };
std::vector<double> weights = { 2, 20, 20, 30, 40, 30, 60, 10 };
std::vector<std::string> names = { "camera", "necklace", "vase", "picture", "tv", "video", "chest", "brick" };
int MAX_WEIGHT = 102;

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

        // Binary variables, 1 if we put item i in the knapsack, 0 if not
        std::vector<Variable> x = prob.addVariables(static_cast<int>(values.size()))
            .withType(ColumnType::Binary)
            .withName([&](int i) { return names[i]; })
            .toArray();

        // Weight restriction on the knapsack
        prob.addConstraint(scalarProduct(x, weights) <= double(MAX_WEIGHT));

        // Objective: Maximze total value
        prob.setObjective(scalarProduct(x, values), xpress::ObjSense::Maximize);

        // Optimize
        // prob.writeProb("BinBurglar.lp");
        prob.optimize();

        // Check the solution status
        if (prob.attributes.getSolStatus() != xpress::SolStatus::Optimal ) {
            std::ostringstream oss; oss << prob.attributes.getSolStatus(); // Convert xpress::SolStatus to String
            throw std::runtime_error("Problem not solved to optimality: " + oss.str());
        }

        // Print out the solution
        std::cout << std::endl << "*** Solution ***" << std::endl;
        std::cout << "Objective: " << prob.attributes.getObjVal() << std::endl;
        std::vector<double> sol = prob.getSolution();
        for (Variable v : x) std::cout << v.getName() << " = " << v.getValue(sol) << std::endl;
        std::cout << std::endl;


        /*
        * Now to show some additionaly functionaly, we add constraints to consider the items
        * "vase" and "picture" as one pair and the items "tv" and "video" as a second pair so
        * that the pairs are always taken together
        */
        // Create a map (using for_each and lambda function) to facilitate easy look up of the variables by name
        std::map<std::string, Variable> nameToVariableMap;
        std::for_each(x.begin(), x.end(), [&](Variable var) {
            nameToVariableMap[var.getName()] = var;
        });

        // Add constraint x["vase"] == x["picture"]
        prob.addConstraint(nameToVariableMap["vase"] == nameToVariableMap["picture"]);
        // Add constraint x["tv"] == x["video"]
        prob.addConstraint(nameToVariableMap["tv"] == nameToVariableMap["video"]);

        /*
        * Logical constraint: Take exactly one of the two pairs of variables (i.e. take either the
        * first pair "vase" and "picture" or the second pair "tv" and "video", but not both pairs)
        */
        // x["vase"]=1 -> x["tv"]+x["video"]=0 */
        Variable vase = nameToVariableMap["vase"];
        Expression tvPlusVideo = nameToVariableMap["tv"] + nameToVariableMap["video"];
        prob.addConstraint(vase.ifThen(tvPlusVideo <= 0.0));

        // x["vase"]=0 -> x["tv"]+x["video"]=2
        // Instead of `vase.ifNotThen()`, we can use this alternative way to create the implication constraint
        Inequality enablePairTwo = prob.addConstraint(tvPlusVideo >= 2.0);
        prob.setIndicator(vase, false, enablePairTwo);

        // By inspecting the problem, we can see the two methods of adding implication constraints
        // indeed yield similar constraint
        prob.writeProb("BinBurglar.lp");

        // Optimize again
        prob.optimize();

        // Check the solution status again
        if (prob.attributes.getSolStatus() != xpress::SolStatus::Optimal ) {
            std::ostringstream oss; oss << prob.attributes.getSolStatus(); // Convert xpress::SolStatus to String
            throw std::runtime_error("Problem not solved to optimality: " + oss.str());
        }

        // Print out the new solution
        std::cout << std::endl << "*** Solution ***" << std::endl;
        std::cout << "Objective: " << prob.attributes.getObjVal() << std::endl;
        sol = prob.getSolution();
        for (Variable v : x) std::cout << v.getName() << " = " << v.getValue(sol) << std::endl;
        std::cout << std::endl;
        return 0;
    }
    catch (std::exception& e) {
        std::cout << "Exception: " << e.what() << std::endl;
        return -1;
    }
}

Back to examples browserPrevious exampleNext example