Skip to content

Core API Reference

The core modules provide the fundamental building blocks: circuits, gates, parameters, kernels, the execution runner, and the unified result type.

Circuits

The Circuit class (aliased as QuantumCircuit) is the backend-agnostic intermediate representation. It supports single- and multi-qubit gates, parameterised rotations, measurements, composition, and parameter binding.

from hlquantum.circuit import Circuit, Parameter

# Build a parameterised circuit
qc = Circuit(2)
qc.rx(0, "theta").cx(0, 1).measure_all()

# Inspect
print(qc.depth)        # critical-path length
print(qc.gate_count)   # total gates
print(qc.parameters)   # [Parameter('theta')]

# Bind parameters
bound = qc.bind_parameters({"theta": 1.57})

# Compose circuits with |
c_total = qc | Circuit(2).h(1)

Backend-agnostic quantum circuit representation.

Circuit

High-level quantum circuit.

Source code in hlquantum/circuit.py
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
class Circuit:
    """High-level quantum circuit."""

    def __init__(self, num_qubits: int) -> None:
        if num_qubits < 1:
            raise ValueError("num_qubits must be >= 1")
        self.num_qubits = num_qubits
        self.gates: List[Gate] = []
        self.metadata: Dict[str, Any] = {}

    @property
    def qubits(self):
        return range(self.num_qubits)

    def h(self, target: int) -> "Circuit":
        self._validate_qubits(target)
        self.gates.append(Gate(name="h", targets=(target,)))
        return self

    def x(self, target: int) -> "Circuit":
        self._validate_qubits(target)
        self.gates.append(Gate(name="x", targets=(target,)))
        return self

    def y(self, target: int) -> "Circuit":
        self._validate_qubits(target)
        self.gates.append(Gate(name="y", targets=(target,)))
        return self

    def z(self, target: int) -> "Circuit":
        self._validate_qubits(target)
        self.gates.append(Gate(name="z", targets=(target,)))
        return self

    def s(self, target: int) -> "Circuit":
        self._validate_qubits(target)
        self.gates.append(Gate(name="s", targets=(target,)))
        return self

    def t(self, target: int) -> "Circuit":
        self._validate_qubits(target)
        self.gates.append(Gate(name="t", targets=(target,)))
        return self

    def rx(self, target: int, angle: Union[float, Parameter, str]) -> "Circuit":
        self._validate_qubits(target)
        p = Parameter(angle) if isinstance(angle, str) else angle
        self.gates.append(Gate(name="rx", targets=(target,), params=(p,)))
        return self

    def ry(self, target: int, angle: Union[float, Parameter, str]) -> "Circuit":
        self._validate_qubits(target)
        p = Parameter(angle) if isinstance(angle, str) else angle
        self.gates.append(Gate(name="ry", targets=(target,), params=(p,)))
        return self

    def rz(self, target: int, angle: Union[float, Parameter, str]) -> "Circuit":
        self._validate_qubits(target)
        p = Parameter(angle) if isinstance(angle, str) else angle
        self.gates.append(Gate(name="rz", targets=(target,), params=(p,)))
        return self

    def cx(self, control: int, target: int) -> "Circuit":
        self._validate_qubits(control, target)
        self.gates.append(Gate(name="cx", targets=(target,), controls=(control,)))
        return self

    def cz(self, control: int, target: int) -> "Circuit":
        self._validate_qubits(control, target)
        self.gates.append(Gate(name="cz", targets=(target,), controls=(control,)))
        return self

    def swap(self, q0: int, q1: int) -> "Circuit":
        self._validate_qubits(q0, q1)
        self.gates.append(Gate(name="swap", targets=(q0, q1)))
        return self

    def ccx(self, c0: int, c1: int, target: int) -> "Circuit":
        self._validate_qubits(c0, c1, target)
        self.gates.append(Gate(name="ccx", targets=(target,), controls=(c0, c1)))
        return self

    def measure(self, target: int) -> "Circuit":
        self._validate_qubits(target)
        self.gates.append(Gate(name="mz", targets=(target,)))
        return self

    def measure_all(self) -> "Circuit":
        for q in range(self.num_qubits):
            self.measure(q)
        return self

    @property
    def depth(self) -> int:
        """Circuit depth (longest critical path)."""
        if not self.gates:
            return 0
        qubit_layers: Dict[int, int] = {}
        max_depth = 0
        for gate in self.gates:
            involved = list(gate.targets) + list(gate.controls)
            layer = max((qubit_layers.get(q, 0) for q in involved), default=0)
            for q in involved:
                qubit_layers[q] = layer + 1
            max_depth = max(max_depth, layer + 1)
        return max_depth

    @property
    def gate_count(self) -> int:
        """Total number of gates."""
        return len(self.gates)

    @property
    def parameters(self) -> List[Parameter]:
        """Unique parameters in the circuit."""
        params = []
        seen = set()
        for gate in self.gates:
            for p in gate.params:
                if isinstance(p, Parameter) and p.name not in seen:
                    params.append(p)
                    seen.add(p.name)
        return params

    def bind_parameters(self, value_dict: Dict[Union[str, Parameter], float]) -> "Circuit":
        """Replace parameters with values and return a new circuit."""
        normalized_values = {}
        for k, v in value_dict.items():
            name = k.name if isinstance(k, Parameter) else k
            normalized_values[name] = v

        new_qc = Circuit(self.num_qubits)
        new_qc.metadata = self.metadata.copy()

        for gate in self.gates:
            new_params = []
            for p in gate.params:
                if isinstance(p, Parameter):
                    if p.name not in normalized_values:
                        raise ValueError(f"Missing parameter value: {p.name}")
                    new_params.append(normalized_values[p.name])
                else:
                    new_params.append(p)

            new_gate = Gate(
                name=gate.name,
                targets=gate.targets,
                controls=gate.controls,
                params=tuple(new_params)
            )
            new_qc.gates.append(new_gate)

        return new_qc

    def _validate_qubits(self, *qubits: int) -> None:
        for q in qubits:
            if not 0 <= q < self.num_qubits:
                raise IndexError(f"Qubit index {q} out of range.")

    def __or__(self, other: "Circuit") -> "Circuit":
        if not isinstance(other, Circuit):
            return NotImplemented

        num_qubits = max(self.num_qubits, other.num_qubits)
        new_qc = Circuit(num_qubits)
        for gate in self.gates:
            new_qc.gates.append(gate)
        for gate in other.gates:
            new_qc.gates.append(gate)
        return new_qc

    def __repr__(self) -> str:
        return f"Circuit(num_qubits={self.num_qubits}, gates={len(self.gates)})"

    def __len__(self) -> int:
        return len(self.gates)

    @classmethod
    def from_qiskit(cls, qc: Any) -> "Circuit":
        """Import circuit from Qiskit."""
        new_qc = cls(qc.num_qubits)
        for instruction in qc.data:
            op, qargs = instruction.operation, instruction.qubits
            name = op.name
            try:
                targets = [qc.find_bit(q).index for q in qargs]
            except AttributeError:
                targets = [q.index for q in qargs]

            if name in ("rx", "ry", "rz", "p", "u1"):
                func_name = "rz" if name in ("p", "u1", "rz") else name
                getattr(new_qc, func_name)(targets[0], float(op.params[0]))
            elif name in ("cx", "cz"):
                getattr(new_qc, name)(targets[0], targets[1])
            elif name == "swap":
                new_qc.swap(targets[0], targets[1])
            elif name == "ccx":
                new_qc.ccx(targets[0], targets[1], targets[2])
            elif name in ("h", "x", "y", "z", "s", "t"):
                getattr(new_qc, name)(targets[0])
            elif name == "measure":
                new_qc.measure(targets[0])
            elif name == "barrier":
                pass
            else:
                raise ValueError(f"Unsupported Qiskit gate: {name}")
        return new_qc

    @classmethod
    def from_cirq(cls, circuit: Any) -> "Circuit":
        """Import circuit from Cirq."""
        import math
        qubits = sorted(list(circuit.all_qubits()))
        qubit_map = {q: i for i, q in enumerate(qubits)}
        new_qc = cls(max(1, len(qubits)))

        for moment in circuit:
            for op in moment:
                gate = op.gate
                targets = [qubit_map[q] for q in op.qubits]
                gate_str = str(gate).lower()

                if "measure" in gate_str:
                    new_qc.measure(targets[0])
                    continue

                if hasattr(gate, "exponent"):
                    angle = float(gate.exponent) * math.pi
                    if "rx" in gate_str or "xpow" in gate_str:
                        if angle == math.pi: new_qc.x(targets[0])
                        else: new_qc.rx(targets[0], angle)
                        continue
                    if "ry" in gate_str or "ypow" in gate_str:
                        if angle == math.pi: new_qc.y(targets[0])
                        else: new_qc.ry(targets[0], angle)
                        continue
                    if "rz" in gate_str or "zpow" in gate_str:
                        if angle == math.pi: new_qc.z(targets[0])
                        elif angle == math.pi / 2: new_qc.s(targets[0])
                        elif angle == math.pi / 4: new_qc.t(targets[0])
                        else: new_qc.rz(targets[0], angle)
                        continue

                if gate_str == "h":
                    new_qc.h(targets[0])
                elif gate_str in ("x", "y", "z", "s", "t"):
                    getattr(new_qc, gate_str)(targets[0])
                elif "cnot" in gate_str or gate_str == "cx":
                    new_qc.cx(targets[0], targets[1])
                elif gate_str == "cz":
                    new_qc.cz(targets[0], targets[1])
                elif gate_str == "swap":
                    new_qc.swap(targets[0], targets[1])
                elif "ccx" in gate_str or "toffoli" in gate_str:
                    new_qc.ccx(targets[0], targets[1], targets[2])
                else:
                    raise ValueError(f"Unsupported Cirq gate: {gate}")
        return new_qc

