Initializing help system before first use

Contract - Semi-continuous variables


Type: Contract allocation
Rating: 3 (intermediate)
Description: A small MIP-problem example demonstrating how to define semi-continuous variables.
File(s): ContractAllocation.cpp


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

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

/* Shows the use of a matrix of variables (i.e. x[i][j]) and different ways to specify contraints*/

/* Contract Allocation example. Given a number of districts, we have to produce enough
 * of a certain resource to fulfill all contracts. Each district has a maximum amount of
 * resource they can produce. Minimize costs to fulfill contracts.
 */

const int NDISTRICT = 6;  // Number of districts
const int NCONTRACT = 10; // Number of contracts

const std::vector<double> output = { 50, 40, 10, 20, 70, 50 };  // Max. output per district
const std::vector<double> cost   = { 50, 20, 25, 30, 45, 40 };  // Cost per unit per district
// Required volume of resources by contracts:
const std::vector<double> volume = { 20, 10, 30, 15, 20, 30, 10, 50, 10, 20 };


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

        /* VARIABLES */

        // Variables indicating whether a project is chosen
        std::vector<std::vector<Variable>> x = prob.addVariables(NDISTRICT, NCONTRACT)
                .withType(ColumnType::Binary)
                .withName("x_d%d_c%d")
                .toArray();

        // Quantities allocated to contractors
        std::vector<std::vector<Variable>> q = prob.addVariables(NDISTRICT, NCONTRACT)
                        .withType(ColumnType::SemiContinuous)
                        .withUB([&](int d, int){ return output[d]; })
                        .withLimit(5)
                        .withName("q_d%d_c%d")
                        .toArray();

        /* CONSTRAINTS */

        // "Size": Produce the required volume of resource for each contract
        // for all c in [0,NCONTRACT]
        //      sum(d in [0,NDISTRICT]) q[d][c] >= volume[c]
        prob.addConstraints(NCONTRACT, [&](int c) {
            SumExpression coveredVolume = sum(NDISTRICT, [&](int d) { return q[d][c]; });
            return coveredVolume.geq(volume[c]).setName("Size_" + std::to_string(c));
        });

        // "Min": at least 2 districts per contract
        // for all c in [0,NCONTRACT]
        //      sum(d in [0,NDISTRICT]) x[d][c] >= 2
        prob.addConstraints(NCONTRACT, [&](int c) {
            LinExpression districtsPerContract = LinExpression::create();
            for (int d=0; d<NDISTRICT; d++) {
                districtsPerContract.addTerm(x[d][c]);
            }
            return districtsPerContract.geq(2.0).setName("Min_" + std::to_string(c));
        });

        // Do not exceed max. output
        // for all d in [0,NDISTRICT]
        //      sum(c in [0,NCONTRACT]) q[d][c] <= output[d]
        prob.addConstraints(NDISTRICT, [&](int d) {
            return (sum(q[d]) <= output[d]).setName("Output_" + std::to_string(d));
        });

        // If a contract is allocated to a district, then at least 1 unit is allocated to it
        // for all d in [0,NDISTRICT[
        //      for all c in [0,NCONTRACT[
        //           x[d][c] <= q[d][c]
        prob.addConstraints(NDISTRICT, NCONTRACT, [&](int d, int c) {
            return x[d][c].leq(q[d][c]).setName("XQ_" + std::to_string(d) + "_" + std::to_string(c));
        });

        /* OBJECTIVE */

        LinExpression obj = LinExpression::create();
        for (int c=0; c<NCONTRACT; c++) {
            for (int d=0; d<NDISTRICT; d++) {
                obj.addTerm(q[d][c], cost[d]);
            }
        }
        prob.setObjective(obj, ObjSense::Minimize);

        /* SOLVE & PRINT */

        prob.writeProb("Contract.lp", "l");
        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());
        }

        // Print the solution
        std::vector<double> sol = prob.getSolution();
        std::cout << "*** Solution ***" << std::endl;
        std::cout << "Objective value: " << prob.attributes.getObjVal() << std::endl;
        for (std::vector<Variable> q_d : q) {
            for (Variable q_dc : q_d) {
                if (q_dc.getValue(sol) > 0.0) {
                    std::cout << q_dc.getName() << ": " << q_dc.getValue(sol) << ", ";
                }
            }
            std::cout << 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.