Running on Amazon Braket (and real QPUs)
The "braket" provider can target the AWS Braket service — the managed
simulators (SV1, DM1, TN1) and the real QPUs (IonQ, IQM, Rigetti) — not just the
local simulator.
Doing so requires one change of protocol, because of a hard constraint:
:::{important}
A QPU never returns a state vector. Braket rejects the StateVector result
type whenever shots > 0, and every QPU requires shots > 0. The exact
state-vector path that makes simulation fast therefore cannot run on
hardware.
The energy has to be measured instead:
with each \(\langle P_j\rangle\) estimated from shots in that Pauli’s own eigenbasis. :::
Carcará implements that path. Pass shots > 0 and the driver switches from
amplitudes to measurements automatically.
Quick start
from ase import Atoms
from carcara.algorithms import VQE
atoms = Atoms("H2", positions=[[3, 3, 2.63], [3, 3, 3.37]],
cell=[[6, 0, 0], [0, 6, 0], [0, 0, 6]], pbc=True)
# Local Braket simulator, shot-based (identical protocol to a QPU).
atoms.calc = VQE(basis="FAO", h=0.35, device="braket-local", shots=8192)
atoms.get_total_energy()
# The AWS managed simulator.
atoms.calc = VQE(basis="FAO", h=0.35, device="braket-sv1", shots=8192)
# A real trapped-ion QPU.
atoms.calc = VQE(basis="FAO", h=0.35, device="braket-ionq-aria", shots=8192)
Naming a Braket device selects the braket provider automatically, so
backend_provider is optional. Anything beyond device= and shots= is
unchanged — the whole driver API is identical.
:::{warning}
AWS devices need configured credentials (aws configure) and bill your
account per quantum task. Estimate the cost first (see Cost below).
:::
How the energy is measured
Measuring one Pauli term per circuit is correct but ruinous — a modest active space has \(10^2\)–\(10^4\) terms. Carcará instead partitions the Hamiltonian into qubit-wise commuting (QWC) groups: two Pauli strings are QWC when, on every qubit where both act non-trivially, they carry the same Pauli. A QWC set is measurable by a single circuit — rotate each qubit once into the basis its group prescribes, measure everything, and read every term’s expectation value out of the same bit-strings.
from carcara.backends.measurement import qubit_wise_commuting_groups
groups, identity = qubit_wise_commuting_groups(hamiltonian)
len(groups) # circuits per energy evaluation
On LiH this collapses 118 Pauli terms into 29 measurement circuits; on H₂, 14 terms into 5. The identity term needs no measurement and is added as a constant.
The estimate converges as \(1/\sqrt{\text{shots}}\):
shots E (Ha) error 1-sigma bound
500 -0.93823185 -3.98e-04 6.60e-02
5000 -0.94026417 -2.43e-03 2.09e-02
50000 -0.93703754 +7.96e-04 6.60e-03
exact -0.93783349
Registered devices
from carcara.backends.hardware import describe_devices, device_arn
for device in describe_devices():
print(device.name, device.simulator, device.arn)
device |
kind |
shots |
ARN |
|---|---|---|---|
|
simulator |
optional |
(local) |
|
simulator |
optional |
|
|
simulator |
optional |
|
|
simulator |
optional |
|
|
QPU |
required |
|
|
QPU |
required |
|
|
QPU |
required |
|
|
QPU |
required |
|
A raw ARN is accepted too, so a device released after this version can still be named:
VQE(basis="FAO", device="arn:aws:braket:eu-west-2::device/qpu/vendor/New-1",
shots=4096)
Naming a QPU without shots is refused in the constructor rather than at
submission time:
>>> ADAPTVQE(device="braket-ionq-aria")
ValueError: device 'braket-ionq-aria' is real quantum hardware, which cannot
return a state vector: pass shots > 0 (e.g. shots=8192) so the energy is
estimated from measurements.
Gate set
Carcará emits only X, H, S, Si, CNot and Rz — all Braket-native and
available on every Braket QPU (the device’s own compiler maps them to its native
basis). examples/13_braket_aws_compatibility.py verifies this on every run.
Cost
A hardware run is billed per quantum task, and one energy evaluation costs one task per QWC group. Plan before submitting:
from carcara.backends.measurement import shot_noise_estimate
from carcara.backends.providers import build_provider
provider = build_provider("braket", shots=8192)
groups = provider.measurement_groups(hamiltonian)
print(f"{len(groups)} tasks per energy evaluation")
print(f"+/- {shot_noise_estimate(hamiltonian, 8192):.2e} Ha at 8192 shots")
The standard error is bounded by \(\left(\sum_j |c_j|\right)/\sqrt{\text{shots}}\) — the coefficient 1-norm estimate. For H₂ that 1-norm is 1.48 Ha, so chemical accuracy (1.6 mHa) needs \(\sim\!8.5\times10^5\) shots per group in the worst case. That bound is why hardware VQE needs error mitigation and smarter estimators; it is not a defect of the implementation.
Current limitation
:::{admonition} Only the energy evaluation is hardware-native :class: caution
The energy runs on the device, but ADAPT-VQE’s pool-gradient screening is still computed classically from the state vector. A fully hardware-native adaptive loop would have to measure each pool gradient as well; that is not implemented yet.
For a fixed ansatz, VQE is fully hardware-native
today — every cost evaluation in the optimization is measured on the device.
:::
Verifying compatibility
examples/13_braket_aws_compatibility.py runs the full check locally — no AWS
account, no charges — and prints a report covering the gate set, the
shots-versus-state-vector constraint, QWC grouping, shot-noise convergence,
per-evaluation task count, and the registered devices:
python examples/13_braket_aws_compatibility.py