depth property

Circuit depth (longest critical path).

gate_count property

Total number of gates.

parameters property

Unique parameters in the circuit.

bind_parameters(value_dict)

Replace parameters with values and return a new circuit.

Source code in hlquantum/circuit.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
def bind_parameters(self, value_dict: Dict[Union[str, Parameter], float]) -> "Circuit":
    """Replace parameters with values and return a new circuit."""
    normalized_values = {}
    for k, v in value_dict.items():
        name = k.name if isinstance(k, Parameter) else k
        normalized_values[name] = v

    new_qc = Circuit(self.num_qubits)
    new_qc.metadata = self.metadata.copy()

    for gate in self.gates:
        new_params = []
        for p in gate.params:
            if isinstance(p, Parameter):
                if p.name not in normalized_values:
                    raise ValueError(f"Missing parameter value: {p.name}")
                new_params.append(normalized_values[p.name])
            else:
                new_params.append(p)

        new_gate = Gate(
            name=gate.name,
            targets=gate.targets,
            controls=gate.controls,
            params=tuple(new_params)
        )
        new_qc.gates.append(new_gate)

    return new_qc

from_cirq(circuit) classmethod

Import circuit from Cirq.

Source code in hlquantum/circuit.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
@classmethod
def from_cirq(cls, circuit: Any) -> "Circuit":
    """Import circuit from Cirq."""
    import math
    qubits = sorted(list(circuit.all_qubits()))
    qubit_map = {q: i for i, q in enumerate(qubits)}
    new_qc = cls(max(1, len(qubits)))

    for moment in circuit:
        for op in moment:
            gate = op.gate
            targets = [qubit_map[q] for q in op.qubits]
            gate_str = str(gate).lower()

            if "measure" in gate_str:
                new_qc.measure(targets[0])
                continue

            if hasattr(gate, "exponent"):
                angle = float(gate.exponent) * math.pi
                if "rx" in gate_str or "xpow" in gate_str:
                    if angle == math.pi: new_qc.x(targets[0])
                    else: new_qc.rx(targets[0], angle)
                    continue
                if "ry" in gate_str or "ypow" in gate_str:
                    if angle == math.pi: new_qc.y(targets[0])
                    else: new_qc.ry(targets[0], angle)
                    continue
                if "rz" in gate_str or "zpow" in gate_str:
                    if angle == math.pi: new_qc.z(targets[0])
                    elif angle == math.pi / 2: new_qc.s(targets[0])
                    elif angle == math.pi / 4: new_qc.t(targets[0])
                    else: new_qc.rz(targets[0], angle)
                    continue

            if gate_str == "h":
                new_qc.h(targets[0])
            elif gate_str in ("x", "y", "z", "s", "t"):
                getattr(new_qc, gate_str)(targets[0])
            elif "cnot" in gate_str or gate_str == "cx":
                new_qc.cx(targets[0], targets[1])
            elif gate_str == "cz":
                new_qc.cz(targets[0], targets[1])
            elif gate_str == "swap":
                new_qc.swap(targets[0], targets[1])
            elif "ccx" in gate_str or "toffoli" in gate_str:
                new_qc.ccx(targets[0], targets[1], targets[2])
            else:
                raise ValueError(f"Unsupported Cirq gate: {gate}")
    return new_qc

