C++ setup (Qiskit C++ and Armadillo)

The C++ track mirrors the Python one:

  • Qiskit C++ builds and transpiles quantum circuits, and submits them to a backend. It is a header-only layer over the Qiskit C library.

  • Armadillo does the linear algebra: state vectors, unitaries, Hamiltonians, eigenvalues, and the classical baselines.

  • yaml-cpp reads the same YAML inputs as the Python track.

The compiler, CMake, Armadillo, yaml-cpp and Rust (needed once, to build the Qiskit C library) all come from the conda environment:

./install.sh
conda activate qalgos

If you already did the Python setup (Qiskit) setup, you only need the next step.

Installing Qiskit C++

Qiskit C++ is not on conda or PyPI. It is a set of headers that call the Qiskit C library, which is built from the Qiskit source. One script does both, into third_party/ (not committed):

conda activate qalgos
./scripts/install_qiskit_cpp.sh

The first run compiles Qiskit’s Rust code and takes several minutes. It needs Qiskit 2.2 or newer; the script builds release 2.5.2 by default. To pick another release:

QISKIT_REF=2.5.2 ./scripts/install_qiskit_cpp.sh

What Qiskit C++ can and cannot do

Circuit construction, parameters, observables and the transpiler run locally. Running a circuit needs a backend interface (IBM Quantum through qiskit-ibm-runtime C, QRMI, or SQC), because Qiskit C++ has no built-in simulator. For simulation in C++, use Armadillo: write the state vector and gates as arma::cx_vec and arma::cx_mat. This is also what serves as the classical baseline.

Building a program

Layout:

cpp/
├── CMakeLists.txt
└── main.cpp

cpp/main.cpp (Qiskit builds the circuit, Armadillo holds the state):

#include <armadillo>
#include <iostream>
#include "circuit/quantumcircuit.hpp"

using namespace Qiskit::circuit;

int main() {
    QuantumRegister qr(2);
    ClassicalRegister cr(2);
    QuantumCircuit circ(qr, cr);
    circ.h(0);
    circ.cx(0, 1);
    circ.measure(0, 0);
    circ.measure(1, 1);
    std::cout << "qubits = " << circ.num_qubits() << "\n";

    arma::cx_vec psi = arma::randu<arma::cx_vec>(4);
    psi /= arma::norm(psi);
    std::cout << "norm   = " << arma::norm(psi) << "\n";
}

cpp/CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(qalgos CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

# Built by scripts/install_qiskit_cpp.sh
set(TP ${CMAKE_SOURCE_DIR}/../third_party)
set(QISKIT_LIB_DIR ${TP}/qiskit/dist/c/lib)

find_package(Armadillo REQUIRED)
find_package(yaml-cpp REQUIRED)

add_executable(app main.cpp)
target_include_directories(app PRIVATE
  ${TP}/qiskit-cpp/src
  ${TP}/qiskit/dist/c/include
  ${ARMADILLO_INCLUDE_DIRS})
target_link_directories(app PRIVATE ${QISKIT_LIB_DIR})
target_link_libraries(app PRIVATE
  qiskit ${ARMADILLO_LIBRARIES} yaml-cpp::yaml-cpp)
set_target_properties(app PROPERTIES BUILD_RPATH ${QISKIT_LIB_DIR})

Build and run, with the environment active:

cmake -S cpp -B build -G Ninja -DCMAKE_PREFIX_PATH="$CONDA_PREFIX"
cmake --build build
./build/app configs/example.yaml

Quick check without CMake

c++ -std=c++17 -O2 main.cpp -o app \
    -I third_party/qiskit-cpp/src -I third_party/qiskit/dist/c/include \
    -I"$CONDA_PREFIX/include" -L"$CONDA_PREFIX/lib" \
    -L third_party/qiskit/dist/c/lib -lqiskit -larmadillo -lyaml-cpp \
    -Wl,-rpath,"$PWD/third_party/qiskit/dist/c/lib" \
    -Wl,-rpath,"$CONDA_PREFIX/lib"

Tips

  • Build in Release mode. Armadillo is much slower without optimization.

  • Armadillo uses BLAS and LAPACK underneath. The conda package links against an optimized one, so large matrix operations are fast.

  • Complex amplitudes: use arma::cx_vec and arma::cx_mat.

  • Large sparse operators (Hamiltonians): use arma::sp_mat.

  • Read all experiment parameters from YAML rather than hard-coding them. See the example on the configuration page.