Comparison Operators

QUBO++ supports two types of operators for creating constraints:

  • Equality operator: $f=n$, where $f$ is an expression and $n$ is an integer.
  • Range operator: $l\leq f\leq u$, where $f$ is an expression and $l$ and $u$ ($l\leq u$) are integers.

These operators return an expression that attains the minimum value of 0 if and only if the corresponding constraints are satisfied.

Note This page describes the classic approach of adding a constraint to the objective as a penalty expression (a polynomial whose value is 0 exactly when the constraint is satisfied). Wrapping the constraint part in qbpp::cons() instead declares it as a native constraint: no auxiliary (slack) variables are introduced and the bundled solvers search efficiently for solutions that satisfy it, which often yields better solutions. See Nonlinear Functions and Native Constraints for details.

Equality operator

The equality operator $f=n$ creates the following expression:

\[(f−n)^2\]

This expression attains the minimum value of 0 if and only if the equality $f=n$ is satisfied.

The following QUBO++ program searches for all solutions satisfying $a+2b+3c=3$ using the Exhaustive Solver:

#include <qbpp/qbpp.hpp>
#include <qbpp/exhaustive_solver.hpp>

int main() {
  auto a = qbpp::var("a");
  auto b = qbpp::var("b");
  auto c = qbpp::var("c");
  auto f = a + 2 * b + 3 * c == 3;
  f.simplify_as_binary();
  std::cout << "f = " << f << std::endl;
  std::cout << "f.body() = " << f.body() << std::endl;

  auto solver = qbpp::ExhaustiveSolver(f);
  auto sols = solver.search({{"best_energy_sols", 0}});
  for (const auto& sol : sols) {
    std::cout << "a = " << a(sol) << ", b = " << b(sol) << ", c = " << c(sol)
              << ", f = " << f(sol) << ", f.body() = " << f.body(sol) << std::endl;
  }
}

In this program, f internally holds two qbpp::Expr objects:

  • f: $(a+2b+3c−3)^2$, which attains the minimum value of 0 if the equality $a+2b+3c=3$ is satisfied.
  • f.body(): the left-hand side of the equality, $a+2b+3c$.

Using the Exhaustive Solver object created for f, all optimal solutions are stored in sols. By iterating over sols, all solutions and the values of f and f.body() are printed as follows:

f = 9 -5*a -8*b -9*c +4*a*b +6*a*c +12*b*c
f.body() = a +2*b +3*c
a = 0, b = 0, c = 1, f = 0, f.body() = 3
a = 1, b = 1, c = 0, f = 0, f.body() = 3

These results confirm that two optimal solutions attain f = 0 and satisfy f.body() = 3.

Notes on Supported Equality Forms

QUBO++ supports the equality operator only in the following form:

  • expression == integer.

The following forms are not supported:

  • integer == expression
  • expression1 == expression2

Instead of expression1 == expression2, you can rewrite the constraint as:

  • expression1 - expression2 == 0

which is fully supported.

Range operator

QUBO++ supports the two-sided range operator of the form $l\leq f \leq u$ ($l\leq u$):

  • Range operator: $l \leq f \leq u$ — written as l <= expr <= u

It creates an expression that attains the minimum value of 0 if and only if the constraint is satisfied.

NOTE The l <= expr <= u chain syntax requires both bounds to be integer literals on the outside. When one of the bounds should be infinite, use the one-sided operators described later (expr >= l for no upper bound, expr <= u for no lower bound) — these are the preferred forms instead of the chain l <= expr <= +qbpp::inf / -qbpp::inf <= expr <= u.

We consider the following cases depending on the values of $l$ and $u$.

  • Case 1: $u=l$
  • Case 2: $u=l+1$
  • Case 3: $u=l+2$
  • Case 4: $u\geq l+3$.

Case 1: $u=l$

If $u=l$, the range constraint reduces to the equality constraint $f=l$, which can be implemented directly using the equality operator.

Case 2: $u=l+1$

If $u=l+1$, the following expression is created:

\[(f-l)(f-u)\]