from_qiskit(qc) classmethod

Import circuit from Qiskit.

Source code in hlquantum/circuit.py
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
@classmethod
def from_qiskit(cls, qc: Any) -> "Circuit":
    """Import circuit from Qiskit."""
    new_qc = cls(qc.num_qubits)
    for instruction in qc.data:
        op, qargs = instruction.operation, instruction.qubits
        name = op.name
        try:
            targets = [qc.find_bit(q).index for q in qargs]
        except AttributeError:
            targets = [q.index for q in qargs]

        if name in ("rx", "ry", "rz", "p", "u1"):
            func_name = "rz" if name in ("p", "u1", "rz") else name
            getattr(new_qc, func_name)(targets[0], float(op.params[0]))
        elif name in ("cx", "cz"):
            getattr(new_qc, name)(targets[0], targets[1])
        elif name == "swap":
            new_qc.swap(targets[0], targets[1])
        elif name == "ccx":
            new_qc.ccx(targets[0], targets[1], targets[2])
        elif name in ("h", "x", "y", "z", "s", "t"):
            getattr(new_qc, name)(targets[0])
        elif name == "measure":
            new_qc.measure(targets[0])
        elif name == "barrier":
            pass
        else:
            raise ValueError(f"Unsupported Qiskit gate: {name}")
    return new_qc

