Algorithm API design conventions

uniqc.algorithms exposes its primitives as circuit fragments — self-contained Circuit objects you can assemble freely with Circuit.add_circuit(). This page documents the four conventions used across the package.

1. Variational ansätze — _ansatz suffix

Variational algorithms expose a parameterised ansatz factory that returns a fresh :class:~uniqc.circuit_builder.Circuit.

from uniqc import hea, hva, qaoa_ansatz, uccsd_ansatz, vqd_ansatz
import numpy as np

# Default HEA (backward-compatible)
c = hea(n_qubits=4, depth=2, params=np.zeros(16))

# Enhanced HEA: custom rotations, gates, and topology
c = hea(n_qubits=4, depth=2,
        rotation_gates=["rx", "ry", "rz"],
        entangling_gate="cz",
        topology="linear")

# Parametric entangling gate (extra params per edge)
c = hea(n_qubits=4, depth=2, entangling_gate="xx", topology="ring")

# HVA with commuting Hamiltonian groups
groups = [[("X0X1", 1.0), ("Y0Y1", 1.0)], [("Z0Z1", 0.5)]]
c = hva(groups, p=2)

Names: hea, hva, qaoa_ansatz, uccsd_ansatz, vqd_ansatz.

2. State preparation — _circuit suffix

State-preparation primitives take an n_qubits: int (or qubits list) and return a fresh Circuit containing only state-prep gates (no measurements):

from uniqc.algorithms import (
    qft_circuit, dicke_state_circuit, thermal_state_circuit,
    ghz_state, w_state, cluster_state,
)

c = qft_circuit(3)              # 3-qubit QFT
c = ghz_state(4)                # 4-qubit GHZ

The legacy in-place forms (e.g. qft_circuit(circuit, qubits=...)) are kept as deprecated dispatch and emit a DeprecationWarning.

3. Oracular algorithms — input a Circuit

Algorithms that consume an oracle accept a Circuit as their first argument and return a fresh Circuit:

from uniqc.algorithms import (
    deutsch_jozsa_oracle, deutsch_jozsa_circuit,
    grover_oracle, grover_diffusion,
    amplitude_estimation_circuit,
)

oracle = deutsch_jozsa_oracle(qubits=[0, 1, 2], balanced=True)
dj     = deutsch_jozsa_circuit(oracle, qubits=[0, 1, 2])

ora    = grover_oracle(marked_state=5, qubits=[0, 1, 2])
diff   = grover_diffusion(qubits=[0, 1, 2])

4. Measurement — class-based interface

Measurement primitives are classes. The constructor takes the clean state-preparation circuit (no measurements); the class adds basis rotations and measurements internally without mutating the input.

Each class exposes:

  • .get_readout_circuits() -> list[Circuit] — circuits to actually run

  • .execute(backend="statevector", *, program_type="qasm", **kwargs) — run them and return the post-processed result

from uniqc.circuit_builder import Circuit
from uniqc.algorithms import (
    PauliExpectation, StateTomography, ClassicalShadow,
    BasisRotationMeasurement,
)

c = Circuit()
c.h(0); c.cx(0, 1)

value = PauliExpectation(c, "ZZ").execute("statevector")
rho   = StateTomography(c, shots=8192).execute()

The free-function APIs (pauli_expectation, state_tomography, classical_shadow, basis_rotation_measurement) remain available as thin convenience wrappers but new code should prefer the class form.

5. _example helpers

Every algorithm module additionally exports a <name>_example() function intended for documentation and quick smoke-tests. They are not part of __all__ — import them via the full module path:

from uniqc.algorithms.core.circuits.qft import qft_example
c = qft_example()

6. Parameter management

All ansatz functions accept either np.ndarray (backward-compatible) or Parameters objects for symbolic parameter management.

from uniqc.circuit_builder import Parameters
from uniqc.algorithms.core.ansatz import hea, hea_param_count

# Auto-generation: when params=None, a named Parameters object is created
c = hea(n_qubits=4, depth=2)
print(c._params.name)  # "theta_hea"
print(len(c._params))  # 16

# Manual Parameters: create, bind, and use
n_params = hea_param_count(n_qubits=4, depth=2,
                          rotation_gates=["rx", "ry", "rz"])
params = Parameters("my_ansatz", size=n_params)
params.bind([0.1] * n_params)
c = hea(n_qubits=4, depth=2, params=params)

# QAOA uses separate betas and gammas
c = qaoa_ansatz(H, p=2)
print(c._params["betas"].name)  # "betas_qaoa"
print(c._params["gammas"].name) # "gammas_qaoa"

Benefits of Parameters:

  • Named parameters for debugging and gradient tracking

  • Symbolic arithmetic via sympy expressions

  • Rebindable values for multiple optimization runs

  • See: examples/2_advanced/algorithms/parameters_demo.py