Since there is no integer strictly between $l$ and $u$, this expression attains the minimum value of 0 if and only if $f=l$ or $f=u$.

Case 3: $u=l+2$

We introduce an auxiliary binary variable $a \in \lbrace 0,1\rbrace$ and use the following expression:

\[\begin{aligned} (f-l-a)(f-l-(a+1)) \end{aligned}\]

This expression evaluates as follows for for $f=l$, $l+1$, and $l+2$

\[\begin{aligned} (f-l-a)(f-l-(a+1)) &= (-a)(-(a+1)) && \text{if } f=l \\ &= (1-a)(-a) && \text{if } f=l+1 \\ &=(2-a)(1-a) && \text{if } f=l+2 \end{aligned}\]

In all cases, the minimum value 0 is attainable by an appropriate choice of $a$. Therefore, the expression takes the minimum value of 0 if $l\leq f\leq u$ is satisfied.

Let $g = f-l-a$. Then We have,

\[\begin{aligned} (f-l-a)(f-l-(a+1)) &= g(g-1) \end{aligned}\]

which is always positive if $g\leq -1$ or $g\geq 2$. Hence, the expression attains the minimum value of 0 if and only if $l\leq f\leq u$ is satisfied.

Case 4: $u\geq l+3$

We introduce an auxiliary integer variable $a$ that takes integer values in the range $[l,u−1]$. Such an integer variable can be defined using multiple binary variables, as described in Integer Variables and Solving Simultaneous Equations.

The expression for this case is:

\[\begin{aligned} (f-a)(f-(a+1)) \end{aligned}\]

Similarly to Case 3, we can show that this expression is always positive if $f$ is not in $[l,u]$.

Suppose that $f$ takes an integer value in the range $[l,u]$. If we choose $a=f$, then

\[\begin{aligned} f-a &= 0 & {\rm if\,\,} f\in [l,u-1]\\ f-(a+1) &= 0& {\rm if\,\,} f\in [l+1,u] \end{aligned}\]

Thus, either $f−a=0$ or $f−(a+1)=0$ holds for any $f\in[l,u]$. Therefore, $(f−a)(f−(a+1))$ attains the minimum value of 0 if and only if $l\leq f\leq u$.

Reducing the number of binary variables

In Integer Variables and Solving Simultaneous Equations, an integer variable $a\in [l,u]$ is represented using $n$ binary variables $x_0, x_1, \ldots, x_{n-1}$ as follows:

\[\begin{aligned} a & = l+2^0x_0+2^1x_1+\cdots +2^{n-2}x_{n-2}+dx_{n-1} \end{aligned}\]

This expression can represent all integers from $l$ to $l+2^{n-1}+d-1$. Thus, we can choose $n$ and $d$ such that

\[\begin{aligned} u-1&=l+2^{n-1}+d-1. \end{aligned}\]

For Case 4, QUBO++ instead uses the following linear expression with $n-1$ binary variables $x_1, \ldots, x_{n-1}$:

\[\begin{aligned} a &= l+2^1x_1+\cdots +2^{n-2}x_{n-2}+dx_{n-1} \end{aligned}\]

This expression represents integers from $l$ to $l+2^{n-1}+d-2$. Accordingly, we select $n$ and $d$ so that

\[\begin{aligned} u-1&=l+2^{n-1}+d-2. \end{aligned}\]

We call such an integer variable $a$ a unit-gap integer variable. Although some values in $[l,u]$ cannot be taken by $a$, for any $k\in[l,u]$ that cannot be represented, $k−1$ can be represented. Therefore, either $a$ or $a+1$ can take any value in the range $[l,u]$, which is sufficient for enforcing the range constraint.

QUBO++ program for the four cases

The following program demonstrates how the four cases are implemented in QUBO++:

#include <qbpp/qbpp.hpp>

