QUBO++ (C++) vs PyQBPP (Python)

QUBO++ is available in two languages: C++ (QUBO++) and Python (PyQBPP). Both provide the same core functionality for formulating and solving QUBO/HUBO problems. This page summarizes the key differences between the two.

Installation

  C++ (QUBO++) Python (PyQBPP)
Command sudo apt install qbpp pip install pyqbpp
Platform Linux (amd64 / arm64) Linux (amd64 / arm64)
Details Installation Installation

Coefficient and Energy Precision

In C++, the coefficient type (coeff_t) and energy type (energy_t) are fixed at compile time. The default types are int32_t and int64_t, which may overflow for problems with large coefficients. To use arbitrary-precision integers, define INTEGER_TYPE_CPP_INT before including the header:

#define INTEGER_TYPE_CPP_INT
#include <qbpp/qbpp.hpp>

Alternatively, you can pass -DINTEGER_TYPE_CPP_INT as a compiler option.

In Python, 32-bit coefficients and 64-bit energy (c32e64) are used by default (import pyqbpp). For arbitrary precision, you can import the cppint submodule:

import pyqbpp as qbpp              # Default: c32e64 (32-bit coeff, 64-bit energy)
import pyqbpp.cppint as qbpp       # Arbitrary precision (cpp_int)
  C++ Python
Default coefficient int32_t (32-bit) int32_t (32-bit)
Default energy int64_t (64-bit) int64_t (64-bit)
Changing precision #define INTEGER_TYPE_CPP_INT etc. import pyqbpp.cppint at import time
Details C++ Data Types Python Data Types

Large Integer Constants

When working with problems involving large integer constants that exceed the range of int64_t, the handling differs between C++ and Python.

C++: You must define INTEGER_TYPE_CPP_INT and write large constants as strings, because C++ integer literals cannot exceed int64_t:

#define INTEGER_TYPE_CPP_INT
#include <qbpp/qbpp.hpp>

int main() {
  auto x = qbpp::var("x");
  auto f = x * qbpp::coeff_t("123456789012345678901234567890");
  std::cout << f << std::endl;
}

Python: For large integer constants beyond int64_t, import the cppint submodule which uses cpp_int (arbitrary-precision integers):

import pyqbpp.cppint as qbpp

x = qbpp.var("x")
f = x * 123456789012345678901234567890
print(f)

Both produce: 123456789012345678901234567890*x

Type Distinctions

In C++, there are distinct types for variables (Var), terms (Term), and expressions (Expr). Although implicit conversions are provided (e.g., VarTermExpr), understanding these types is important for reading and writing QUBO++ code.

In Python, you do not need to be aware of these class distinctions. Dynamic typing handles conversions automatically, so you can mix variables, terms, and expressions freely in arithmetic operations.

For example, in C++, writing auto f = 2; makes f an int, so f += x; causes a compile error. You must explicitly create an Expr:

auto x = qbpp::var("x");
auto f = qbpp::toExpr(2);  // Must be Expr, not int
f += x;                   // f is now Expr representing 2 + x

In Python, no such type awareness is needed:

x = qbpp.var("x")
f = 2          # Just an int — no problem
f += x         # f automatically becomes an Expr representing 2 + x

Object Copy and Aliasing

C++ uses value semantics by default, while Python uses reference semantics for mutable types. This affects how qbpp::Term, qbpp::Expr, and qbpp::Sol (the mutable types) behave when assigned to another variable.

C++ — Independent Copy

qbpp::Expr f = x;
qbpp::Expr g = f;   // independent copy
f += y;             // f only
std::cout << "f = " << f << std::endl;   // f = x +y
std::cout << "g = " << g << std::endl;   // g = x

Expr g = f deep-copies the data. Mutating f afterwards has no effect on g.

Python — Alias by Default

f = qbpp.expr() + x
g = f               # alias — same object
f += y              # mutates the shared object
print("f =", f)     # f = x +y
print("g =", g)     # g = x +y   (also changed)

g = f only binds the name g to the same object that f points to. Mutating through one name is visible through both.

Comparison Table

Operation C++ (QUBO++) Python (PyQBPP)
g = f (new variable) deep copy (independent) alias (shared object)
f += x in-place in-place
f = f + x (lhs survives) independent — same observable result as f += x new Expr; g (alias) unaffected
Explicit copy implicit (assignment copies) qbpp.Expr(other) for deep copy
Sol copy qbpp::Sol s2 = s1; (deep copy) qbpp.Sol(other_sol) (deep copy)
Solver copy deleted — compile error not advised; copying breaks the resource handle

Practical Tip

