Skip to content

Learning Quantum Collections: qint, qvector, and entangled_set

The quantum data-structure helpers in QPP layer domain-specific semantics on top of familiar standard-library containers. This guide explains how the core primitives—qint, qvector, and entangled_set—fit together inside the Data Structures and Algorithms examples so you can extend or adapt them in your own learning projects.

qint: quantum orientation state

qint represents a four-axis quantum orientation. Each axis stores a discrete amplitude that records polarity as either negative (−1) or positive (+1). The fields map to the following interpretations:

Axis Field Negative (-1) meaning Positive (+1) meaning
X x logical 0 / northbound step logical 1 / southbound step
Y y symbol - / eastward step symbol + / westward step
Z z -45° rotation (back flip) +45° rotation (front flip)
T t -45i imaginary rotation (backward time) +45i imaginary rotation (forward time)

Constructors accept either explicit amplitude values or ordinary integers, which are normalised so that any non-positive number becomes negative. Convenience accessors—x_step(), y_step(), z_step(), and t_step()—expose axis-specific encodings when you need to visualise, log, or convert the state.

qint also defines equality and provides a std::hash specialisation. This makes it a drop-in key for hashed containers such as std::entangled_set and means algorithms can rely on value semantics when deduplicating or comparing states.

qvector: quantum-friendly dynamic arrays

std::qvector<T> is a straightforward alias for std::vector<T> that resides in the standard namespace. The alias lets educational material refer to "quantum vectors" without sacrificing the familiarity or performance characteristics of the standard dynamic array. Any API that works with std::vector—iterators, capacity management, algorithms—works identically with std::qvector.

When storing qint values, std::qvector<qint> becomes the natural building block for quantum sequences. You can reserve capacity, push states, and iterate with range-based for loops exactly as you would with classical data.

entangled_set: context-aware hash sets

std::entangled_set<Key> wraps an std::unordered_set but injects a cryptographically inspired entanglement context into its hashing pipeline. By default, each set instance seeds a shared context with two secret keys, a "domain" tag, and an epoch counter. The generated salt feeds into the hasher functor so that identical logical sets can produce different hash sequences across contexts—helping demonstrate how quantum systems decorrelate shared states.

Key features to be aware of:

  • Context control: You can pass a custom context (via make_context or make_default_context) to share entropy between sets or to rotate epochs when demonstrating time-evolving behaviour.
  • Transparent equality: transparent_equal allows heterogeneous lookups so string-like keys can be compared without constructing temporary std::string objects. Combined with the hasher's is_transparent marker, this mirrors the ergonomics of std::unordered_set while reinforcing how entanglement can span related representations.
  • Broad key support: The hashing utility handles integrals, floating-point numbers, tuples, string-like types, and pointers by delegating to specialised helpers. That makes std::entangled_set a flexible drop-in replacement for quantum-flavoured collections built on top of the STL.

Putting the pieces together

The Arrays and Hashing example uses these helpers to reframe classic interview questions in a quantum context. The duplicate-check routine below highlights the interplay between all three types:

std::qvector<qint> states = {/* ... prepare quantum inputs ... */};
std::entangled_set<qint> visited;
visited.reserve(states.size());
for (const auto& state : states) {
    if (visited.find(state) != visited.end())
        return true; // entangled collision detected
    visited.insert(state);
}
return false;
  • qvector provides contiguous storage for a sequence of qint states.
  • Each qint carries multi-axis orientation data while remaining hashable.
  • entangled_set tracks which states have been observed using its salted hash function.

Try modifying the context epoch between runs or mixing qint constructions (from both integer and amplitude inputs) to experiment with how the data structures react. Because these helpers are thin abstractions over standard containers, they are safe playgrounds for bridging classical algorithm drills and quantum intuition.

Hardware API

This file sketches the interface expected by future Q++ hardware backends. A hardware profile is described in a simple YAML format and defines qubit count, supported gates and rate limits.

The runtime emits QIR strings when executing tasks. Real backends would translate this intermediate form to device specific commands. For now HardwareStub simply stores the strings for inspection.

The compiler attaches probability metadata to the emitted QIR so that hardware schedulers can make informed decisions. Hardware profiles describe qubit counts, supported gates and rate limits using YAML. A priority-aware scheduler may pause, resume, reprioritize or remove tasks based on these profiles.

The API is intentionally minimal:

class HardwareStub {
  void emit(const std::string& qir);
  std::string result() const;
};

Implementations may choose to stream operations, perform buffering or apply other optimizations. Rate-limit handling and multi-backend support are left for future work.

Initial Mappers

Logical qubits must be assigned to physical locations before routing. The AbstractInitialMapper interface encapsulates this placement step. Two implementations are provided:

  • LineInitialMapper – maps qubits along a simple device line, centering the highest-degree logical nodes.
  • VF2InitialMapper – explores placements using a pruned backtracking search similar to the VF2 algorithm. If no complete mapping is found within the time budget it falls back to the line heuristic.

A DynamicInitialMapper chooses between these strategies based on circuit size and interaction density and serves as the default when no mapper is specified.

External Providers

Basic support for running programs on external hardware APIs is provided through the :class:HardwareAPI helper in hardware_api.py. It can connect to either Qiskit or Cirq backends. Credentials may be supplied using add_credentials (the earlier add_credientials spelling is still accepted for backward compatibility):

