Initializing help system before first use

Constraint types - Logical, general, SOS, quadratic


Type: Programming
Rating: 2 (easy-medium)
Description: Small examples showing how to define special constraint types:
  • Stating logic clauses that resemble SAT-type formulations (BoolVars).
  • Formulating some constraints on the minimum and absolute values of linear combinations of variables (GeneralConstraints).
  • Using the 'pwl' construct to formulate a piecewise linear cost function (PiecewiseLinear).
  • Formulation of a small quadratic programming problem (QuadraticProgramming).
  • Approximation of a nonlinear function by a special ordered set of type 2 (SpecialOrderedSets) and of a quadratic function in 2 variables by special ordered sets of type 2 (SpecialOrderedSetsQuadratic).
File(s): BoolVars.cpp, GeneralConstraints.cpp, PiecewiseLinear.cpp, QuadraticProgramming.cpp, SpecialOrderedSets.cpp, SpecialOrderedSetsQuadratic.cpp


BoolVars.cpp
// (c) 2024-2025 Fair Isaac Corporation
#include <iomanip>   // For setting precision when printing doubles to console
#include <stdexcept> // For throwing exceptions
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;

/* Problem showing the use of binary variables and how to model
constraints such as OR, AND using 'resultant variables'
*/
int main() {
  try {
    // Number of variables to create
    int R = 5;

    // Create a problem instance
    XpressProblem prob;
    // prob.callbacks.addMessageCallback(XpressProblem::console);

    // Create boolean variables x
    std::vector<xpress::objects::Variable> x = prob.addVariables(R)
                                                   .withType(ColumnType::Binary)
                                                   .withName("x_%d")
                                                   .toArray();

    // Create boolean variables xNeg (intended as xNeg = 1-x, but the problem
    // does not know this yet)
    auto xNeg = prob.addVariables(R)
                    .withType(ColumnType::Binary)
                    .withName("xNeg_%d")
                    .toArray();

    // Now add the relation that x[r] + xNeg[r] = 1 for all r in [0...R[
    prob.addConstraints(R, [&](int r) { return x[r] + xNeg[r] == 1.0; });

    // Add some random constraints
    prob.addConstraint(x[2].eq(xNeg[3])); // Add constraint x[2] == xNeg[3]

    // Now we are going to construct constraints using OR and AND operators on
    // the variables We need a 'resultant variable' to construct these OR and
    // AND constraints We create two here, with names 'TRUE' and 'FALSE'
    Variable trueVar = prob.addVariable(ColumnType::Binary, "TRUE");
    Variable falseVar = prob.addVariable(ColumnType::Binary, "FALSE");

    // Fix these helper variables to 1 (true) and 0 (false), respectively,
    // (hence the naming of these 'variables')
    trueVar.fix(1);  // Add constraint trueVar  == 1
    falseVar.fix(0); // Add constraint falseVar == 0

    // We can now use trueVar as placeholder for '== 1':
    prob.addConstraint(trueVar.andOf(
        x[0], xNeg[4])); // Add constraint trueVar == AND(x[0], xNeg[4])
    prob.addConstraint(
        trueVar.orOf(x[0], x[2])); // Add constraint trueVar == OR(x[0], x[2])

    // For more complicated expressions, we need non-fixed resultant variables
    // for each operator
    Variable andResultant1 =
        prob.addVariable(ColumnType::Binary, "andresultant1");
    Variable andResultant2 =
        prob.addVariable(ColumnType::Binary, "andresultant2");

    // Add constraint that AND(x[0] + x[1] + x[2]) == andResultant1
    std::vector<Variable> subrange1(
        x.begin(),
        x.begin() + 3); // We first explicitly create a subarray of variables
    prob.addConstraint(andResultant1.andOf(subrange1));

    // Add constraint that AND(xNeg[3] + xNeg[4]) == andResultant2. Now we
    // create the subarray within the function call
    prob.addConstraint(andResultant2.andOf(
        std::vector<Variable>(xNeg.begin() + 3, xNeg.end())));

    // Now finally create constraint definition that OR(andResultant1,
    // andResultant2) == trueVar (which equals 1)
    GeneralConstraintDefinition new_constraint_def =
        trueVar.orOf(andResultant1, andResultant2);
    // Now actually add the GeneralConstraintDefinition to the problem to get a
    // GeneralConstraint
    GeneralConstraint new_constraint = prob.addConstraint(new_constraint_def);

    // Finally, add a constraint that none of xNeg[0...2] should be true
    prob.addConstraint(
        falseVar.orOf(std::vector<Variable>(xNeg.begin(), xNeg.begin() + 3)));

    // write the problem in LP format for manual inspection
    std::cout << "Writing the problem to 'BoolVars.lp'" << std::endl;
    prob.writeProb("BoolVars.lp", "l");

    // Solve the problem
    std::cout << "Solving the problem" << std::endl;
    prob.optimize();

    // Check the solution status
    std::cout << "Problem finished with SolStatus "
              << prob.attributes.getSolStatus() << std::endl;
    if (prob.attributes.getSolStatus() != xpress::SolStatus::Optimal) {
      throw std::runtime_error("Problem not solved to optimality");
    }

    // Print the solution to console (first set precision to e.g. 5)
    std::cout << std::fixed << std::setprecision(5);
    std::cout << "Solution has objective value (profit) of "
              << prob.attributes.getObjVal() << std::endl;
    std::cout << std::endl << "*** Solution ***" << std::endl;

    // Retrieve the solution values in one go
    std::vector<double> sol = prob.getSolution();

    // Loop over the relevant variables and print their name and value
    for (Variable x_r : x)
      std::cout << x_r.getName() << " = " << x_r.getValue(sol) << std::endl;
    std::cout << std::endl;

    for (Variable xNeg_r : xNeg)
      std::cout << xNeg_r.getName() << " = " << xNeg_r.getValue(sol)
                << std::endl;
    std::cout << std::endl;

    return 0;
  } catch (std::exception &e) {
    std::cout << "Exception: " << e.what() << std::endl;
    return -1;
  }
}