In Python, treat any expression you want to keep around as off-limits to compound assignment unless you intend every alias to see the change. In C++, you can freely mix f = f + x and f += x interchangeably; the compiler chooses the optimal path via rvalue overloads.

Solver objects (EasySolver, ExhaustiveSolver, ABS3Solver) own heavy compute resources (GPU contexts, thread pools). In C++ they are non-copyable by design — the copy constructor is deleted. In Python they should also not be copied; create a new solver if needed.

Syntax Differences

The following table shows the main syntax differences between C++ and Python.

Feature C++ (QUBO++) Python (PyQBPP)
Include / Import #include <qbpp/qbpp.hpp> import pyqbpp as qbpp
Solver include #include <qbpp/easy_solver.hpp> Not required
Variable auto a = qbpp::var("a"); a = qbpp.var("a")
Variable array auto x = qbpp::var("x", n); x = qbpp.var("x", shape=n)
Integer variable auto x = 0 <= qbpp::var_int("x") <= 10; x = qbpp.var("x", between=(0, 10))
Explicit conversion to Expr auto f = qbpp::toExpr(2); Not required
Equality auto f = (expr == 3); f = qbpp.constrain(expr, equal=3)
Range constraint auto f = (1 <= expr <= 5); f = qbpp.constrain(expr, between=(1, 5))
Array slice x(qbpp::slice(1, 3)), x(qbpp::all, j) x[1:3], x[:, j]
Array concat qbpp::concat(a, b) qbpp.concat([a, b])
Einsum (tensor contraction) qbpp::einsum<2>("ij,jk->ik", A, B) qbpp.einsum("ij,jk->ik", A, B)
Search with params solver.search({{"time_limit", 10}, {"target_energy", 0}}) solver.search(time_limit=10, target_energy=0)
Output std::cout << sol << std::endl; print(sol)

In C++, einsum requires the output dimension as a template argument (<2> in the example above) because the result type Array<Dim, T> is parameterized by Dim at compile time, while the subscript string is only inspected at runtime. Python infers the output dimension automatically from the subscript. See Einsum Function for details.

Quick Start Example

The same problem solved in both languages (minimizing $f = (a + 2b + 3c - 4)^2$ with EasySolver):

C++:

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

int main() {
  auto a = qbpp::var("a");
  auto b = qbpp::var("b");
  auto c = qbpp::var("c");
  auto f = qbpp::sqr(a + 2 * b + 3 * c - 4);
  f.simplify_as_binary();

  std::cout << "f = " << f << std::endl;

  auto solver = qbpp::EasySolver(f);
  auto sol = solver.search({{"time_limit", 10}, {"target_energy", 0}});
  std::cout << "sol = " << sol << std::endl;
}

Python:

import pyqbpp as qbpp

a = qbpp.var("a")
b = qbpp.var("b")
c = qbpp.var("c")
f = qbpp.sqr(a + 2 * b + 3 * c - 4)
f = qbpp.simplify_as_binary(f)
print("f =", f)

solver = qbpp.EasySolver(f)
sol = solver.search(time_limit=10, target_energy=0)
print("sol =", sol)

Output (C++):

f = 16 -7*a -12*b -15*c +4*a*b +6*a*c +12*b*c
sol = 0:{{a,1},{b,0},{c,1}}

Output (Python):

f = 16 -7*a -12*b -15*c +4*a*b +6*a*c +12*b*c
sol = Sol(energy=0, {a: 1, b: 0, c: 1})

Which Should I Use?

C++ (QUBO++) — Strengths

  • Faster expression building: Building large expressions with millions of terms is significantly faster in native C++. The solver execution time is the same in both languages, but the time to construct the model can differ substantially for large problems.
  • Mathematical range syntax: Range constraints use the natural notation l <= f <= u, which reads like a mathematical formula.
  • Integration with existing C++ projects: Can be embedded directly into existing C++ applications.

Python (PyQBPP) — Strengths

  • No compilation: Write and run immediately. Ideal for interactive exploration with Jupyter notebooks and the Python REPL.
  • Simpler syntax: Less boilerplate — no #include, #define, main(), auto, or namespace qualifiers.
  • Easy installation: pip install pyqbpp in a virtual environment, no sudo required.
  • Data science ecosystem: Seamless integration with NumPy, pandas, matplotlib, and other Python libraries for data preparation and result analysis.

Summary

  C++ (QUBO++) Python (PyQBPP)
Expression building speed Fast (native) Slower (Python binding overhead)
Solver speed Same Same
Ease of use Moderate Easy
Interactive use No Yes (Jupyter, REPL)

Recommendation: Start with PyQBPP (Python) for prototyping and learning. Switch to C++ (QUBO++) if you need faster expression building for large-scale problems.


Back to top

Page last modified: 2026.05.12.