Gate dataclass

A single quantum gate operation.

Source code in hlquantum/circuit.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@dataclass
class Gate:
    """A single quantum gate operation."""

    name: str
    targets: Tuple[int, ...]
    controls: Tuple[int, ...] = ()
    params: Tuple[Union[float, Parameter], ...] = ()

    def __repr__(self) -> str:
        parts = [self.name]
        if self.controls:
            parts.append(f"controls={self.controls}")
        parts.append(f"targets={self.targets}")
        if self.params:
            param_repr = [str(p) if isinstance(p, Parameter) else f"{p:.3f}" for p in self.params]
            parts.append(f"params=({', '.join(param_repr)})")
        return f"Gate({', '.join(parts)})"

Parameter dataclass

A symbolic parameter for a quantum gate.

Source code in hlquantum/circuit.py
 9
10
11
12
13
14
15
@dataclass(frozen=True)
class Parameter:
    """A symbolic parameter for a quantum gate."""
    name: str

    def __repr__(self) -> str:
        return f"${self.name}"

Kernels

The @kernel decorator lets you write quantum logic as plain Python functions.

from hlquantum import kernel

@kernel(num_qubits=2)
def bell(qc):
    qc.h(0)
    qc.cx(0, 1)
    qc.measure_all()

print(bell.circuit)  # QuantumCircuit(num_qubits=2, gates=4)

Quantum kernel decorator and wrapper.

Kernel

Wraps a function into a reusable quantum kernel.

Source code in hlquantum/kernel.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Kernel:
    """Wraps a function into a reusable quantum kernel."""

    def __init__(self, fn: Callable, num_qubits: int) -> None:
        self._fn = fn
        self.num_qubits = num_qubits
        self.name = fn.__name__
        functools.update_wrapper(self, fn)

    @property
    def circuit(self) -> QuantumCircuit:
        qc = QuantumCircuit(self.num_qubits)
        self._fn(qc)
        return qc

    def __call__(self, qc: Optional[QuantumCircuit] = None) -> QuantumCircuit:
        if qc is None:
            qc = QuantumCircuit(self.num_qubits)
        self._fn(qc)
        return qc

    def __repr__(self) -> str:
        return f"Kernel({self.name!r}, num_qubits={self.num_qubits})"

kernel(num_qubits)

Decorator to transform a function into a Kernel.

Source code in hlquantum/kernel.py
36
37
38
39
40
def kernel(num_qubits: int) -> Callable:
    """Decorator to transform a function into a Kernel."""
    def decorator(fn: Callable) -> Kernel:
        return Kernel(fn, num_qubits)
    return decorator

Runner

The run() function is the high-level entry point. It accepts a circuit or kernel, optionally transpiles it, executes on a backend, and applies error mitigation.

import hlquantum as hlq
from hlquantum.mitigation import ThresholdMitigation

result = hlq.run(
    bell,
    shots=1000,
    transpile=True,
    mitigation=ThresholdMitigation(threshold=0.01),
)

Convenience helpers for executing quantum circuits.

run(circuit_or_kernel, *, shots=1000, include_statevector=False, transpile=False, mitigation=None, backend=None, **kwargs)