GeneralConstraints.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * A simple example that formulates some constraints on the minimum and absolute
 * values of linear combinations of variables.
 */

#include <iostream>
#include <xpress.hpp>

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

int main() {
  std::cout << "Formulating the general constraint example problem"
            << std::endl;
  int const R = 3;
  XpressProblem prob;

  std::vector<Variable> x =
      prob.addVariables(R).withName("x_%d").withUB(20).toArray();

  Variable y = prob.addVariable("y");
  Variable z = prob.addVariable("z");

  Expression objective = sum(x);

  // We want to formulate abs(x(0)-2*x(1)) <= 10. We need to introduce
  // two auxiliary variables for the argument of the abs function
  // and then break down the abs expression in multiple steps
  Variable diff1 = prob.addVariable("diff1");
  Variable absOfDiff1 = prob.addVariable("absOfDiff1");

  prob.addConstraint(diff1 == x[0] - 2 * x[1]);
  prob.addConstraint(absOfDiff1.absOf(diff1));
  prob.addConstraint(absOfDiff1 <= 10); // Could also be a bound

  // We link a new variable to the minimum of the x(i) and
  // require this variable to be >= 5
  // Clearly, this bound constraint could also be achieved by simply setting
  // the bounds of each x variable.
  Variable minOfX = prob.addVariable("minOfX");
  prob.addConstraint(minOfX.minOf(x));
  prob.addConstraint(minOfX >= 5);

  // We link variable y to the maximum of other variables, expressions, and
  // constants
  // y = max(x(2), 20, x(0)-z)
  Variable diff2 = prob.addVariable("diff2");
  prob.addConstraint(diff2 == x[0] - z);

  // the below code is equivalent to using the MaxOf function on the resultant y
  // prob.addConstraint(y.MaxOf(new Variable[] {x[2], diff2},
  // 20).setName("max_constraint"));
  prob.addConstraint(y.maxOf(std::vector<Variable>{x[2], diff2},
                             std::vector<double>{20}, "max_constraint"));

  // set objective function with a maximization sense
  prob.setObjective(objective, ObjSense::Maximize);

  // write the problem in LP format for manual inspection
  std::cout << "Writing the problem to 'GeneralConstraints.lp'" << std::endl;
  prob.writeProb("GeneralConstraints.lp", "l");

  // Solve the problem
  std::cout << "Solving the problem" << std::endl;
  prob.optimize();

  // check the solution status
  std::cout << "Problem finished with SolStatus "
            << to_string(prob.attributes.getSolStatus()) << std::endl;
  if (prob.attributes.getSolStatus() != SolStatus::Optimal) {
    throw std::runtime_error("Problem not solved to optimality");
  }

  // print the optimal solution of the problem to the console
  std::cout << "Solution has objective value (profit) of "
            << prob.attributes.getObjVal() << std::endl;
  std::cout << "*** Solution ***" << std::endl;
  auto sol = prob.getSolution();

  for (int r = 0; r < R; r++) {
    if (r)
      std::cout << ", ";
    std::cout << "x_" << r << " = " << x[r].getValue(sol);
  }
  std::cout << std::endl;
  std::cout << "y = " << y.getValue(sol) << ", z = " << z.getValue(sol)
            << std::endl;
  std::cout << "ABS ( x[0] - 2*x[1] ) = " << absOfDiff1.getValue(sol)
            << ", minOfX = " << minOfX.getValue(sol) << std::endl;

  return 0;
}

PiecewiseLinear.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * An example that demonstrates how to model a piecewise linear cost function. A
 * piecewise linear cost function f(x) assigns a different linear cost function
 * for different intervals of the domain of its argument x. This situation
 * occurs in real-world problems if there are price discounts available starting
 * from a certain quantity of sold goods.
 *
 * - Example discussed in mipformref whitepaper -
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;

int main(void) {
  std::cout << "Formulating the piecewise linear example problem" << std::endl;

  XpressProblem prob;
  Variable x = prob.addVariable("x");
  Variable fx = prob.addVariable("fx");
  Expression objective = fx;

  std::vector<double> BREAKPOINT_X{0,   50,  50,
                                   120, 120, 200}; // the breakpoints x values
  std::vector<double> BREAKPOINT_Y{0,   50,  75,
                                   180, 240, 400}; // the breakpoints y values

  // Create the break points from the x,y data
  std::vector<PwlBreakpoint> breakpoints(BREAKPOINT_X.size());
  for (unsigned i = 0; i < breakpoints.size(); ++i)
    breakpoints[i] = PwlBreakpoint(BREAKPOINT_X[i], BREAKPOINT_Y[i]);

  // add the piecewise linear constraints with the breakpoints
  prob.addConstraint(fx.pwlOf(x, breakpoints, "pwl_with_breakpoints"));

  // ! Add a lower bound on x to get a somewhat more meaningful model
  // x >= 150
  x.setLB(150);

  // set objective function with a minimization sense
  prob.setObjective(objective, ObjSense::Minimize);

  // write the problem in LP format for manual inspection
  std::cout << "Writing the problem to 'PiecewiseLinear.lp'" << std::endl;
  prob.writeProb("PiecewiseLinear.lp", "l");

  // Solve the problem
  std::cout << "Solving the problem" << std::endl;
  prob.optimize();

  // check the solution status
  std::cout << "Problem finished with SolStatus "
            << to_string(prob.attributes.getSolStatus()) << std::endl;
  if (prob.attributes.getSolStatus() != SolStatus::Optimal) {
    throw std::runtime_error("Problem not solved to optimality");
  }

  // print the optimal solution of the problem to the console
  std::cout << "Solution has objective value (profit) of "
            << prob.attributes.getObjVal() << std::endl;
  std::cout << std::endl;
  std::cout << "*** Solution ***" << std::endl;
  auto sol = prob.getSolution();

  std::cout << "x = " << x.getValue(sol) << ", fx = " << fx.getValue(sol)
            << std::endl;
  return 0;
}

QuadraticProgramming.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Small Quadratic Programming example.
 *
 * <pre>
 *   minimize x1 + x1^2 +2x1x2 +2x2^2 +x4^2
 *   s.t.
 *     C1:  x1 +2x2 -4x4 &gt;= 0
 *     C2: 3x1 -2x3 - x4 &lt;= 100
 *     C3: 10 &lt;= x1 +3x2 +3x3 -2x4 &lt;= 30
 *     0 &lt;= x1 &lt;= 20
 *     0 &lt;= x2,x3
 *     x4 free
 * </pre>
 */

#include <iostream>
#include <vector>
#include <xpress.hpp>

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

