Running FAST-VQE on Quantum Computers
This example demonstrates how to run FAST-VQE with custom estimators and samplers — including Amazon Braket’s local simulator interfaces, and real quantum hardware - specifically Rigetti’s Ankaa-3 device.
We will also estimate the total shot counts and the approximate execution cost before running the on the Rigetti’s Ankaa-3 device.
Full Script
The script below shows the complete workflow; each section is explained afterward.
1"""Run FAST-VQE with non-default estimators and samplers."""
2# pyright: reportUnknownVariableType=false, reportMissingImports=false
3# ruff: noqa
4
5from pathlib import Path
6
7import matplotlib.pyplot as plt
8import numpy as np
9from braket.devices import Devices
10
11import qrunch as qc
12
13
14def plot_energies(
15 fast_convergence: list[tuple[list[float], list[float], str]],
16 *,
17 outdir: Path = Path("dist"),
18 show: bool = False,
19) -> None:
20 """
21 Plot VQE energy convergence and log-scale error vs reference energy.
22
23 Args:
24 fast_convergence: Sequence of energies, errors and label.
25 outdir: Output directory where plots are written. Defaults to "dist".
26 show: Whether to display the figures interactively after saving.
27 """
28 # Create figure and axis
29 fig, ax = plt.subplots(figsize=(8, 5))
30
31 # Ensure output directory exists.
32 outdir.mkdir(parents=True, exist_ok=True)
33
34 for fast_energies, energy_errors, label in fast_convergence:
35 energies = np.array(fast_energies)
36 errors = np.array(energy_errors)
37
38 # Prepare x-axis as 1-based iteration indices for readability.
39 iterations = np.arange(1, len(energies) + 1)
40
41 # Plot error bars
42 ax.errorbar(
43 iterations,
44 energies,
45 yerr=errors,
46 fmt="o",
47 capsize=5,
48 elinewidth=1.2,
49 markeredgewidth=1.2,
50 label=label,
51 )
52
53 # Label axes and title
54 ax.set_xlabel("Iteration")
55 ax.set_ylabel("Total Energy [Hartree]")
56 ax.set_title("FAST-VQE Convergence with Error Bars due to shot Noise, and emulated noise")
57
58 # Grid and legend
59 ax.grid(True, linestyle="--", alpha=0.5)
60 ax.legend()
61
62 # Tight layout and save plots
63 fig.tight_layout()
64
65 convergence_plot = outdir / "vqe_convergence.png"
66 fig.savefig(convergence_plot, dpi=200, bbox_inches="tight")
67 if show:
68 plt.show()
69 else:
70 # Close figures to free memory when running in batch contexts.
71 plt.close(fig)
72
73
74
75
76def main(
77 *,
78 do_local_braket: bool = True,
79 do_noisy_sampler: bool = True,
80 do_real_hardware: bool = False,
81) -> tuple[float, float]:
82 """
83 Run FAST-VQE on LiH with different estimators and samplers.
84
85 Args:
86 do_local_braket: Whether to run with the local Braket backend simulator.
87 do_noisy_sampler: Whether to run with a noisy sampler emulating Ankaa-3.
88 do_real_hardware: Whether to run with real quantum hardware (Ankaa-3).
89
90 """
91
92 # Build Lithium Hydride (LiH) molecular configuration.
93 molecular_configuration = qc.build_molecular_configuration(
94 molecule=[
95 ("H", 0.0, 0.0, 0.0),
96 ("Li", 1.5474, 0.0, 0.0),
97 ],
98 basis_set="sto3g",
99 )
100 # Build ground state problem.
101 problem_builder = qc.problem_builder_creator().ground_state().standard().create()
102 ground_state_problem = problem_builder.build_unrestricted(molecular_configuration)
103
104 # Excitation gate estimator (Kvantifys proprietary chemistry tailored state vector simulator)
105 excitation_gate_estimator = (
106 qc.estimator_creator()
107 .excitation_gate() # Narrow the estimator type to an excitation gate estimator
108 .with_parallel_setting("parallel") # Configure parallelization behavior: "serial" or "parallel"
109 .with_spin_particle_conservation() # Configure to conserve alpha and beta electrons separately
110 .choose_estimator_error_mitigator() # Start sub-choice: Choose estimator error mitigator
111 .symmetry_adapted() # Perform the sub-choice selection - Here we pick the symmetry adapted error mitigator
112 .create(with_shot_counter=True) # Create the estimator instance
113 )
114
115 # Excitation gate sampler (Kvantifys proprietary chemistry tailored state vector simulator)
116 excitation_gate_sampler = (
117 qc.sampler_creator()
118 .excitation_gate() # Narrow the sampler type to an excitation gate sampler
119 .with_parallel_setting("parallel") # Configure parallelization behavior: "serial" or "parallel"
120 .with_spin_particle_conservation() # Configure to conserve alpha and beta electrons separately
121 .create(with_shot_counter=True) # Create the sampler instance
122 )
123
124 # A FAST gate selector using the excitation gate sampler.
125 excitation_gate_gate_selector = (
126 qc.gate_selector_creator()
127 .fast() # Narrow the gate selector type to a FAST gate selector
128 .with_shots(10_000) # Configure to use 10,000 shots when estimating sampling
129 .with_sampler(excitation_gate_sampler) # Configure to use the excitation gate sampler
130 .create() # Create the gate selector instance
131 )
132
133 # Build the user-configured VQE instance with the excitation_gate_estimator
134 # and gate selector that uses the excitation_gate_sampler
135 excitation_gate_fast_vqe_calculator = (
136 qc.calculator_creator() # Start creating a VQE instance
137 .vqe() # Narrow to Variational quantum eigensolver (VQE)
138 .iterative() # Narrow to the iterative VQE
139 .standard() # Narrow to the standard FAST-VQE
140 .with_options( # Configure to use the user-defined iterative VQE options
141 options=qc.options.IterativeVqeOptions(max_iterations=10)
142 )
143 .choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
144 .quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
145 .with_estimator(excitation_gate_estimator) # Configure to use the excitation gate estimator
146 .with_estimator_shots(100_000) # Configure to use 10,000 shots in the estimator
147 .with_gate_selector(excitation_gate_gate_selector) # Configure to use the gate selector
148 .create() # Create the calculator instance
149 )
150 excitation_gate_result = excitation_gate_fast_vqe_calculator.calculate(ground_state_problem)
151
152 print("result from excitation gate estimator and sampler:") # noqa: T201
153 print(excitation_gate_result) # noqa: T201
154
155 print("Total number of shots used in estimator", excitation_gate_estimator.total_shots) # noqa: T201
156 estimated_estimator_cost = excitation_gate_estimator.total_braket_price(device=Devices.Rigetti.Ankaa3)
157 print("Estimated cost in dollars for Estimator on Devices.Rigetti.Ankaa3: ", estimated_estimator_cost) # noqa: T201
158
159 print("Total number of shots used in sampler", excitation_gate_sampler.total_shots) # noqa: T201
160 estimated_sampler_cost = excitation_gate_sampler.total_braket_price(device=Devices.Rigetti.Ankaa3)
161 print("Estimated cost in dollars for Sampler on Devices.Rigetti.Ankaa3: ", estimated_sampler_cost) # noqa: T201
162
163 local_braket_result = None
164 if do_local_braket:
165 # An Amazon Local Braket backend estimator
166 local_braket_estimator = (
167 qc.estimator_creator()
168 .backend() # Narrow the estimator type to a backend estimator
169 .choose_backend() # Start sub-choice: Choose backend
170 .local_amazon_braket() # Perform the sub-choice selection - Here we pick the local Braket state vector simulator backend
171 .create(with_shot_counter=True) # Create the estimator instance
172 )
173
174 # An Amazon Local Braket backend sampler
175 local_braket_sampler = (
176 qc.sampler_creator()
177 .backend() # Narrow the sampler type to a backend sampler
178 .choose_backend() # Start sub-choice: Choose backend
179 .local_amazon_braket() # Perform the sub-choice selection - Here we pick the local Braket simulator backend
180 .create(with_shot_counter=True) # Create the estimator instance
181 )
182
183 # A FAST gate selector using the local braket sampler.
184 local_braket_gate_selector = (
185 qc.gate_selector_creator()
186 .fast() # Narrow the gate selector type to a FAST gate selector
187 .with_shots(10_000) # Configure to use 10,000 shots when estimating sampling
188 .with_sampler(local_braket_sampler) # Configure to use the local braket sampler
189 .create() # Create the gate selector instance
190 )
191
192 # Create the calculator instance using the local_braket_estimator
193 # and gate selector that uses the local_braket_sampler
194 local_braket_fast_vqe_calculator = (
195 qc.calculator_creator() # Start creating a calculator instance
196 .vqe() # Narrow: pick Variational quantum eigensolver (VQE)
197 .iterative() # Narrow to the iterative VQE
198 .standard() # Narrow to the standard FAST-VQE
199 .with_options( # Configure to use the user-defined iterative VQE options
200 options=qc.options.IterativeVqeOptions(max_iterations=10)
201 )
202 .choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
203 .quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
204 .with_estimator(local_braket_estimator) # Configure to use the local braket estimator
205 .with_estimator_shots(100_000) # Configure to use 10,000 shots in the estimator
206 .with_gate_selector(local_braket_gate_selector) # Configure to use the gate selector
207 .create() # Create the calculator instance
208 )
209 local_braket_result = local_braket_fast_vqe_calculator.calculate(ground_state_problem)
210
211 print("Total number of shots used in estimator", local_braket_estimator.total_shots) # noqa: T201
212 estimated_estimator_cost = local_braket_estimator.total_braket_price(device=Devices.Rigetti.Ankaa3)
213 print("Estimated cost in dollars for Estimator on Devices.Rigetti.Ankaa3: ", estimated_estimator_cost) # noqa: T201
214
215 print("Total number of shots used in sampler", local_braket_sampler.total_shots) # noqa: T201
216 estimated_sampler_cost = local_braket_sampler.total_braket_price(device=Devices.Rigetti.Ankaa3)
217 print("Estimated cost in dollars for Sampler on Devices.Rigetti.Ankaa3: ", estimated_sampler_cost) # noqa: T201
218
219 print("result from local braket estimator and sampler:") # noqa: T201
220 print(local_braket_result) # noqa: T201
221
222 noisy_result = None
223 if do_noisy_sampler:
224
225 # Create a real Ankaa-3 Quantum Processor hardware backend.
226 backend = qc.backend_creator().amazon_braket(device=Devices.Rigetti.Ankaa3).create()
227
228 # Get device data from the backend - from the Ankaa-3 Quantum Processor
229 ankaa3_device_data = backend.get_device_data()
230
231 # An Amazon Local Braket backend simulator sampler
232 # that emulate the noise from the Ankaa-3 Quantum Processor
233 # using the density matrix simulator.
234 noisy_sampler = (
235 qc.sampler_creator()
236 .backend() # Narrow the estimator type to a backend estimator
237 .choose_backend() # Start sub-choice: Choose backend
238 .local_amazon_braket(
239 device_to_simulate=ankaa3_device_data
240 ) # Perform the sub-choice selection - Here the local braket emulating Ankaa-3
241 .choose_sampler_error_mitigator() # Start sub-choice: Choose sampler error mitigator
242 .hamming_weight_post_selection() # Configure the error mitigator to use hamming weight post selection.
243 .create() # Create the estimator instance
244 )
245
246 # A FAST gate selector using the noisy sampler that emulate an Ankaa-3 Quantum Processor.
247 noisy_gate_selector = (
248 qc.gate_selector_creator()
249 .fast() # Narrow the gate selector type to a FAST gate selector
250 .with_shots(10_000) # Configure to use 10,000 shots when estimating sampling
251 .with_sampler(noisy_sampler) # Configure to use the local braket sampler
252 .create() # Create the gate selector instance
253 )
254
255 # Build the user-configured VQE calculator instance with the excitation gate estimator
256 # and gate selector that uses the noisy sampler that emulate an Ankaa-3 Quantum Processor.
257 noisy_fast_vqe_calculator = (
258 qc.calculator_creator() # Start creating a calculator instance
259 .vqe() # Narrow: pick Variational quantum eigensolver (VQE)
260 .iterative() # Narrow to the iterative VQE
261 .standard() # Narrow to the standard FAST-VQE
262 .with_options( # Configure to use the user-defined iterative VQE options
263 options=qc.options.IterativeVqeOptions(max_iterations=10)
264 )
265 .choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
266 .quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
267 .with_estimator(excitation_gate_estimator) # Configure to use the excitation gate estimator
268 .with_estimator_shots(100_000) # Configure to use 10,000 shots in the estimator
269 .with_gate_selector(noisy_gate_selector) # Configure to use the gate selector
270 .create() # Create the calculator instance
271 )
272 noisy_result = noisy_fast_vqe_calculator.calculate(ground_state_problem)
273 print("result from the local braket estimator and noisy sampler:") # noqa: T201
274 print(noisy_result) # noqa: T201
275
276 real_hardware_braket_result = None
277 if do_real_hardware:
278 # A real hardware sampler using the Amazon Braket backend
279 # dispatching to an Ankaa-3 Quantum Processor
280 real_hardware_braket_sampler = (
281 qc.sampler_creator()
282 .backend() # Narrow the estimator type to a backend estimator
283 .choose_backend() # Start sub-choice: Choose backend
284 .amazon_braket(
285 device=Devices.Rigetti.Ankaa3
286 ) # Perform the sub-choice selection - Here we pick the Rigetti Ankaa3
287 .choose_sampler_error_mitigator() # Start sub-choice: Choose sampler error mitigator
288 .hamming_weight_post_selection() # Configure the error mitigator to use hamming weight post selection.
289 .create() # Create the estimator instance
290 )
291
292 # A FAST gate selector using the Ankaa-3 Quantum Processor for the sampler.
293 real_hardware_braket_gate_selector = (
294 qc.gate_selector_creator()
295 .fast() # Narrow the gate selector type to a FAST gate selector
296 .with_shots(10_000) # Configure to use 10,000 shots when estimating sampling
297 .with_sampler(real_hardware_braket_sampler) # Configure to use the local braket sampler
298 .create() # Create the gate selector instance
299 )
300
301 # Build the user-configured VQE calculator instance with the excitation gate estimator
302 # and gate selector that uses the Ankaa-3 Quantum Processor for the sampler.
303 real_hardware_braket_fast_vqe_calculator = (
304 qc.calculator_creator() # Start creating a calculator instance
305 .vqe() # Narrow: pick Variational quantum eigensolver (VQE)
306 .iterative() # Narrow to the iterative VQE
307 .standard() # Narrow to the standard FAST-VQE
308 .with_options( # Configure to use the user-defined iterative VQE options
309 options=qc.options.IterativeVqeOptions(max_iterations=10)
310 )
311 .choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
312 .quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
313 .with_estimator(excitation_gate_estimator) # Configure to use the excitation gate estimator
314 .with_estimator_shots(100_000) # Configure to use 10,000 shots in the estimator
315 .with_gate_selector(real_hardware_braket_gate_selector) # Configure to use the gate selector
316 .create() # Create the calculator instance
317 )
318 real_hardware_braket_result = real_hardware_braket_fast_vqe_calculator.calculate(ground_state_problem)
319
320 print("result from the excitation gate estimator and real quantum hardware sampler:") # noqa: T201
321 print(real_hardware_braket_result) # noqa: T201
322
323 fast_convergence: list[tuple[list[float], list[float], str]] = []
324
325 labels = ["Excitation Gate Simulator"]
326 results = [excitation_gate_result]
327 if do_local_braket and local_braket_result is not None:
328 labels.append("Local Braket Simulator")
329 results.append(local_braket_result)
330
331 if do_noisy_sampler and noisy_result is not None:
332 labels.append("Noisy Simulator")
333 results.append(noisy_result)
334
335 if do_real_hardware and real_hardware_braket_result is not None:
336 labels.append("Real Hardware")
337 results.append(real_hardware_braket_result)
338
339 for result, label in zip(results, labels, strict=True):
340 fast_energies = result.total_energy_per_macro_iteration_with_initial_energy_and_final_energy.values
341 energy_errors = result.total_energy_per_macro_iteration_with_initial_energy_and_final_energy.errors
342 fast_convergence.append((fast_energies, energy_errors, label))
343
344 plot_energies(
345 fast_convergence=fast_convergence,
346 show=True,
347 )
348
349 return excitation_gate_result.total_energy.value, excitation_gate_result.total_energy.error
350
351
352if __name__ == "__main__":
353 main()
Explanation
Imports
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from braket.devices import Devices
import qrunch as qc
We import the public Kvantify Qrunch API via import qrunch as qc (stable user interface)
and, since we will query device pricing and eventually run on the device, we also
import Devices from braket.devices.
In addition, we use matplotlib, numpy, and Path.
The plotting method
def plot_energies(
fast_convergence: list[tuple[list[float], list[float], str]],
*,
outdir: Path = Path("dist"),
show: bool = False,
) -> None:
"""
Plot VQE energy convergence and log-scale error vs reference energy.
Args:
fast_convergence: Sequence of energies, errors and label.
outdir: Output directory where plots are written. Defaults to "dist".
show: Whether to display the figures interactively after saving.
"""
# Create figure and axis
fig, ax = plt.subplots(figsize=(8, 5))
# Ensure output directory exists.
outdir.mkdir(parents=True, exist_ok=True)
for fast_energies, energy_errors, label in fast_convergence:
energies = np.array(fast_energies)
errors = np.array(energy_errors)
# Prepare x-axis as 1-based iteration indices for readability.
iterations = np.arange(1, len(energies) + 1)
# Plot error bars
ax.errorbar(
iterations,
energies,
yerr=errors,
fmt="o",
capsize=5,
elinewidth=1.2,
markeredgewidth=1.2,
label=label,
)
# Label axes and title
ax.set_xlabel("Iteration")
ax.set_ylabel("Total Energy [Hartree]")
ax.set_title("FAST-VQE Convergence with Error Bars due to shot Noise, and emulated noise")
# Grid and legend
ax.grid(True, linestyle="--", alpha=0.5)
ax.legend()
# Tight layout and save plots
fig.tight_layout()
convergence_plot = outdir / "vqe_convergence.png"
fig.savefig(convergence_plot, dpi=200, bbox_inches="tight")
if show:
plt.show()
else:
# Close figures to free memory when running in batch contexts.
plt.close(fig)
This code plots the convergence of the VQE energy with the error as error bars.
The code generates a PNG file under dist/ as vqe_convergence.png
Build the LiH ground-state problem
# Build Lithium Hydride (LiH) molecular configuration.
molecular_configuration = qc.build_molecular_configuration(
molecule=[
("H", 0.0, 0.0, 0.0),
("Li", 1.5474, 0.0, 0.0),
],
basis_set="sto3g",
)
# Build ground state problem.
problem_builder = qc.problem_builder_creator().ground_state().standard().create()
ground_state_problem = problem_builder.build_unrestricted(molecular_configuration)
We construct a Lithium Hydride (LiH) molecule using the minimal STO-3G basis.
Create an excitation-gate estimator
# Excitation gate estimator (Kvantifys proprietary chemistry tailored state vector simulator)
excitation_gate_estimator = (
qc.estimator_creator()
.excitation_gate() # Narrow the estimator type to an excitation gate estimator
.with_parallel_setting("parallel") # Configure parallelization behavior: "serial" or "parallel"
.with_spin_particle_conservation() # Configure to conserve alpha and beta electrons separately
.choose_estimator_error_mitigator() # Start sub-choice: Choose estimator error mitigator
.symmetry_adapted() # Perform the sub-choice selection - Here we pick the symmetry adapted error mitigator
.create(with_shot_counter=True) # Create the estimator instance
)
The excitation-gate estimator is Kvantify’s proprietary chemistry-aware state-vector simulator. It supports parallel execution and can enforce separate conservation of α and β electrons. We additionally attach a symmetry-adapted error mitigator and we enable shot counting for later cost analysis. This is the default estimator, but we here define it explicitly for clarity.
The estimator is the object that can evaluate expectation values of quantum circuits. In this case the estimator is used to evaluate the expectation value of the Hamiltonian (energy) of the quantum state prepared by the VQE iterative ansatz.
Create an excitation-gate sampler
# Excitation gate sampler (Kvantifys proprietary chemistry tailored state vector simulator)
excitation_gate_sampler = (
qc.sampler_creator()
.excitation_gate() # Narrow the sampler type to an excitation gate sampler
.with_parallel_setting("parallel") # Configure parallelization behavior: "serial" or "parallel"
.with_spin_particle_conservation() # Configure to conserve alpha and beta electrons separately
.create(with_shot_counter=True) # Create the sampler instance
)
Like the estimator, it runs in parallel mode, conserves spin, and maintains an internal shot counter.
The sampler is the object that provide a set of bitstrings representing measurement outcomes, and associated counts. In this case this means sampling the quantum state prepared by the VQE iterative ansatz, where each bitstring represents a Slater determinant (or occupation number vector).
Build the FAST gate selector
# A FAST gate selector using the excitation gate sampler.
excitation_gate_gate_selector = (
qc.gate_selector_creator()
.fast() # Narrow the gate selector type to a FAST gate selector
.with_shots(10_000) # Configure to use 10,000 shots when estimating sampling
.with_sampler(excitation_gate_sampler) # Configure to use the excitation gate sampler
.create() # Create the gate selector instance
)
A FAST gate selector controls which excitation gates are added during the iterative VQE procedure. Here it is configured to use the excitation-gate sampler and 10000 shots per iteration. Here we use the default FAST gate selector that uses the Heuristic Gradient metric to select gates. Note that the FAST gate selector only require a sampler, not an estimator. See https://arxiv.org/abs/2303.07417 for details.
Assemble and run the excitation-gate-based FAST-VQE calculator
# Build the user-configured VQE instance with the excitation_gate_estimator
# and gate selector that uses the excitation_gate_sampler
excitation_gate_fast_vqe_calculator = (
qc.calculator_creator() # Start creating a VQE instance
.vqe() # Narrow to Variational quantum eigensolver (VQE)
.iterative() # Narrow to the iterative VQE
.standard() # Narrow to the standard FAST-VQE
.with_options( # Configure to use the user-defined iterative VQE options
options=qc.options.IterativeVqeOptions(max_iterations=10)
)
.choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
.quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
.with_estimator(excitation_gate_estimator) # Configure to use the excitation gate estimator
.with_estimator_shots(100_000) # Configure to use 10,000 shots in the estimator
.with_gate_selector(excitation_gate_gate_selector) # Configure to use the gate selector
.create() # Create the calculator instance
)
excitation_gate_result = excitation_gate_fast_vqe_calculator.calculate(ground_state_problem)
We combine the estimator, and gate selector into a user-configured iterative VQE calculator.
It uses up to 10 iterations, a “quick default” greedy minimizer, and 100000 estimator
shots per energy evaluation.
Calling calculate(ground_state_problem) performs the ground state energy calculation.
Estimate total shots and cost
print("Total number of shots used in estimator", excitation_gate_estimator.total_shots) # noqa: T201
estimated_estimator_cost = excitation_gate_estimator.total_braket_price(device=Devices.Rigetti.Ankaa3)
print("Estimated cost in dollars for Estimator on Devices.Rigetti.Ankaa3: ", estimated_estimator_cost) # noqa: T201
print("Total number of shots used in sampler", excitation_gate_sampler.total_shots) # noqa: T201
estimated_sampler_cost = excitation_gate_sampler.total_braket_price(device=Devices.Rigetti.Ankaa3)
print("Estimated cost in dollars for Sampler on Devices.Rigetti.Ankaa3: ", estimated_sampler_cost) # noqa: T201
Both estimator and sampler expose their total shot counts and we can estimate cloud-hardware pricing
via .total_braket_price(device=...).
This provides a convenient cost estimate for experiments on,
for example, Rigetti Ankaa-3.
Create a local Braket backend estimator
# An Amazon Local Braket backend estimator
local_braket_estimator = (
qc.estimator_creator()
.backend() # Narrow the estimator type to a backend estimator
.choose_backend() # Start sub-choice: Choose backend
.local_amazon_braket() # Perform the sub-choice selection - Here we pick the local Braket state vector simulator backend
.create(with_shot_counter=True) # Create the estimator instance
)
# An Amazon Local Braket backend sampler
local_braket_sampler = (
qc.sampler_creator()
.backend() # Narrow the sampler type to a backend sampler
.choose_backend() # Start sub-choice: Choose backend
.local_amazon_braket() # Perform the sub-choice selection - Here we pick the local Braket simulator backend
.create(with_shot_counter=True) # Create the estimator instance
)
Here we construct an estimator and sampler that uses a local Braket simulator backend.
The local indicate that the calculation run locally on the user’s machine.
We enable shot counting for later cost analysis.
Build and run a FAST-VQE with the local Braket backends
# A FAST gate selector using the local braket sampler.
local_braket_gate_selector = (
qc.gate_selector_creator()
.fast() # Narrow the gate selector type to a FAST gate selector
.with_shots(10_000) # Configure to use 10,000 shots when estimating sampling
.with_sampler(local_braket_sampler) # Configure to use the local braket sampler
.create() # Create the gate selector instance
)
# Create the calculator instance using the local_braket_estimator
# and gate selector that uses the local_braket_sampler
local_braket_fast_vqe_calculator = (
qc.calculator_creator() # Start creating a calculator instance
.vqe() # Narrow: pick Variational quantum eigensolver (VQE)
.iterative() # Narrow to the iterative VQE
.standard() # Narrow to the standard FAST-VQE
.with_options( # Configure to use the user-defined iterative VQE options
options=qc.options.IterativeVqeOptions(max_iterations=10)
)
.choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
.quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
.with_estimator(local_braket_estimator) # Configure to use the local braket estimator
.with_estimator_shots(100_000) # Configure to use 10,000 shots in the estimator
.with_gate_selector(local_braket_gate_selector) # Configure to use the gate selector
.create() # Create the calculator instance
)
local_braket_result = local_braket_fast_vqe_calculator.calculate(ground_state_problem)
Similar to the excitation-gate-based VQE, we build a gate selector that uses the local Braket sampler. Then we then assemble an iterative VQE, configured to use the local Braket estimator, and create the FAST-VQE calculator.
Finally we run FAST-VQE calculator again, this time with the local Braket estimator and sampler.
Evaluate cost for the local Braket run
print("Total number of shots used in estimator", local_braket_estimator.total_shots) # noqa: T201
estimated_estimator_cost = local_braket_estimator.total_braket_price(device=Devices.Rigetti.Ankaa3)
print("Estimated cost in dollars for Estimator on Devices.Rigetti.Ankaa3: ", estimated_estimator_cost) # noqa: T201
print("Total number of shots used in sampler", local_braket_sampler.total_shots) # noqa: T201
estimated_sampler_cost = local_braket_sampler.total_braket_price(device=Devices.Rigetti.Ankaa3)
print("Estimated cost in dollars for Sampler on Devices.Rigetti.Ankaa3: ", estimated_sampler_cost) # noqa: T201
As before, we print shot statistics and estimated dollar costs. The cost estimates should be close to the cost estimated using the excitation-gate estimator and sampler. Any difference arise from the error that arise from the limited shot counts.
Create a noisy sampler
# Create a real Ankaa-3 Quantum Processor hardware backend.
backend = qc.backend_creator().amazon_braket(device=Devices.Rigetti.Ankaa3).create()
# Get device data from the backend - from the Ankaa-3 Quantum Processor
ankaa3_device_data = backend.get_device_data()
# An Amazon Local Braket backend simulator sampler
# that emulate the noise from the Ankaa-3 Quantum Processor
# using the density matrix simulator.
noisy_sampler = (
qc.sampler_creator()
.backend() # Narrow the estimator type to a backend estimator
.choose_backend() # Start sub-choice: Choose backend
.local_amazon_braket(
device_to_simulate=ankaa3_device_data
) # Perform the sub-choice selection - Here the local braket emulating Ankaa-3
.choose_sampler_error_mitigator() # Start sub-choice: Choose sampler error mitigator
.hamming_weight_post_selection() # Configure the error mitigator to use hamming weight post selection.
.create() # Create the estimator instance
)
We next create a noisy sampler that uses the Rigetti Aspen-M-3 device as a noise model. You can think of this as a simulator that emulate the noise characteristics of the real device, using a density matrix simulator.
The cost for running sampling on real hardware is typically much smaller than running the estimator on real hardware, as shown by the cost estimates.
To create the noisy sampler, we first create a backend object, and the query the backend object for the device data, that is used to emulate the behaviour of that device.
Note this time we choose the hamming_weight_post_selection
as the sampler error mitigator for the sampler to use the.
This error mitigator removes all states with an incorrect Hamming weight, i.e., an incorrect number of 1’s in the bitstrings, which means an incorrect number of alpha and beta electrons in the occupation number vector (or Slater determinant).
This is required as the noisy sampler will produce bitstrings that do not conserve the number of alpha and beta electrons, due to noise.
Build and run a FAST-VQE with the noisy sampler
# A FAST gate selector using the noisy sampler that emulate an Ankaa-3 Quantum Processor.
noisy_gate_selector = (
qc.gate_selector_creator()
.fast() # Narrow the gate selector type to a FAST gate selector
.with_shots(10_000) # Configure to use 10,000 shots when estimating sampling
.with_sampler(noisy_sampler) # Configure to use the local braket sampler
.create() # Create the gate selector instance
)
# Build the user-configured VQE calculator instance with the excitation gate estimator
# and gate selector that uses the noisy sampler that emulate an Ankaa-3 Quantum Processor.
noisy_fast_vqe_calculator = (
qc.calculator_creator() # Start creating a calculator instance
.vqe() # Narrow: pick Variational quantum eigensolver (VQE)
.iterative() # Narrow to the iterative VQE
.standard() # Narrow to the standard FAST-VQE
.with_options( # Configure to use the user-defined iterative VQE options
options=qc.options.IterativeVqeOptions(max_iterations=10)
)
.choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
.quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
.with_estimator(excitation_gate_estimator) # Configure to use the excitation gate estimator
.with_estimator_shots(100_000) # Configure to use 10,000 shots in the estimator
.with_gate_selector(noisy_gate_selector) # Configure to use the gate selector
.create() # Create the calculator instance
)
noisy_result = noisy_fast_vqe_calculator.calculate(ground_state_problem)
print("result from the local braket estimator and noisy sampler:") # noqa: T201
print(noisy_result) # noqa: T201
Similar to the earlier, we build a gate selector that uses the noisy sampler. Then we assemble an iterative VQE and create the FAST-VQE calculator. Finally we run FAST-VQE calculator again, this time with the noisy sampler and local Braket state vector estimator.
Printing the result yield a slightly higher energy, due to the noise in the sampler.
Create a real hardware sampler
Finally now that we have an estimate of the cost and we have emulated the behaviour of the real hardware, we can now create a sampler that runs on real quantum hardware.
# A real hardware sampler using the Amazon Braket backend
# dispatching to an Ankaa-3 Quantum Processor
real_hardware_braket_sampler = (
qc.sampler_creator()
.backend() # Narrow the estimator type to a backend estimator
.choose_backend() # Start sub-choice: Choose backend
.amazon_braket(
device=Devices.Rigetti.Ankaa3
) # Perform the sub-choice selection - Here we pick the Rigetti Ankaa3
.choose_sampler_error_mitigator() # Start sub-choice: Choose sampler error mitigator
.hamming_weight_post_selection() # Configure the error mitigator to use hamming weight post selection.
.create() # Create the estimator instance
)
Here we create a sampler that runs on the Rigetti Ankaa-3 device.
We choose the hamming_weight_post_selection
as the sampler error mitigator for the sampler to use the.
Choosing another device is as simple as changing the device name.
See Choose a Backend for more options on using different backends, different quantum computer providers, etc.
Build and run a FAST-VQE with the real hardware sampler
# A FAST gate selector using the Ankaa-3 Quantum Processor for the sampler.
real_hardware_braket_gate_selector = (
qc.gate_selector_creator()
.fast() # Narrow the gate selector type to a FAST gate selector
.with_shots(10_000) # Configure to use 10,000 shots when estimating sampling
.with_sampler(real_hardware_braket_sampler) # Configure to use the local braket sampler
.create() # Create the gate selector instance
)
# Build the user-configured VQE calculator instance with the excitation gate estimator
# and gate selector that uses the Ankaa-3 Quantum Processor for the sampler.
real_hardware_braket_fast_vqe_calculator = (
qc.calculator_creator() # Start creating a calculator instance
.vqe() # Narrow: pick Variational quantum eigensolver (VQE)
.iterative() # Narrow to the iterative VQE
.standard() # Narrow to the standard FAST-VQE
.with_options( # Configure to use the user-defined iterative VQE options
options=qc.options.IterativeVqeOptions(max_iterations=10)
)
.choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
.quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
.with_estimator(excitation_gate_estimator) # Configure to use the excitation gate estimator
.with_estimator_shots(100_000) # Configure to use 10,000 shots in the estimator
.with_gate_selector(real_hardware_braket_gate_selector) # Configure to use the gate selector
.create() # Create the calculator instance
)
real_hardware_braket_result = real_hardware_braket_fast_vqe_calculator.calculate(ground_state_problem)
Similar to previous we build a VQE instance, a gate selector, and the FAST-VQE calculator, that use the real hardware sampler to select the gate.
Finally we run FAST-VQE calculator.
Make the plot
fast_convergence: list[tuple[list[float], list[float], str]] = []
labels = ["Excitation Gate Simulator"]
results = [excitation_gate_result]
if do_local_braket and local_braket_result is not None:
labels.append("Local Braket Simulator")
results.append(local_braket_result)
if do_noisy_sampler and noisy_result is not None:
labels.append("Noisy Simulator")
results.append(noisy_result)
if do_real_hardware and real_hardware_braket_result is not None:
labels.append("Real Hardware")
results.append(real_hardware_braket_result)
for result, label in zip(results, labels, strict=True):
fast_energies = result.total_energy_per_macro_iteration_with_initial_energy_and_final_energy.values
energy_errors = result.total_energy_per_macro_iteration_with_initial_energy_and_final_energy.errors
fast_convergence.append((fast_energies, energy_errors, label))
plot_energies(
fast_convergence=fast_convergence,
show=True,
)
We plot the VQE convergence with the error bars by calling the plotting function defined above.
—
Running the Example
After saving the script as run_estimators_and_samplers_example.py, execute:
$ python run_estimators_and_samplers_example.py
You will see the cost estimates printed to the console:
$ python run_estimators_and_samplers_example.py
Total number of shots used in estimator 470600000
Estimated cost in dollars for Estimator on Devices.Rigetti.Ankaa3: 424951.7999999651
Total number of shots used in sampler 100000
Estimated cost in dollars for Sampler on Devices.Rigetti.Ankaa3: 92.99999999999999
The estimated cost is from 14. October 2025, so are most likely out of date.
And you will see the VQE convergence plot saved as dist/vqe_convergence.png: