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
(viamake_context
ormake_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 temporarystd::string
objects. Combined with the hasher'sis_transparent
marker, this mirrors the ergonomics ofstd::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 ofqint
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
.