int main() {
  XpressProblem prob;

  prob.callbacks.addMessageCallback(XpressProblem::console);

  ///// VARIABLES
  std::vector<Variable> x(4);
  x[0] = prob.addVariable(0, 20, ColumnType::Continuous, "x1");
  x[1] = prob.addVariable("x2");
  x[2] = prob.addVariable("x3");
  x[3] = prob.addVariable(XPRS_MINUSINFINITY, XPRS_PLUSINFINITY,
                          ColumnType::Continuous, "x4");

  ///// OBJECTIVE
  QuadExpression obj = QuadExpression::create();
  obj.addTerm(x[0]);
  obj.addTerm(x[0] * x[0]);
  obj.addTerm(2 * x[0] * x[1]);
  obj.addTerm(2.0 * x[1] * x[1]);
  obj.addTerm(x[3].square());
  prob.setObjective(obj, ObjSense::Minimize);

  ///// CONSTRAINTS
  prob.addConstraint(sum(x[0], 2 * x[1], -4 * x[3]) >= 0);
  prob.addConstraint(sum(3 * x[0], -2 * x[2], -x[3]) <= 100);
  prob.addConstraint(sum(x[0], 3 * x[1], 3 * x[2], -2 * x[3]).in(10, 30));

  ///// SOLVING + OUTPUT
  prob.writeProb("qp.lp");
  prob.optimize();

  std::cout << "Problem status: " << to_string(prob.attributes.getLpStatus())
            << std::endl;
  if (prob.attributes.getLpStatus() != LPStatus::Optimal)
    throw std::runtime_error("optimization failed with status " +
                             to_string(prob.attributes.getLpStatus()));

  std::cout << "Objective function value: " << prob.attributes.getObjVal()
            << std::endl;
  auto sol = prob.getSolution();
  for (int i = 0; i < 4; i++)
    std::cout << x[i].getName() << ": " << x[i].getValue(sol) << ", ";
  std::cout << std::endl;
  return 0;
}

SpecialOrderedSets.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Approximation of a nonlinear function by a special ordered set (SOS-2). An
 * SOS-2 is a constraint that allows at most 2 of its variables to have a
 * nonzero value. In addition, these variables have to be adjacent.
 *
 * - Example discussed in mipformref whitepaper -
 */

#include <iostream>
#include <xpress.hpp>

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

int main() {

  int NB = 4;                                // number of breakpoints
  std::vector<double> B_X{1, 2.5, 4.5, 6.5}; // X coordinates of breakpoints
  std::vector<double> B_Y{1.5, 6, 3.5, 2.5}; // Y coordinates of breakpoints

  std::cout << "Formulating the special ordered sets example problem"
            << std::endl;
  XpressProblem prob;
  // create one w variable for each breakpoint. We express
  auto w = prob.addVariables(NB).withName("w_%d").withUB(1).toArray();

  Variable x = prob.addVariable("x");
  Variable y = prob.addVariable("y");

  // Define the SOS-2 with weights from B_X. This is necessary
  // to establish the ordering between the w variables.
  prob.addConstraint(SOS::sos2(w, B_X, "SOS_2"));

  // We use the w variables to express a convex combination.
  // In combination with the above SOS-2 condition,
  // X and Y are represented as a convex combination of 2 adjacent
  // breakpoints.
  prob.addConstraint(sum(w) == 1);

  // The below constraints express the actual locations of X and Y
  // in the plane as a convex combination of the breakpoints, subject
  // to the assignment found for the w variables.
  prob.addConstraint(x == scalarProduct(w, B_X));
  prob.addConstraint(y == scalarProduct(w, B_Y));

  // set lower and upper bounds on x
  x.setLB(2);
  x.setUB(6);

  // set objective function with a minimization sense
  prob.setObjective(y, ObjSense::Minimize);

  // write the problem in LP format for manual inspection
  std::cout << "Writing the problem to 'SpecialOrderedSets.lp'" << std::endl;
  prob.writeProb("SpecialOrderedSets.lp", "l");

  // Solve the problem
  std::cout << "Solving the problem" << std::endl;
  prob.optimize();

  // check the solution status
  std::cout << "Problem finished with SolStatus "
            << to_string(prob.attributes.getSolStatus()) << std::endl;
  if (prob.attributes.getSolStatus() != SolStatus::Optimal) {
    throw std::runtime_error("Problem not solved to optimality");
  }

  // print the optimal solution of the problem to the console
  std::cout << "Solution has objective value (profit) of "
            << prob.attributes.getObjVal() << std::endl;
  std::cout << "*** Solution ***" << std::endl;
  auto sol = prob.getSolution();

  for (int b = 0; b < NB; b++) {
    std::cout << "w_" << b << " = " << w[b].getValue(sol);
    if (b < NB - 1)
      std::cout << ", ";
    else
      std::cout << std::endl;
  }

  std::cout << "x = " << x.getValue(sol) << ", y = " << y.getValue(sol)
            << std::endl;

  return 0;
}

SpecialOrderedSetsQuadratic.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Approximation of a quadratic function in 2 variables by special ordered sets
 * (SOS-2). An SOS-2 is a constraint that allows at most 2 of its variables to
 * have a nonzero value. In addition, these variables have to be adjacent.
 *
 * - Example discussed in mipformref whitepaper -
 */

#include <iostream>
#include <xpress.hpp>

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