Execute a circuit or kernel and return the result.

Source code in hlquantum/runner.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def run(
    circuit_or_kernel: Union[QuantumCircuit, Kernel],
    *,
    shots: int = 1000,
    include_statevector: bool = False,
    transpile: bool = False,
    mitigation: Optional[Union[List[Any], Any]] = None,
    backend: Optional[Backend] = None,
    **kwargs,
) -> ExecutionResult:
    """Execute a circuit or kernel and return the result."""
    if isinstance(circuit_or_kernel, Kernel):
        circuit = circuit_or_kernel.circuit
    elif isinstance(circuit_or_kernel, QuantumCircuit):
        circuit = circuit_or_kernel
    else:
        raise TypeError(f"Expected Circuit or Kernel, got {type(circuit_or_kernel).__name__}")

    if transpile:
        circuit = default_transpile(circuit)

    be = backend or get_default_backend()
    be.validate(circuit)

    result = be.run(
        circuit, 
        shots=shots, 
        include_statevector=include_statevector, 
        **kwargs
    )

    if mitigation:
        methods = mitigation if isinstance(mitigation, list) else [mitigation]
        result = apply_mitigation(result, methods)

    return result

Results

Every backend returns an ExecutionResult dataclass with counts, probabilities, expectation values, and optional state-vector access.

result.counts             # {'00': 512, '11': 488}
result.probabilities      # {'00': 0.512, '11': 0.488}
result.most_probable      # '00'
result.expectation_value()  # 1.0 (parity-based)
result.get_state_vector() # numpy array (simulators only)

Unified result container for circuit executions.

ExecutionResult dataclass

Result of a quantum circuit execution.

Source code in hlquantum/result.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@dataclass
class ExecutionResult:
    """Result of a quantum circuit execution."""

    counts: Dict[str, int] = field(default_factory=dict)
    shots: int = 0
    backend_name: str = ""
    raw: Any = None
    state_vector: Optional[Any] = None
    metadata: Dict[str, Any] = field(default_factory=dict)

    def get_state_vector(self) -> Any:
        """Return the state vector as a numpy array."""
        if self.state_vector is None:
            return None
        import numpy as np
        return np.asarray(self.state_vector)

    @property
    def probabilities(self) -> Dict[str, float]:
        if self.shots == 0:
            return {}
        return {k: v / self.shots for k, v in self.counts.items()}

    @property
    def most_probable(self) -> Optional[str]:
        if not self.counts:
            return None
        return max(self.counts, key=self.counts.get)

    def expectation_value(self) -> float:
        if self.shots == 0:
            return 0.0
        total = 0.0
        for bitstring, count in self.counts.items():
            parity = (-1) ** bitstring.count("1")
            total += parity * count
        return total / self.shots

    def __repr__(self) -> str:
        top = dict(sorted(self.counts.items(), key=lambda x: -x[1])[:5])
        return (
            f"ExecutionResult(shots={self.shots}, "
            f"backend={self.backend_name!r}, top_counts={top})"
        )

get_state_vector()

Return the state vector as a numpy array.

Source code in hlquantum/result.py
20
21
22
23
24
25
def get_state_vector(self) -> Any:
    """Return the state vector as a numpy array."""
    if self.state_vector is None:
        return None
    import numpy as np
    return np.asarray(self.state_vector)

Exceptions

All HLQuantum-specific errors inherit from HLQuantumError.

Exception Purpose
HLQuantumError Base class
BackendError Backend execution failure
CircuitValidationError Invalid circuit structure
BackendNotAvailableError Required SDK not installed

Custom exception hierarchy.

BackendError

Bases: HLQuantumError

Raised on backend execution error.

Source code in hlquantum/exceptions.py
8
9
class BackendError(HLQuantumError):
    """Raised on backend execution error."""

BackendNotAvailableError

Bases: HLQuantumError

Raised when backend is unavailable.

Source code in hlquantum/exceptions.py
16
17
class BackendNotAvailableError(HLQuantumError):
    """Raised when backend is unavailable."""

CircuitValidationError

Bases: HLQuantumError

Raised on circuit validation failure.

Source code in hlquantum/exceptions.py
12
13
class CircuitValidationError(HLQuantumError):
    """Raised on circuit validation failure."""

HLQuantumError

Bases: Exception

Base exception for all HLQuantum errors.

Source code in hlquantum/exceptions.py
4
5
class HLQuantumError(Exception):
    """Base exception for all HLQuantum errors."""