Usage

Backends

QddProvider exposes QDD as Qiskit BackendV2 objects. The default backend is qasm_simulator, which returns shot counts or exact probabilities. The statevector_simulator backend returns statevectors.

from qdd import QddProvider

provider = QddProvider()
backend = provider.get_backend("qasm_simulator")
statevector_backend = provider.get_backend("statevector_simulator")

If the backend name is omitted, QddProvider().get_backend() returns qasm_simulator.

Run a Qiskit Circuit

Use the backend with normal Qiskit QuantumCircuit objects. Transpiling for the QDD backend is recommended when the circuit may contain gates outside QDD’s native basis.

from qiskit import QuantumCircuit, transpile

from qdd import QddProvider

backend = QddProvider().get_backend("qasm_simulator")

circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.measure_all()

compiled = transpile(circuit, backend, seed_transpiler=1234)
job = backend.run(
    compiled,
    shots=2048,
    memory=True,
    seed_simulator=1234,
)
result = job.result()

print(result.get_counts())
print(result.get_memory()[:5])

Set shots=None to compute exact probabilities instead of sampling:

probability_circuit = circuit.remove_final_measurements(inplace=False)
compiled = transpile(probability_circuit, backend, seed_transpiler=1234)
result = backend.run(compiled, shots=None).result()
print(result.data(0)["probabilities"])

Runtime options can be passed to run or stored on the backend:

backend.set_options(shots=4096, seed_simulator=1234)
result = backend.run(compiled).result()

result = backend.run(compiled, shots=1024, seed_simulator=5678).result()

Common runtime options are shots, memory, seed_simulator, use_mpi, show_progress, and show_progress_frequency.

Statevector Simulation

Use statevector_simulator for final statevectors. The circuit should not end with measurements when you want the unmeasured quantum state.

from qiskit import QuantumCircuit, transpile

from qdd import QddProvider

backend = QddProvider().get_backend("statevector_simulator")

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)

compiled = transpile(circuit, backend, seed_transpiler=1234)
statevector = backend.run(compiled).result().get_statevector()
print(statevector)

QDD Sampler

QDD also provides a Sampler implementation for Qiskit primitive-style inputs. Each pub is a tuple of (circuit, parameter_values).

from qiskit import QuantumCircuit

from qdd.qdd_sampler import Sampler

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()

sampler = Sampler()
job = sampler.run(pubs=[(circuit, [])], shots=4096)
result = job.result()[0]

print(result.data.quasi_dist.binary_probabilities())
print(result.metadata["shots"])

Use is_exact=True to request exact probabilities with shots=None:

exact_job = sampler.run(pubs=[(circuit, [])], is_exact=True)
exact_result = exact_job.result()[0]
print(exact_result.data.quasi_dist.binary_probabilities())

Pass backend options, transpile options, or skip Qiskit transpilation in the constructor. For example, this sets QDD backend shots and asks Qiskit transpilation to use optimization level 1:

sampler = Sampler(
    backend_options={"shots": 4096},
    transpile_options={"optimization_level": 1},
)

QDD Estimator

The Estimator computes expectation values for observables represented by Qiskit primitive inputs.

from qiskit.circuit.library import RealAmplitudes
from qiskit.quantum_info import SparsePauliOp

from qdd.qdd_estimator import Estimator

ansatz = RealAmplitudes(num_qubits=2, reps=1)
observable = SparsePauliOp.from_list([("ZZ", 1.0), ("XI", 0.5)])
theta = [0.1, 0.2, 0.3, 0.4]

estimator = Estimator(default_precision=0.0)
job = estimator.run([(ansatz, [observable], theta)])
result = job.result()[0]

print(result.data.evs)

default_precision=0.0 requests exact probabilities. Positive precision values are translated into a shot count. You can also override precision on each run call:

job = estimator.run([(ansatz, [observable], theta)], precision=0.01)
result = job.result()[0]
print(result.data.evs)
print(result.data.stds)

Backend options are accepted by both primitives. With the qdd-mpi package, pass {"use_mpi": True} and launch the Python process with mpirun; see MPI Support for a complete MPI example.