int main() {

  int const NX = 10;         // number of breakpoints on the X-axis
  int const NY = 10;         // number of breakpoints on the Y-axis
  std::vector<double> X(NX); // X coordinates of grid points
  std::vector<double> Y(NY); // Y coordinates of breakpoints
  // two dimensional array of function values on the grid points
  std::vector<std::vector<double>> F_XY(NX, std::vector<double>(NY));

  // assign the toy data
  for (int i = 0; i < NX; i++)
    X[i] = i + 1;
  for (int i = 0; i < NY; i++)
    Y[i] = i + 1;
  for (int i = 0; i < NX; i++)
    for (int j = 0; j < NY; j++)
      F_XY[i][j] = (X[i] - 5) * (Y[j] - 5);

  std::cout << "Formulating the special ordered sets quadratic example problem"
            << std::endl;
  XpressProblem prob;
  // create one w variable for each X breakpoint. We express
  auto wx = prob.addVariables(NX)
                .withName("wx_%d")
                // this upper bound i redundant because of the convex
                // combination constraint on the sum of the wx
                .withUB(1)
                .toArray();
  // create one w variable for each Y breakpoint. We express
  auto wy = prob.addVariables(NY)
                .withName("wy_%d")
                // this upper bound i redundant because of the convex
                // combination constraint on the sum of the wy
                .withUB(1)
                .toArray();

  // create a two-dimensional array of w variable for each grid point. We
  // express
  auto wxy = prob.addVariables(NX, NY)
                 .withName("wxy_%d_%d")
                 // this upper bound is redundant because of the convex
                 // combination constraint on the sum of the wy
                 .withUB(1)
                 .toArray();

  Variable x = prob.addVariable("x");
  Variable y = prob.addVariable("y");
  Variable fxy = prob.addVariable("fxy");

  // make fxy a free variable
  fxy.setLB(XPRS_MINUSINFINITY);

  // Define the SOS-2 constraints with weights from X and Y.
  // This is necessary to establish the ordering between
  // variables in wx and in wy.
  prob.addConstraint(SOS::sos2(wx, X, "SOS_2_X"));
  prob.addConstraint(SOS::sos2(wy, Y, "SOS_2_Y"));
  prob.addConstraint(sum(wx) == 1);
  prob.addConstraint(sum(wy) == 1);

  // link the wxy variables to their 1-dimensional colleagues
  prob.addConstraints(NX, [&](auto i) {
    return wx[i] == sum(NY, [&](auto j) { return wxy[i][j]; });
  });
  prob.addConstraints(NY, [&](auto j) {
    return wy[j] == sum(NX, [&](auto i) { return wxy[i][j]; });
  });

  // now express the actual x, y, and f(x,y) coordinates
  prob.addConstraint(x == scalarProduct(wx, X));
  prob.addConstraint(y == scalarProduct(wy, Y));
  prob.addConstraint(fxy == sum(NX, [&](auto i) {
                       return sum(
                           NY, [&](auto j) { return wxy[i][j] * F_XY[i][j]; });
                     }));

  // set lower and upper bounds on x and y
  x.setLB(2);
  x.setUB(10);
  y.setLB(2);
  y.setUB(10);

  // set objective function with a minimization sense
  prob.setObjective(fxy, ObjSense::Minimize);

  // write the problem in LP format for manual inspection
  std::cout << "Writing the problem to 'SpecialOrderedSetsQuadratic.lp'"
            << std::endl;
  prob.writeProb("SpecialOrderedSetsQuadratic.lp", "l");

  // Solve the problem
  std::cout << "Solving the problem" << std::endl;
  prob.optimize();

  // check the solution status
  std::cout << "Problem finished with SolStatus "
            << to_string(prob.attributes.getSolStatus()) << std::endl;
  if (prob.attributes.getSolStatus() != SolStatus::Optimal) {
    throw std::runtime_error("Problem not solved to optimality");
  }

  // print the optimal solution of the problem to the console
  std::cout << "Solution has objective value (profit) of "
            << prob.attributes.getObjVal() << std::endl;
  std::cout << "*** Solution ***" << std::endl;
  auto sol = prob.getSolution();

  for (int i = 0; i < NX; i++) {
    std::cout << "wx_" << i << " = " << wx[i].getValue(sol);
    if (i < NX - 1)
      std::cout << ", ";
    else
      std::cout << std::endl;
  }
  for (int j = 0; j < NY; j++) {
    std::cout << "wy_" << j << " = " << wy[j].getValue(sol);
    if (j < NX - 1)
      std::cout << ", ";
    else
      std::cout << std::endl;
  }

  std::cout << "x = " << x.getValue(sol) << ", y = " << y.getValue(sol)
            << std::endl;
  return 0;
}

© 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.