from hardware_api import HardwareAPI
api = HardwareAPI('qiskit')
api.add_credentials('qiskit', token='MYTOKEN')
backend = api.connect()

If no credentials are provided the helper assumes that a backend is available locally and falls back to the frameworks' simulators. This mirrors execution on a classical wavefunction simulator or a directly attached QPU.

To run on a specific device, call select_backend with the desired backend name. For example, "statevector_simulator" chooses the statevector simulator in Qiskit while "density_matrix" selects the matching Cirq simulator.

Running C++/Q++ Programs

Q++ or C++ sources are lowered to OpenQASM before execution. The resulting string can be submitted to an external provider using run_qasm or run_source. run_qasm accepts a pre-generated OpenQASM string while run_source compiles a Q++/C++ file containing __qasm blocks or circuit.push_back statements and executes the resulting program:

from hardware_api import HardwareAPI

api = HardwareAPI('cirq')
api.select_backend('density_matrix')
result = api.run_source('program.qpp')[0]

Both helpers execute the program on the configured backend, falling back to local simulators when credentials are omitted.

Example Q++ Source

An end-to-end example Q++ file demonstrates how the helper can be used from C++/Q++ code. Circuits in Q++ are vectors of gate operations, so the Circuit type inherits from std::vector and accepts gates via push_back. The program below targets Qiskit and can be switched to Cirq by changing the provider name:

#include "hardware_api.hpp"

int main() {
  // Replace "qiskit" with "cirq" to target Cirq instead
  HardwareAPI api("qiskit");
  api.select_backend("statevector_simulator");

  Circuit circuit; // Circuit inherits from std::vector
  circuit.push_back(H(0));
  circuit.push_back(CX(0,1));
  circuit.push_back(Measure(0,1));

  return 0;
}

Save the file as examples/hardware_api_example.qpp and execute it using run_source:

from hardware_api import HardwareAPI
api = HardwareAPI('qiskit')
api.run_source('examples/hardware_api_example.qpp')

Testing

The connectors can be exercised with the provided unit tests. After installing optional dependencies (pip install qiskit cirq) run:

pytest tests/test_hardware_api.py

The tests automatically skip when the required frameworks are not installed, allowing them to run in minimal environments.

Runtime Overview

This document describes the prototype runtime environment used by Q++. It complements the frontend overview and focuses on task execution and hardware integration.

Scheduler

The qpp::Scheduler class manages a list of tasks. Each task carries a priority and may be paused or resumed. Tasks can also be removed, cleared, or reprioritized at runtime. When run() is invoked the tasks are executed in priority order. This simple model is intended to mimic a more complete quantum/classical scheduler that would handle device selection and probabilistic control flow.

All public methods are protected by a mutex so that tasks may be added, paused or resumed from multiple threads. This ensures thread safety when using task<CPU>, task<QPU>, task<AUTO> or task<MIXED> functions across different components.

Registers

qregister and cregister provide explicit quantum and classical storage. A qregister wraps a qclass instance and exposes import and export helpers so that state can be saved or restored. cregister stores ordinary integer bits for hybrid algorithms.

Raw Gate Injection

Code may contain __qasm { ... } blocks. The parser collects the contents of these blocks and the runtime forwards them unchanged to the selected backend.

Device Stubs

HardwareStub is a minimal backend that simply collects emitted QIR strings. It serves as a placeholder until real devices are connected.

Probability Metadata

Measurements and conditional branches that depend on quantum state are tagged with probability information. This metadata is preserved through the LLVM pass pipeline and attached to the generated QIR so that hardware backends can reason about likely outcomes.

Inline Explanations

When a #explain directive is encountered the runtime emits a textual annotation describing upcoming operations. These explanations may be consumed by tooling or displayed to the user during simulation.

Frontend Overview

This document outlines how the Q++ compiler frontend interprets quantum-aware constructs.

task<> Declarations

task<AUTO> hybrid_logic() {}
task<QPU>  quantum_logic() {}
task<CPU>  classical_logic() {}

The template parameter specifies the intended execution target or allows the compiler to infer it via AUTO.

Conditional Expressions

if (q[0]) { /* ... */ }

Conditions depending on quantum memory mark the guarded scope as probabilistic and influence scheduling decisions.

Memory Allocation

qalloc int qarray[4];
qregister int reg1;
register int r2;

Explicit quantum or classical registers can be declared. When the register keyword is used alone the compiler chooses the appropriate kind.

Quantum-Aware Types

qstruct Token { qbit state; int index; };

Structures containing quantum members exhibit probabilistic behavior and are tracked by the scheduler. Classical-only variants cstruct and cclass are available when no quantum state is required. They behave like their quantum counterparts but omit amplitude storage.

Boolean and Bitwise Behavior

Boolean variables linked to quantum memory become probabilistic:

bool flag = q[0];
int x = a & b;  // may emit Toffoli when operands are quantum

Bitwise operators expand to the corresponding quantum gates when required. The current implementation maps ^ to a controlled-X gate (CNOT) and & to a Toffoli gate when the operands reference quantum memory. Single qubit operators such as |, ~, and explicit method calls provide access to additional gates including Y and Z.