int main() {
  auto f = qbpp::toExpr(qbpp::var("f"));
  auto f1 = 1 <= f <= 1;
  auto f2 = 1 <= f <= 2;
  auto f3 = 1 <= f <= 3;
  auto f4 = 1 <= f <= 5;
  std::cout << "f1 = " << f1.simplify() << std::endl;
  std::cout << "f2 = " << f2.simplify() << std::endl;
  std::cout << "f3 = " << f3.simplify() << std::endl;
  std::cout << "f4 = " << f4.simplify() << std::endl;
}

This program produces the following output:

f1 = 1 -2*f +f*f
f2 = 2 -3*f +f*f
f3 = 2 -3*f +3*{s0} +f*f -2*f*{s0} +{s0}*{s0}
f4 = 2 -3*f +6*{s1}[0] +3*{s1}[1] +f*f -4*f*{s1}[0] -2*f*{s1}[1] +4*{s1}[0]*{s1}[0] +4*{s1}[0]*{s1}[1] +{s1}[1]*{s1}[1]

These outputs correspond to the following expressions:

\[\begin{aligned} f_1 &= (f-1)^2\\ f_2 &= (f-1)(f-2)\\ f_3 &= (f-x_0)(f-(x_0+1))\\ f_4 &= (f-(2x_{1,0}+x_{1,1}+1))(f-(2x_{1,0}+x_{1,1}+2)) \end{aligned}\]

QUBO++ program using the range operator

The following program demonstrates the use of the range operator in QUBO++:

#include <qbpp/qbpp.hpp>
#include <qbpp/exhaustive_solver.hpp>

int main() {
  auto a = qbpp::var("a");
  auto b = qbpp::var("b");
  auto c = qbpp::var("c");
  auto f = 5 <= 4 * a + 9 * b + 15 * c <= 14;
  f.simplify_as_binary();
  auto solver = qbpp::ExhaustiveSolver(f);
  auto sols = solver.search({{"best_energy_sols", 0}});
  for (const auto& sol : sols) {
    std::cout << "a = " << a(sol) << ", b = " << b(sol) << ", c = " << c(sol)
              << ", f = " << f(sol) << ", f.body() = " << f.body(sol)
              << ", sol = " << sol << std::endl;
  }
}

For three binary variables $a$, $b$, and $c$, this program searches for solutions satisfying the constraint

\[\begin{aligned} 5\leq 4a+9b+15c \leq 14 \end{aligned}\]

This program produces the following output:

a = 0, b = 1, c = 0, f = 0, f.body() = 9, sol = 0:{{a,0},{b,1},{c,0},{{0}[0],0},{{0}[1],1},{{0}[2],0}}
a = 0, b = 1, c = 0, f = 0, f.body() = 9, sol = 0:{{a,0},{b,1},{c,0},{{0}[0],1},{{0}[1],0},{{0}[2],1}}
a = 1, b = 1, c = 0, f = 0, f.body() = 13, sol = 0:{{a,1},{b,1},{c,0},{{0}[0],1},{{0}[1],1},{{0}[2],1}}

Lower and upper bound operators

QUBO++ supports the following one-sided bound operators:

  • Lower-bound operator: $l\leq f$ — written as expr >= n (instead of n <= expr <= +qbpp::inf)
  • Upper-bound operator: $f\leq u$ — written as expr <= n (instead of -qbpp::inf <= expr <= n)

NOTE The reversed form n >= expr (integer on the left) is a compile error. n <= expr returns an intermediate value reserved for the chained form l <= expr <= u, so it cannot be used as a standalone one-sided constraint. Always place the expression on the left side of <= / >= when writing one-sided constraints.

The range operator internally introduces auxiliary variables, so true infinite values cannot be represented explicitly. When qbpp::inf is used in the chain form (kept available for backward compatibility), QUBO++ estimates the finite maximum and minimum values of the expression $f$ and substitutes them for $+\infty$ and $-\infty$, respectively.

For example, consider the expression

\[\begin{aligned} f=4a + 9 b + 11 c \end{aligned}\]

where $a$, $b$, and $c$ are binary variables. The minimum and maximum possible values of $f$ are 0 and 24, respectively. Thus, QUBO++ uses 0 and 24 as substitutes for $-\infty$ and $+\infty$ when constructing the corresponding range constraints.

QUBO++ program for lower and upper bound operators

The following program demonstrates the lower-bound operator:

#include <qbpp/qbpp.hpp>
#include <qbpp/exhaustive_solver.hpp>

int main() {
  auto a = qbpp::var("a");
  auto b = qbpp::var("b");
  auto c = qbpp::var("c");
  auto f = 4 * a + 9 * b + 11 * c >= 14;
  f.simplify_as_binary();
  auto solver = qbpp::ExhaustiveSolver(f);
  auto sols = solver.search({{"best_energy_sols", 0}});
  for (const auto& sol : sols) {
    std::cout << "a = " << a(sol) << ", b = " << b(sol) << ", c = " << c(sol)
              << ", f = " << f(sol) << ", f.body() = " << f.body(sol)
              << ", sol = " << sol << std::endl;
  }
}

In this program, f is built with the single-sided lower-bound operator >=. Internally, the upper bound is set to expr.pos_sum() = 24 (the largest value the body can take), so f is equivalent to writing 14 <= 4 * a + 9 * b + 11 * c <= +qbpp::inf.

This program produces the following output:

a = 1, b = 0, c = 1, f = 0, f.body() = 15, sol = 0:{{a,1},{b,0},{c,1},{{s0}[0],0},{{s0}[1],0},{{s0}[2],0}}
a = 0, b = 1, c = 1, f = 0, f.body() = 20, sol = 0:{{a,0},{b,1},{c,1},{{s0}[0],1},{{s0}[1],1},{{s0}[2],0}}
a = 0, b = 1, c = 1, f = 0, f.body() = 20, sol = 0:{{a,0},{b,1},{c,1},{{s0}[0],1},{{s0}[1],0},{{s0}[2],1}}
a = 1, b = 1, c = 1, f = 0, f.body() = 24, sol = 0:{{a,1},{b,1},{c,1},{{s0}[0],1},{{s0}[1],1},{{s0}[2],1}}

The following program demonstrates the upper-bound operator:

int main() {
  auto a = qbpp::var("a");
  auto b = qbpp::var("b");
  auto c = qbpp::var("c");
  auto f = 4 * a + 9 * b + 11 * c <= 14;
  f.simplify_as_binary();
  auto solver = qbpp::ExhaustiveSolver(f);
  auto sols = solver.search({{"best_energy_sols", 0}});
  for (const auto& sol : sols) {
    std::cout << "a = " << a(sol) << ", b = " << b(sol) << ", c = " << c(sol)
              << ", f = " << f(sol) << ", f.body() = " << f.body(sol)
              << ", sol = " << sol << std::endl;
  }
}

In this program, f is built with the single-sided upper-bound operator <=. Internally, the lower bound is set to expr.neg_sum() = 0 (the smallest value the body can take), so f is equivalent to writing -qbpp::inf <= 4 * a + 9 * b + 11 * c <= 14.

This program produces the following output:

a = 0, b = 0, c = 0, f = 0, f.body() = 0, sol = 0:{{a,0},{b,0},{c,0},{{s0}[0],0},{{s0}[1],0},{{s0}[2],0}}
a = 1, b = 0, c = 0, f = 0, f.body() = 4, sol = 0:{{a,1},{b,0},{c,0},{{s0}[0],0},{{s0}[1],1},{{s0}[2],0}}
a = 0, b = 1, c = 0, f = 0, f.body() = 9, sol = 0:{{a,0},{b,1},{c,0},{{s0}[0],1},{{s0}[1],0},{{s0}[2],1}}
a = 0, b = 0, c = 1, f = 0, f.body() = 11, sol = 0:{{a,0},{b,0},{c,1},{{s0}[0],0},{{s0}[1],1},{{s0}[2],1}}
a = 1, b = 1, c = 0, f = 0, f.body() = 13, sol = 0:{{a,1},{b,1},{c,0},{{s0}[0],1},{{s0}[1],1},{{s0}[2],1}}

Back to top

Page last modified: 2026.09.02.

© 2026 Koji Nakano, Hiroshima University