amazon-braket-algorithm-library 1.7.3__tar.gz → 1.7.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {amazon_braket_algorithm_library-1.7.3/src/amazon_braket_algorithm_library.egg-info → amazon_braket_algorithm_library-1.7.4}/PKG-INFO +2 -1
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/README.md +1 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4/src/amazon_braket_algorithm_library.egg-info}/PKG-INFO +2 -1
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/amazon_braket_algorithm_library.egg-info/SOURCES.txt +2 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/_algos/_version.py +1 -1
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator.py +105 -74
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator_braket_helpers.py +25 -27
- amazon_braket_algorithm_library-1.7.4/src/braket/experimental/algorithms/quantum_counting/__init__.py +20 -0
- amazon_braket_algorithm_library-1.7.4/src/braket/experimental/algorithms/quantum_counting/quantum_counting.py +483 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/LICENSE +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/NOTICE +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/pyproject.toml +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/setup.cfg +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/setup.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/amazon_braket_algorithm_library.egg-info/dependency_links.txt +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/amazon_braket_algorithm_library.egg-info/requires.txt +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/amazon_braket_algorithm_library.egg-info/top_level.txt +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/adaptive_shot_allocation/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/bells_inequality/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/bells_inequality/bells_inequality.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/bernstein_vazirani/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/bernstein_vazirani/bernstein_vazirani.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/chsh_inequality/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/chsh_inequality/chsh_inequality.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/deutsch_jozsa/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/deutsch_jozsa/deutsch_jozsa.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/grovers_search/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/grovers_search/grovers_search.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/qc_qmc/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/qc_qmc/classical_qmc.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/qc_qmc/qc_qmc.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_approximate_optimization/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_approximate_optimization/quantum_approximate_optimization.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_circuit_born_machine/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_circuit_born_machine/qcbm.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_fourier_transform/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_fourier_transform/quantum_fourier_transform.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_phase_estimation/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_phase_estimation/quantum_phase_estimation.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_walk/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/quantum_walk/quantum_walk.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/shors/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/shors/shors.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/simons/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/algorithms/simons/simons.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/auxiliary_functions/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/auxiliary_functions/random_circuit/__init__.py +0 -0
- {amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/src/braket/experimental/auxiliary_functions/random_circuit/random_circuit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amazon-braket-algorithm-library
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.4
|
|
4
4
|
Summary: An open source library of quantum computing algorithms implemented on Amazon Braket
|
|
5
5
|
Home-page: https://github.com/amazon-braket/amazon-braket-algorithm-library
|
|
6
6
|
Author: Amazon Web Services
|
|
@@ -70,6 +70,7 @@ Running notebooks locally requires additional dependencies located in [notebooks
|
|
|
70
70
|
| Quantum Circuit Born Machine | [Quantum_Circuit_Born_Machine.ipynb](notebooks/textbook/Quantum_Circuit_Born_Machine.ipynb) | [Benedetti2019](https://www.nature.com/articles/s41534-019-0157-8), [Liu2018](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.98.062324) |
|
|
71
71
|
| QFT | [Quantum_Fourier_Transform.ipynb](notebooks/textbook/Quantum_Fourier_Transform.ipynb) | [Coppersmith2002](https://arxiv.org/abs/quant-ph/0201067) |
|
|
72
72
|
| QPE | [Quantum_Phase_Estimation_Algorithm.ipynb](notebooks/textbook/Quantum_Phase_Estimation_Algorithm.ipynb) | [Kitaev1995](https://arxiv.org/abs/quant-ph/9511026) |
|
|
73
|
+
| Quantum Counting | [Quantum_Counting_Algorithm.ipynb](notebooks/textbook/Quantum_Counting_Algorithm.ipynb) | [Brassard1998](https://arxiv.org/abs/quant-ph/9805082) |
|
|
73
74
|
| Quantum Walk | [Quantum_Walk.ipynb](notebooks/textbook/Quantum_Walk.ipynb) | [Childs2002](https://arxiv.org/abs/quant-ph/0209131) |
|
|
74
75
|
|Shor's| [Shors_Algorithm.ipynb](notebooks/textbook/Shors_Algorithm.ipynb) | [Shor1998](https://arxiv.org/abs/quant-ph/9508027) |
|
|
75
76
|
| Simon's | [Simons_Algorithm.ipynb](notebooks/textbook/Simons_Algorithm.ipynb) | [Simon1997](https://epubs.siam.org/doi/10.1137/S0097539796298637) |
|
|
@@ -22,6 +22,7 @@ Running notebooks locally requires additional dependencies located in [notebooks
|
|
|
22
22
|
| Quantum Circuit Born Machine | [Quantum_Circuit_Born_Machine.ipynb](notebooks/textbook/Quantum_Circuit_Born_Machine.ipynb) | [Benedetti2019](https://www.nature.com/articles/s41534-019-0157-8), [Liu2018](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.98.062324) |
|
|
23
23
|
| QFT | [Quantum_Fourier_Transform.ipynb](notebooks/textbook/Quantum_Fourier_Transform.ipynb) | [Coppersmith2002](https://arxiv.org/abs/quant-ph/0201067) |
|
|
24
24
|
| QPE | [Quantum_Phase_Estimation_Algorithm.ipynb](notebooks/textbook/Quantum_Phase_Estimation_Algorithm.ipynb) | [Kitaev1995](https://arxiv.org/abs/quant-ph/9511026) |
|
|
25
|
+
| Quantum Counting | [Quantum_Counting_Algorithm.ipynb](notebooks/textbook/Quantum_Counting_Algorithm.ipynb) | [Brassard1998](https://arxiv.org/abs/quant-ph/9805082) |
|
|
25
26
|
| Quantum Walk | [Quantum_Walk.ipynb](notebooks/textbook/Quantum_Walk.ipynb) | [Childs2002](https://arxiv.org/abs/quant-ph/0209131) |
|
|
26
27
|
|Shor's| [Shors_Algorithm.ipynb](notebooks/textbook/Shors_Algorithm.ipynb) | [Shor1998](https://arxiv.org/abs/quant-ph/9508027) |
|
|
27
28
|
| Simon's | [Simons_Algorithm.ipynb](notebooks/textbook/Simons_Algorithm.ipynb) | [Simon1997](https://epubs.siam.org/doi/10.1137/S0097539796298637) |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amazon-braket-algorithm-library
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.4
|
|
4
4
|
Summary: An open source library of quantum computing algorithms implemented on Amazon Braket
|
|
5
5
|
Home-page: https://github.com/amazon-braket/amazon-braket-algorithm-library
|
|
6
6
|
Author: Amazon Web Services
|
|
@@ -70,6 +70,7 @@ Running notebooks locally requires additional dependencies located in [notebooks
|
|
|
70
70
|
| Quantum Circuit Born Machine | [Quantum_Circuit_Born_Machine.ipynb](notebooks/textbook/Quantum_Circuit_Born_Machine.ipynb) | [Benedetti2019](https://www.nature.com/articles/s41534-019-0157-8), [Liu2018](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.98.062324) |
|
|
71
71
|
| QFT | [Quantum_Fourier_Transform.ipynb](notebooks/textbook/Quantum_Fourier_Transform.ipynb) | [Coppersmith2002](https://arxiv.org/abs/quant-ph/0201067) |
|
|
72
72
|
| QPE | [Quantum_Phase_Estimation_Algorithm.ipynb](notebooks/textbook/Quantum_Phase_Estimation_Algorithm.ipynb) | [Kitaev1995](https://arxiv.org/abs/quant-ph/9511026) |
|
|
73
|
+
| Quantum Counting | [Quantum_Counting_Algorithm.ipynb](notebooks/textbook/Quantum_Counting_Algorithm.ipynb) | [Brassard1998](https://arxiv.org/abs/quant-ph/9805082) |
|
|
73
74
|
| Quantum Walk | [Quantum_Walk.ipynb](notebooks/textbook/Quantum_Walk.ipynb) | [Childs2002](https://arxiv.org/abs/quant-ph/0209131) |
|
|
74
75
|
|Shor's| [Shors_Algorithm.ipynb](notebooks/textbook/Shors_Algorithm.ipynb) | [Shor1998](https://arxiv.org/abs/quant-ph/9508027) |
|
|
75
76
|
| Simon's | [Simons_Algorithm.ipynb](notebooks/textbook/Simons_Algorithm.ipynb) | [Simon1997](https://epubs.siam.org/doi/10.1137/S0097539796298637) |
|
|
@@ -32,6 +32,8 @@ src/braket/experimental/algorithms/quantum_approximate_optimization/__init__.py
|
|
|
32
32
|
src/braket/experimental/algorithms/quantum_approximate_optimization/quantum_approximate_optimization.py
|
|
33
33
|
src/braket/experimental/algorithms/quantum_circuit_born_machine/__init__.py
|
|
34
34
|
src/braket/experimental/algorithms/quantum_circuit_born_machine/qcbm.py
|
|
35
|
+
src/braket/experimental/algorithms/quantum_counting/__init__.py
|
|
36
|
+
src/braket/experimental/algorithms/quantum_counting/quantum_counting.py
|
|
35
37
|
src/braket/experimental/algorithms/quantum_fourier_transform/__init__.py
|
|
36
38
|
src/braket/experimental/algorithms/quantum_fourier_transform/quantum_fourier_transform.py
|
|
37
39
|
src/braket/experimental/algorithms/quantum_phase_estimation/__init__.py
|
|
@@ -33,7 +33,7 @@ def commute(a: str, b: str, qwc: bool = True) -> bool:
|
|
|
33
33
|
Raises:
|
|
34
34
|
ValueError: If the Pauli strigns are of different length.
|
|
35
35
|
"""
|
|
36
|
-
if len(a)!=len(b):
|
|
36
|
+
if len(a) != len(b):
|
|
37
37
|
raise ValueError("Pauli strings must be of the same length.")
|
|
38
38
|
count = 0
|
|
39
39
|
for i in zip(a, b):
|
|
@@ -42,15 +42,17 @@ def commute(a: str, b: str, qwc: bool = True) -> bool:
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
# Partial functions for specific commutation checks
|
|
45
|
-
qwc_commute = partial(commute, qwc=True)
|
|
46
|
-
gen_commute = partial(commute, qwc=False)
|
|
45
|
+
qwc_commute = partial(commute, qwc=True) # Qubit-wise commutation
|
|
46
|
+
gen_commute = partial(commute, qwc=False) # General commutation
|
|
47
47
|
|
|
48
48
|
# BAYESIAN STATISTICS (closed formulas from Appendix B of arXiv:2110.15339v6)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def term_variance_estimate(
|
|
51
|
+
def term_variance_estimate(
|
|
52
|
+
term_idx: int, measurements: Union[MeasurementData, None] = None
|
|
53
|
+
) -> float:
|
|
52
54
|
"""
|
|
53
|
-
Estimate variance for a single Pauli term.
|
|
55
|
+
Estimate variance for a single Pauli term.
|
|
54
56
|
See Eq 14 in Appendix B of "Adaptive Estimation of Quantum Observables (arXiv:2110.15339v6)
|
|
55
57
|
|
|
56
58
|
Args:
|
|
@@ -64,10 +66,12 @@ def term_variance_estimate(term_idx: int, measurements: Union[MeasurementData, N
|
|
|
64
66
|
if measurements:
|
|
65
67
|
x0 = measurements[term_idx][term_idx][(1, 1)]
|
|
66
68
|
x1 = measurements[term_idx][term_idx][(-1, -1)]
|
|
67
|
-
return 4*((x0+1)*(x1+1))/((x0+x1+2)*(x0+x1+3))
|
|
69
|
+
return 4 * ((x0 + 1) * (x1 + 1)) / ((x0 + x1 + 2) * (x0 + x1 + 3))
|
|
68
70
|
|
|
69
71
|
|
|
70
|
-
def terms_covariance_estimate(
|
|
72
|
+
def terms_covariance_estimate(
|
|
73
|
+
i: int, j: int, measurements: Union[MeasurementData, None] = None
|
|
74
|
+
) -> float:
|
|
71
75
|
"""
|
|
72
76
|
Estimate covariance between two Pauli terms.
|
|
73
77
|
See Eq 25-6 in Appendix B of "Adaptive Estimation of Quantum Observables (arXiv:2110.15339v6)
|
|
@@ -93,14 +97,17 @@ def terms_covariance_estimate(i: int, j: int, measurements: Union[MeasurementDat
|
|
|
93
97
|
xy11 = measurements[i][j][(-1, -1)]
|
|
94
98
|
|
|
95
99
|
# Calculate prior probabilities
|
|
96
|
-
p00 = 4*((x0+1)*(y0+1))/((x0+x1+2)*(y0+y1+2))
|
|
97
|
-
p01 = 4*((x0+1)*(y1+1))/((x0+x1+2)*(y0+y1+2))
|
|
98
|
-
p10 = 4*((x1+1)*(y0+1))/((x0+x1+2)*(y0+y1+2))
|
|
99
|
-
p11 = 4*((x1+1)*(y1+1))/((x0+x1+2)*(y0+y1+2))
|
|
100
|
+
p00 = 4 * ((x0 + 1) * (y0 + 1)) / ((x0 + x1 + 2) * (y0 + y1 + 2))
|
|
101
|
+
p01 = 4 * ((x0 + 1) * (y1 + 1)) / ((x0 + x1 + 2) * (y0 + y1 + 2))
|
|
102
|
+
p10 = 4 * ((x1 + 1) * (y0 + 1)) / ((x0 + x1 + 2) * (y0 + y1 + 2))
|
|
103
|
+
p11 = 4 * ((x1 + 1) * (y1 + 1)) / ((x0 + x1 + 2) * (y0 + y1 + 2))
|
|
100
104
|
|
|
101
105
|
# Return Bayesian covariance estimate
|
|
102
|
-
return
|
|
103
|
-
|
|
106
|
+
return (
|
|
107
|
+
4
|
|
108
|
+
* ((xy00 + p00) * (xy11 + p11) - (xy01 + p01) * (xy10 + p10))
|
|
109
|
+
/ ((xy00 + xy01 + xy10 + xy11 + 4) * (xy00 + xy01 + xy10 + xy11 + 5))
|
|
110
|
+
)
|
|
104
111
|
|
|
105
112
|
|
|
106
113
|
class AdaptiveShotAllocator:
|
|
@@ -156,11 +163,10 @@ class AdaptiveShotAllocator:
|
|
|
156
163
|
raise ValueError("Number of Paulis must match coefficients")
|
|
157
164
|
|
|
158
165
|
# Validate Pauli strings
|
|
159
|
-
valid_chars = set(
|
|
166
|
+
valid_chars = set("IXYZ")
|
|
160
167
|
for pauli in paulis:
|
|
161
168
|
if not set(pauli).issubset(valid_chars):
|
|
162
|
-
raise ValueError(
|
|
163
|
-
f"Invalid Pauli string: {pauli}. Must only contain I, X, Y, or Z")
|
|
169
|
+
raise ValueError(f"Invalid Pauli string: {pauli}. Must only contain I, X, Y, or Z")
|
|
164
170
|
self.paulis = paulis
|
|
165
171
|
self.coeffs = coeffs
|
|
166
172
|
self.graph = self._generate_graph()
|
|
@@ -179,9 +185,10 @@ class AdaptiveShotAllocator:
|
|
|
179
185
|
- Updates graph weights to initial values
|
|
180
186
|
"""
|
|
181
187
|
# Initialize measurement counts for all term pairs
|
|
182
|
-
self.measurements = [
|
|
183
|
-
|
|
184
|
-
|
|
188
|
+
self.measurements = [
|
|
189
|
+
[{(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0} for _ in range(self.num_terms)]
|
|
190
|
+
for _ in range(self.num_terms)
|
|
191
|
+
]
|
|
185
192
|
self.shots = None # Clear shot allocation history
|
|
186
193
|
self._update_graph_weights() # Reset graph weights
|
|
187
194
|
|
|
@@ -201,7 +208,9 @@ class AdaptiveShotAllocator:
|
|
|
201
208
|
Edges are added between terms that commute according to the provided function.
|
|
202
209
|
"""
|
|
203
210
|
if commute != qwc_commute:
|
|
204
|
-
warnings.warn(
|
|
211
|
+
warnings.warn(
|
|
212
|
+
"Braket only supports simultaneous measurement of qubit-wise commuting operators."
|
|
213
|
+
)
|
|
205
214
|
|
|
206
215
|
# Create graph and add nodes with Pauli string labels
|
|
207
216
|
self.graph = nx.Graph()
|
|
@@ -229,8 +238,9 @@ class AdaptiveShotAllocator:
|
|
|
229
238
|
_, cliq = approximation.clique_removal(self.graph)
|
|
230
239
|
self.cliq = [sorted(i) for i in cliq] # Sort cliques for consistency
|
|
231
240
|
|
|
232
|
-
def visualize_graph(
|
|
233
|
-
|
|
241
|
+
def visualize_graph(
|
|
242
|
+
self, node_size: int = 1230, font_size: int = 10, show_cliques: bool = True
|
|
243
|
+
) -> None:
|
|
234
244
|
"""
|
|
235
245
|
Visualize the graph with colored edges based on clique membership.
|
|
236
246
|
|
|
@@ -242,15 +252,17 @@ class AdaptiveShotAllocator:
|
|
|
242
252
|
"""
|
|
243
253
|
|
|
244
254
|
# Generate random colors for each clique
|
|
245
|
-
cliq_colors = [
|
|
246
|
-
|
|
255
|
+
cliq_colors = [
|
|
256
|
+
"#" + "".join([hex(random.randint(0, 255))[2:].zfill(2) for _ in range(3)])
|
|
257
|
+
for _ in self.cliq
|
|
258
|
+
]
|
|
247
259
|
|
|
248
260
|
# Build edge list and colors
|
|
249
261
|
if show_cliques:
|
|
250
262
|
el = []
|
|
251
263
|
ec = []
|
|
252
264
|
for e in self.graph.edges:
|
|
253
|
-
for i,c in enumerate(self.cliq):
|
|
265
|
+
for i, c in enumerate(self.cliq):
|
|
254
266
|
if (e[0] in c) and (e[1] in c):
|
|
255
267
|
el.append(e)
|
|
256
268
|
ec.append(cliq_colors[i])
|
|
@@ -258,23 +270,32 @@ class AdaptiveShotAllocator:
|
|
|
258
270
|
else:
|
|
259
271
|
el = list(self.graph.edges)
|
|
260
272
|
# Use gray for all edges when showing full graph
|
|
261
|
-
ec = [
|
|
273
|
+
ec = ["gray" for _ in el]
|
|
262
274
|
|
|
263
275
|
# Create layout
|
|
264
276
|
pos = nx.circular_layout(self.graph)
|
|
265
277
|
|
|
266
278
|
# Draw the graph
|
|
267
279
|
plt.figure(figsize=(10, 10))
|
|
268
|
-
nx.draw(
|
|
269
|
-
|
|
270
|
-
|
|
280
|
+
nx.draw(
|
|
281
|
+
self.graph,
|
|
282
|
+
pos=pos,
|
|
283
|
+
with_labels=False,
|
|
284
|
+
node_color="white",
|
|
285
|
+
node_size=node_size,
|
|
286
|
+
edgelist=el,
|
|
287
|
+
edge_color=ec,
|
|
288
|
+
)
|
|
271
289
|
|
|
272
290
|
# Add labels
|
|
273
|
-
nx.draw_networkx_labels(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
291
|
+
nx.draw_networkx_labels(
|
|
292
|
+
self.graph,
|
|
293
|
+
pos,
|
|
294
|
+
labels=nx.get_node_attributes(self.graph, "label"),
|
|
295
|
+
font_size=font_size,
|
|
296
|
+
font_color="black",
|
|
297
|
+
font_family="Times",
|
|
298
|
+
)
|
|
278
299
|
|
|
279
300
|
# Set edge colors
|
|
280
301
|
ax = plt.gca()
|
|
@@ -299,9 +320,12 @@ class AdaptiveShotAllocator:
|
|
|
299
320
|
# Base weight from coefficients
|
|
300
321
|
weight = self.coeffs[i] * self.coeffs[j]
|
|
301
322
|
# Multiply by variance/covariance estimate
|
|
302
|
-
weight *= (
|
|
303
|
-
|
|
304
|
-
|
|
323
|
+
weight *= (
|
|
324
|
+
term_variance_estimate(i, measurements)
|
|
325
|
+
if i == j
|
|
326
|
+
else terms_covariance_estimate(i, j, measurements)
|
|
327
|
+
)
|
|
328
|
+
self.graph[i][j]["weight"] = weight
|
|
305
329
|
|
|
306
330
|
def incremental_shot_allocation(self, num_shots: int) -> List[int]:
|
|
307
331
|
"""
|
|
@@ -337,13 +361,14 @@ class AdaptiveShotAllocator:
|
|
|
337
361
|
current_shots = self.shots if self.shots else [0 for c in self.cliq]
|
|
338
362
|
|
|
339
363
|
# Calculate weighted covariance matrix for error estimates
|
|
340
|
-
weighted_covariance = nx.adjacency_matrix(
|
|
341
|
-
self.graph, weight="weight").toarray()
|
|
364
|
+
weighted_covariance = nx.adjacency_matrix(self.graph, weight="weight").toarray()
|
|
342
365
|
|
|
343
366
|
# Initialize error estimates for each clique using current shots
|
|
344
367
|
# Error = sqrt(sum of covariances) / number of shots
|
|
345
|
-
clique_error = [
|
|
346
|
-
|
|
368
|
+
clique_error = [
|
|
369
|
+
np.sqrt(weighted_covariance[c, c].sum()) / (current_shots[e] or not current_shots[e])
|
|
370
|
+
for e, c in enumerate(self.cliq)
|
|
371
|
+
]
|
|
347
372
|
|
|
348
373
|
# Allocate shots one at a time
|
|
349
374
|
for _ in range(num_shots):
|
|
@@ -356,8 +381,7 @@ class AdaptiveShotAllocator:
|
|
|
356
381
|
# Update error estimate for the chosen clique
|
|
357
382
|
# New error = Old error * (n/(n+1)) where n+1 is the updated shot count.
|
|
358
383
|
total_shots = current_shots[cliq_id] + proposed_allocation[cliq_id]
|
|
359
|
-
clique_error[cliq_id] *= ((total_shots-1)
|
|
360
|
-
or not (total_shots-1))/(total_shots)
|
|
384
|
+
clique_error[cliq_id] *= ((total_shots - 1) or not (total_shots - 1)) / (total_shots)
|
|
361
385
|
|
|
362
386
|
return proposed_allocation
|
|
363
387
|
|
|
@@ -379,18 +403,23 @@ class AdaptiveShotAllocator:
|
|
|
379
403
|
ValueError: If graph weights have not been properly initialized
|
|
380
404
|
"""
|
|
381
405
|
# Get weighted covariance matrix
|
|
382
|
-
weighted_covariance = nx.adjacency_matrix(
|
|
383
|
-
self.graph, weight="weight").toarray()
|
|
406
|
+
weighted_covariance = nx.adjacency_matrix(self.graph, weight="weight").toarray()
|
|
384
407
|
|
|
385
408
|
# Sum variance contributions from each clique
|
|
386
|
-
variance_estimate = sum(
|
|
387
|
-
|
|
409
|
+
variance_estimate = sum(
|
|
410
|
+
[
|
|
411
|
+
(weighted_covariance[c, c].sum()) / (self.shots[e] or not self.shots[e])
|
|
412
|
+
for e, c in enumerate(self.cliq)
|
|
413
|
+
]
|
|
414
|
+
)
|
|
388
415
|
|
|
389
416
|
return np.sqrt(variance_estimate)
|
|
390
417
|
|
|
391
|
-
def expectation_from_measurements(
|
|
418
|
+
def expectation_from_measurements(
|
|
419
|
+
self, measurements: Union[MeasurementData, None] = None
|
|
420
|
+
) -> float:
|
|
392
421
|
"""
|
|
393
|
-
Calculate the energy expectation value from measurement results
|
|
422
|
+
Calculate the energy expectation value from measurement results
|
|
394
423
|
for the different Pauli string observables.
|
|
395
424
|
|
|
396
425
|
For each Pauli term, computes <P> = (N++ - N--)/N_total where:
|
|
@@ -414,17 +443,15 @@ class AdaptiveShotAllocator:
|
|
|
414
443
|
expectation = 0.0
|
|
415
444
|
for i in range(len(self.coeffs)):
|
|
416
445
|
# Verify no invalid measurements
|
|
417
|
-
assert measurements[i][i][(
|
|
418
|
-
|
|
419
|
-
assert measurements[i][i][(-1, 1)
|
|
420
|
-
] == 0, "Invalid measurement detected: (-1,+1)"
|
|
446
|
+
assert measurements[i][i][(1, -1)] == 0, "Invalid measurement detected: (+1,-1)"
|
|
447
|
+
assert measurements[i][i][(-1, 1)] == 0, "Invalid measurement detected: (-1,+1)"
|
|
421
448
|
|
|
422
449
|
# Calculate expectation for this term
|
|
423
|
-
term_shots = measurements[i][i][(
|
|
424
|
-
1, 1)] + measurements[i][i][(-1, -1)]
|
|
450
|
+
term_shots = measurements[i][i][(1, 1)] + measurements[i][i][(-1, -1)]
|
|
425
451
|
if term_shots:
|
|
426
452
|
term_expect = (
|
|
427
|
-
measurements[i][i][(1, 1)] - measurements[i][i][(-1, -1)]
|
|
453
|
+
measurements[i][i][(1, 1)] - measurements[i][i][(-1, -1)]
|
|
454
|
+
) / term_shots
|
|
428
455
|
expectation += self.coeffs[i] * term_expect
|
|
429
456
|
|
|
430
457
|
return expectation
|
|
@@ -447,13 +474,11 @@ class AdaptiveShotAllocator:
|
|
|
447
474
|
Raises:
|
|
448
475
|
AssertionError: If any validation check fails
|
|
449
476
|
"""
|
|
450
|
-
assert len(
|
|
451
|
-
measurements) == self.num_terms, "Wrong number of measurement records"
|
|
477
|
+
assert len(measurements) == self.num_terms, "Wrong number of measurement records"
|
|
452
478
|
|
|
453
479
|
for c in self.cliq:
|
|
454
480
|
# Get total shots for this clique
|
|
455
|
-
m_cliq =
|
|
456
|
-
measurements[c[0]][c[0]][(-1, -1)])
|
|
481
|
+
m_cliq = measurements[c[0]][c[0]][(1, 1)] + measurements[c[0]][c[0]][(-1, -1)]
|
|
457
482
|
|
|
458
483
|
# Check consistency within clique
|
|
459
484
|
for i in c:
|
|
@@ -463,25 +488,31 @@ class AdaptiveShotAllocator:
|
|
|
463
488
|
assert v >= 0, "Measurement counts should not be negative"
|
|
464
489
|
|
|
465
490
|
# Measurements should be symmetric
|
|
466
|
-
assert measurements[i][j][(1,1)]==measurements[j][i][(1,1)]
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
assert measurements[i][j][(
|
|
473
|
-
|
|
491
|
+
assert measurements[i][j][(1, 1)] == measurements[j][i][(1, 1)], (
|
|
492
|
+
"Measurement should be symmetric"
|
|
493
|
+
)
|
|
494
|
+
assert measurements[i][j][(-1, -1)] == measurements[j][i][(-1, -1)], (
|
|
495
|
+
"Measurement should be symmetric"
|
|
496
|
+
)
|
|
497
|
+
assert measurements[i][j][(1, -1)] == measurements[j][i][(-1, 1)], (
|
|
498
|
+
"Measurement should be symmetric"
|
|
499
|
+
)
|
|
500
|
+
assert measurements[i][j][(-1, 1)] == measurements[j][i][(1, -1)], (
|
|
501
|
+
"Measurement should be symmetric"
|
|
502
|
+
)
|
|
474
503
|
|
|
475
504
|
# All pairs in clique should have same total measurements
|
|
476
|
-
assert m_cliq == sum(measurements[i][j].values()),
|
|
477
|
-
|
|
478
|
-
|
|
505
|
+
assert m_cliq == sum(measurements[i][j].values()), (
|
|
506
|
+
f"The number of times {i} and {j} were measured together should be "
|
|
507
|
+
"equal to the number of measurements of their clique."
|
|
508
|
+
)
|
|
479
509
|
|
|
480
510
|
# Diagonal elements should not have invalid combinations
|
|
481
511
|
if i == j:
|
|
482
|
-
assert measurements[i][i][(1, -1)] == measurements[i][i][(-1, 1)] == 0,
|
|
483
|
-
|
|
484
|
-
"(1,1) or (-1,-1) counts."
|
|
512
|
+
assert measurements[i][i][(1, -1)] == measurements[i][i][(-1, 1)] == 0, (
|
|
513
|
+
"A measurement of a single term can only contribute to the "
|
|
514
|
+
"(1,1) or (-1,-1) counts."
|
|
515
|
+
)
|
|
485
516
|
return True
|
|
486
517
|
|
|
487
518
|
def shots_from_measurements(self, measurements: MeasurementData) -> List[int]:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Helper functions to handle quantum measurements and adaptive shot allocation
|
|
2
|
+
Helper functions to handle quantum measurements and adaptive shot allocation
|
|
3
3
|
experiments on Amazon Braket.
|
|
4
4
|
"""
|
|
5
5
|
|
|
@@ -13,6 +13,7 @@ from braket.experimental.algorithms.adaptive_shot_allocation.adaptive_allocator
|
|
|
13
13
|
MeasurementData,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
def observable_from_string(pauli_string: str) -> Observable:
|
|
17
18
|
"""
|
|
18
19
|
Convert Pauli string to Braket observable.
|
|
@@ -23,15 +24,15 @@ def observable_from_string(pauli_string: str) -> Observable:
|
|
|
23
24
|
Returns:
|
|
24
25
|
Observable: Corresponding Braket observable
|
|
25
26
|
"""
|
|
26
|
-
gates = {"I": Observable.I, "X": Observable.X,
|
|
27
|
-
"Y": Observable.Y, "Z": Observable.Z}
|
|
27
|
+
gates = {"I": Observable.I, "X": Observable.X, "Y": Observable.Y, "Z": Observable.Z}
|
|
28
28
|
return Observable.TensorProduct([gates[i[1]](i[0]) for i in enumerate(pauli_string)])
|
|
29
29
|
|
|
30
|
+
|
|
30
31
|
def run_fixed_allocation(
|
|
31
32
|
device: Union[LocalSimulator, AwsDevice],
|
|
32
33
|
circuit: Circuit,
|
|
33
34
|
estimator: AdaptiveShotAllocator,
|
|
34
|
-
shot_allocation: List[int]
|
|
35
|
+
shot_allocation: List[int],
|
|
35
36
|
) -> MeasurementData:
|
|
36
37
|
"""
|
|
37
38
|
Run experiment with a specific shot allocation.
|
|
@@ -45,7 +46,7 @@ def run_fixed_allocation(
|
|
|
45
46
|
Returns:
|
|
46
47
|
MeasurementData: Measurement outcomes for each term pair
|
|
47
48
|
"""
|
|
48
|
-
|
|
49
|
+
|
|
49
50
|
# Step 1. Submit all tasks.
|
|
50
51
|
tasks = {}
|
|
51
52
|
for c_idx, c in enumerate(estimator.cliq):
|
|
@@ -54,41 +55,40 @@ def run_fixed_allocation(
|
|
|
54
55
|
|
|
55
56
|
measurement_circ = circuit.copy()
|
|
56
57
|
for p in c:
|
|
57
|
-
measurement_circ.sample(
|
|
58
|
-
|
|
58
|
+
measurement_circ.sample(observable_from_string(estimator.paulis[p]))
|
|
59
|
+
|
|
60
|
+
tasks[c_idx] = device.run(measurement_circ, shots=shot_allocation[c_idx])
|
|
59
61
|
|
|
60
|
-
tasks[c_idx] = device.run(
|
|
61
|
-
measurement_circ, shots=shot_allocation[c_idx])
|
|
62
|
-
|
|
63
62
|
# Step 2. Post-process results.
|
|
64
|
-
measurements = [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
measurements = [
|
|
64
|
+
[{(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0} for _ in range(len(estimator.paulis))]
|
|
65
|
+
for _ in range(len(estimator.paulis))
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
while tasks:
|
|
69
69
|
task_to_process = None
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
for c_idx in tasks:
|
|
72
72
|
# Check task status
|
|
73
73
|
state = tasks[c_idx].state()
|
|
74
|
-
assert state in ["CREATED", "QUEUED", "RUNNING", "COMPLETED"],
|
|
74
|
+
assert state in ["CREATED", "QUEUED", "RUNNING", "COMPLETED"], (
|
|
75
75
|
f"Encountered quantum task failure (status: {state})."
|
|
76
|
+
)
|
|
76
77
|
|
|
77
78
|
if state == "COMPLETED":
|
|
78
79
|
task_to_process = c_idx
|
|
79
80
|
break
|
|
80
|
-
|
|
81
|
+
|
|
81
82
|
if task_to_process is None:
|
|
82
83
|
continue
|
|
83
|
-
|
|
84
|
+
|
|
84
85
|
# Task is ready for post-processing
|
|
85
86
|
result = tasks[task_to_process].result()
|
|
86
87
|
c = estimator.cliq[task_to_process]
|
|
87
88
|
for i_idx, i in enumerate(c):
|
|
88
89
|
for j_idx, j in enumerate(c):
|
|
89
90
|
for s in range(len(result.values[i_idx])):
|
|
90
|
-
measurements[i][j][(result.values[i_idx][s],
|
|
91
|
-
result.values[j_idx][s])] += 1
|
|
91
|
+
measurements[i][j][(result.values[i_idx][s], result.values[j_idx][s])] += 1
|
|
92
92
|
# Remove task from the queue
|
|
93
93
|
tasks.pop(task_to_process)
|
|
94
94
|
|
|
@@ -101,7 +101,7 @@ def run_adaptive_allocation(
|
|
|
101
101
|
estimator: AdaptiveShotAllocator,
|
|
102
102
|
shots_per_round: int,
|
|
103
103
|
num_rounds: int,
|
|
104
|
-
verbose: bool = False
|
|
104
|
+
verbose: bool = False,
|
|
105
105
|
) -> MeasurementData:
|
|
106
106
|
"""
|
|
107
107
|
Run adaptive shot allocation process.
|
|
@@ -118,14 +118,12 @@ def run_adaptive_allocation(
|
|
|
118
118
|
MeasurementData: Final measurement outcomes
|
|
119
119
|
"""
|
|
120
120
|
if verbose:
|
|
121
|
-
print(
|
|
122
|
-
f"Running {num_rounds} rounds with {shots_per_round} shots each:")
|
|
121
|
+
print(f"Running {num_rounds} rounds with {shots_per_round} shots each:")
|
|
123
122
|
for i in range(num_rounds):
|
|
124
123
|
if verbose:
|
|
125
|
-
print(f"Round {i+1}/{num_rounds}...")
|
|
124
|
+
print(f"Round {i + 1}/{num_rounds}...")
|
|
126
125
|
shots_to_run = estimator.incremental_shot_allocation(shots_per_round)
|
|
127
|
-
new_measurements = run_fixed_allocation(
|
|
128
|
-
device, circuit, estimator, shots_to_run)
|
|
126
|
+
new_measurements = run_fixed_allocation(device, circuit, estimator, shots_to_run)
|
|
129
127
|
estimator.update_measurements(new_measurements)
|
|
130
128
|
|
|
131
129
|
return estimator.measurements
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
|
|
14
|
+
from braket.experimental.algorithms.quantum_counting.quantum_counting import ( # noqa: F401, E501
|
|
15
|
+
build_grover_circuit,
|
|
16
|
+
build_oracle_circuit,
|
|
17
|
+
get_quantum_counting_results,
|
|
18
|
+
quantum_counting_circuit,
|
|
19
|
+
run_quantum_counting,
|
|
20
|
+
)
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
"""Quantum Counting Algorithm implementation using the Amazon Braket SDK.
|
|
2
|
+
|
|
3
|
+
The quantum counting algorithm combines Grover's search operator with Quantum
|
|
4
|
+
Phase Estimation (QPE) to count the number of marked items in an unstructured
|
|
5
|
+
search space of N = 2^n elements.
|
|
6
|
+
|
|
7
|
+
Reference:
|
|
8
|
+
G. Brassard, P. Høyer, and A. Tapp, "Quantum Counting",
|
|
9
|
+
Proceedings of ICALP 1998. arXiv:quant-ph/9805082
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import math
|
|
13
|
+
from typing import Any, Dict, List, Tuple
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
from braket.circuits import Circuit
|
|
18
|
+
from braket.circuits.qubit_set import QubitSetInput
|
|
19
|
+
from braket.devices import Device
|
|
20
|
+
from braket.experimental.algorithms.grovers_search.grovers_search import amplify, build_oracle
|
|
21
|
+
from braket.tasks import QuantumTask
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build_oracle_circuit(
|
|
25
|
+
n_qubits: int,
|
|
26
|
+
marked_states: List[int],
|
|
27
|
+
decompose_ccnot: bool = False,
|
|
28
|
+
) -> Circuit:
|
|
29
|
+
"""Build an oracle circuit that flips the phase of marked states.
|
|
30
|
+
|
|
31
|
+
Composes individual oracle circuits from grovers_search.build_oracle
|
|
32
|
+
for each marked state. Each call flips the phase of one basis state,
|
|
33
|
+
and their composition marks all specified states.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
n_qubits (int): Number of qubits in the search register.
|
|
37
|
+
marked_states (List[int]): Indices of marked computational-basis states.
|
|
38
|
+
decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Circuit: Oracle circuit that flips the phase of all marked states.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If a marked state index is out of range.
|
|
45
|
+
"""
|
|
46
|
+
dim = 2**n_qubits
|
|
47
|
+
for state in marked_states:
|
|
48
|
+
if state < 0 or state >= dim:
|
|
49
|
+
raise ValueError(
|
|
50
|
+
f"Marked state {state} is out of range for {n_qubits} qubits "
|
|
51
|
+
f"(must be in [0, {dim - 1}])."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
oracle_circ = Circuit()
|
|
55
|
+
for state in marked_states:
|
|
56
|
+
bitstring = format(state, f"0{n_qubits}b")
|
|
57
|
+
oracle_circ.add_circuit(build_oracle(bitstring, decompose_ccnot))
|
|
58
|
+
return oracle_circ
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def build_grover_circuit(
|
|
62
|
+
n_qubits: int,
|
|
63
|
+
marked_states: List[int],
|
|
64
|
+
decompose_ccnot: bool = False,
|
|
65
|
+
) -> Circuit:
|
|
66
|
+
"""Build the Grover operator G = D · O as a circuit.
|
|
67
|
+
|
|
68
|
+
Uses circuit primitives from grovers_search: build_oracle for the phase
|
|
69
|
+
oracle and amplify for the diffusion operator (H · oracle_0 · H).
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
n_qubits (int): Number of qubits in the search register.
|
|
73
|
+
marked_states (List[int]): Indices of marked states.
|
|
74
|
+
decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Circuit: Circuit implementing the Grover operator G.
|
|
78
|
+
"""
|
|
79
|
+
oracle_circ = build_oracle_circuit(n_qubits, marked_states, decompose_ccnot)
|
|
80
|
+
diffusion_circ = amplify(n_qubits, decompose_ccnot)
|
|
81
|
+
|
|
82
|
+
grover_circ = Circuit()
|
|
83
|
+
grover_circ.add_circuit(oracle_circ)
|
|
84
|
+
grover_circ.add_circuit(diffusion_circ)
|
|
85
|
+
return grover_circ
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _controlled_oracle_circuit(
|
|
89
|
+
control: int,
|
|
90
|
+
search_qubits: QubitSetInput,
|
|
91
|
+
marked_states: List[int],
|
|
92
|
+
decompose_ccnot: bool = False,
|
|
93
|
+
) -> Circuit:
|
|
94
|
+
"""Build a controlled oracle circuit using gate-level primitives.
|
|
95
|
+
|
|
96
|
+
Makes the phase-flip oracle controlled on the given qubit by prepending
|
|
97
|
+
"1" to each marked-state bitstring, so the MCZ gate gains an extra
|
|
98
|
+
control qubit.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
control (int): Index of the control qubit.
|
|
102
|
+
search_qubits (QubitSetInput): Indices of the search register qubits.
|
|
103
|
+
marked_states (List[int]): Indices of marked computational-basis states.
|
|
104
|
+
decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Circuit: Controlled oracle circuit.
|
|
108
|
+
"""
|
|
109
|
+
n_search = len(search_qubits)
|
|
110
|
+
circ = Circuit()
|
|
111
|
+
|
|
112
|
+
for state in marked_states:
|
|
113
|
+
bitstring = format(state, f"0{n_search}b")
|
|
114
|
+
# Prepend "1" so the MCZ is controlled on the QPE qubit
|
|
115
|
+
controlled_bitstring = "1" + bitstring
|
|
116
|
+
oracle_sub = build_oracle(controlled_bitstring, decompose_ccnot)
|
|
117
|
+
# Map the oracle qubits: qubit 0 -> control, qubits 1..n -> search_qubits
|
|
118
|
+
target_mapping = [control] + list(search_qubits)
|
|
119
|
+
# The oracle may use ancilla qubits beyond n_search+1; they are
|
|
120
|
+
# automatically placed by add_circuit's target mapping.
|
|
121
|
+
all_targets = target_mapping + list(
|
|
122
|
+
range(
|
|
123
|
+
max(target_mapping) + 1,
|
|
124
|
+
max(target_mapping) + 1 + oracle_sub.qubit_count - len(target_mapping),
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
circ.add_circuit(oracle_sub, target=all_targets)
|
|
128
|
+
|
|
129
|
+
return circ
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _controlled_diffusion_circuit(
|
|
133
|
+
control: int,
|
|
134
|
+
search_qubits: QubitSetInput,
|
|
135
|
+
decompose_ccnot: bool = False,
|
|
136
|
+
) -> Circuit:
|
|
137
|
+
"""Build a controlled diffusion (amplitude amplification) circuit.
|
|
138
|
+
|
|
139
|
+
The diffusion operator is D = H^⊗n · O_0 · H^⊗n, where O_0 flips the
|
|
140
|
+
phase of the |0⟩ state. To make it controlled:
|
|
141
|
+
- H gates become controlled-H (CH) gates
|
|
142
|
+
- O_0 becomes controlled by prepending "1" to the "00...0" bitstring
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
control (int): Index of the control qubit.
|
|
146
|
+
search_qubits (QubitSetInput): Indices of the search register qubits.
|
|
147
|
+
decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Circuit: Controlled diffusion circuit.
|
|
151
|
+
"""
|
|
152
|
+
n_search = len(search_qubits)
|
|
153
|
+
circ = Circuit()
|
|
154
|
+
|
|
155
|
+
# First set of controlled-H gates on search qubits
|
|
156
|
+
for sq in search_qubits:
|
|
157
|
+
_add_controlled_h(circ, control, sq)
|
|
158
|
+
|
|
159
|
+
# Controlled O_0: flip phase of |0⟩, controlled on QPE qubit
|
|
160
|
+
# O_0 uses build_oracle("00...0"). Controlled version: build_oracle("1" + "00...0")
|
|
161
|
+
controlled_zero_bitstring = "1" + "0" * n_search
|
|
162
|
+
zero_oracle = build_oracle(controlled_zero_bitstring, decompose_ccnot)
|
|
163
|
+
target_mapping = [control] + list(search_qubits)
|
|
164
|
+
all_targets = target_mapping + list(
|
|
165
|
+
range(
|
|
166
|
+
max(target_mapping) + 1,
|
|
167
|
+
max(target_mapping) + 1 + zero_oracle.qubit_count - len(target_mapping),
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
circ.add_circuit(zero_oracle, target=all_targets)
|
|
171
|
+
|
|
172
|
+
# Second set of controlled-H gates on search qubits
|
|
173
|
+
for sq in search_qubits:
|
|
174
|
+
_add_controlled_h(circ, control, sq)
|
|
175
|
+
|
|
176
|
+
# Apply Z gate to correct the -1 global phase difference between
|
|
177
|
+
# I - 2|s><s| and 2|s><s| - I when controlled.
|
|
178
|
+
circ.z(control)
|
|
179
|
+
|
|
180
|
+
return circ
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _add_controlled_h(circ: Circuit, control: int, target: int) -> None:
|
|
184
|
+
"""Add a controlled-Hadamard gate to the circuit.
|
|
185
|
+
|
|
186
|
+
Decomposition: CH(c, t) = S†(t) · H(t) · T†(t) · CX(c,t) · T(t) · H(t) · S(t)
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
circ (Circuit): Circuit to append the controlled-H to.
|
|
190
|
+
control (int): Control qubit index.
|
|
191
|
+
target (int): Target qubit index.
|
|
192
|
+
"""
|
|
193
|
+
circ.s(target)
|
|
194
|
+
circ.h(target)
|
|
195
|
+
circ.t(target)
|
|
196
|
+
circ.cnot(control, target)
|
|
197
|
+
circ.ti(target)
|
|
198
|
+
circ.h(target)
|
|
199
|
+
circ.si(target)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def controlled_grover_circuit(
|
|
203
|
+
control: int,
|
|
204
|
+
search_qubits: QubitSetInput,
|
|
205
|
+
marked_states: List[int],
|
|
206
|
+
power: int = 1,
|
|
207
|
+
decompose_ccnot: bool = False,
|
|
208
|
+
) -> Circuit:
|
|
209
|
+
"""Build a controlled Grover operator G^power as a gate-level circuit.
|
|
210
|
+
|
|
211
|
+
Constructs the controlled Grover operator C-G = C-D · C-O entirely from
|
|
212
|
+
gate-level primitives:
|
|
213
|
+
- The oracle is made controlled by prepending "1" to each marked-state
|
|
214
|
+
bitstring, adding the control qubit as an extra MCZ control.
|
|
215
|
+
- The diffusion operator is made controlled by using controlled-H (CH)
|
|
216
|
+
gates, a controlled zero-oracle, and a Z gate on the control qubit
|
|
217
|
+
to correct the inherent -1 global phase of the diffusion operator.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
control (int): Index of the QPE control qubit.
|
|
221
|
+
search_qubits (QubitSetInput): Indices of the search register qubits.
|
|
222
|
+
marked_states (List[int]): Indices of marked computational-basis states.
|
|
223
|
+
power (int): Number of times to apply the Grover operator (default 1).
|
|
224
|
+
decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Circuit: Circuit implementing the controlled G^power operator.
|
|
228
|
+
"""
|
|
229
|
+
circ = Circuit()
|
|
230
|
+
|
|
231
|
+
for _ in range(power):
|
|
232
|
+
# Controlled oracle
|
|
233
|
+
circ.add_circuit(
|
|
234
|
+
_controlled_oracle_circuit(control, search_qubits, marked_states, decompose_ccnot)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Controlled diffusion
|
|
238
|
+
circ.add_circuit(
|
|
239
|
+
_controlled_diffusion_circuit(control, search_qubits, decompose_ccnot)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return circ
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def inverse_qft_for_counting(qubits: QubitSetInput) -> Circuit:
|
|
246
|
+
"""Inverse Quantum Fourier Transform applied to the given qubits.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
qubits (QubitSetInput): Qubits on which to apply the inverse QFT.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Circuit: Circuit implementing the inverse QFT.
|
|
253
|
+
"""
|
|
254
|
+
qft_circ = Circuit()
|
|
255
|
+
num_qubits = len(qubits)
|
|
256
|
+
|
|
257
|
+
# SWAP gates to reverse qubit order
|
|
258
|
+
for i in range(math.floor(num_qubits / 2)):
|
|
259
|
+
qft_circ.swap(qubits[i], qubits[-i - 1])
|
|
260
|
+
|
|
261
|
+
# Controlled phase rotations + Hadamard
|
|
262
|
+
for k in reversed(range(num_qubits)):
|
|
263
|
+
for j in reversed(range(1, num_qubits - k)):
|
|
264
|
+
angle = -2 * math.pi / (2 ** (j + 1))
|
|
265
|
+
qft_circ.cphaseshift(qubits[k + j], qubits[k], angle)
|
|
266
|
+
qft_circ.h(qubits[k])
|
|
267
|
+
|
|
268
|
+
return qft_circ
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def quantum_counting_circuit(
|
|
272
|
+
counting_circ: Circuit,
|
|
273
|
+
counting_qubits: QubitSetInput,
|
|
274
|
+
search_qubits: QubitSetInput,
|
|
275
|
+
marked_states: List[int],
|
|
276
|
+
decompose_ccnot: bool = False,
|
|
277
|
+
) -> Circuit:
|
|
278
|
+
"""Create the full quantum counting circuit with result types.
|
|
279
|
+
|
|
280
|
+
Builds the controlled Grover operator as a gate-level circuit and
|
|
281
|
+
applies QPE.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
counting_circ (Circuit): Initial circuit (may contain setup operations).
|
|
285
|
+
counting_qubits (QubitSetInput): Qubits for the counting (precision) register.
|
|
286
|
+
search_qubits (QubitSetInput): Qubits for the search register.
|
|
287
|
+
marked_states (List[int]): Indices of marked computational-basis states.
|
|
288
|
+
decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Circuit: The complete quantum counting circuit with result types.
|
|
292
|
+
"""
|
|
293
|
+
new_circuit = Circuit().add_circuit(counting_circ)
|
|
294
|
+
new_circuit.add_circuit(
|
|
295
|
+
quantum_counting(
|
|
296
|
+
counting_qubits, search_qubits, marked_states, decompose_ccnot
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
return new_circuit.probability(counting_qubits)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def quantum_counting(
|
|
303
|
+
counting_qubits: QubitSetInput,
|
|
304
|
+
search_qubits: QubitSetInput,
|
|
305
|
+
marked_states: List[int],
|
|
306
|
+
decompose_ccnot: bool = False,
|
|
307
|
+
) -> Circuit:
|
|
308
|
+
"""Build the core quantum counting circuit using QPE on the Grover operator.
|
|
309
|
+
|
|
310
|
+
Constructs the controlled Grover operator as a gate-level circuit from
|
|
311
|
+
grovers_search primitives (build_oracle for the phase oracle and
|
|
312
|
+
controlled-H gates for the diffusion operator).
|
|
313
|
+
|
|
314
|
+
The circuit structure:
|
|
315
|
+
1. Apply H to all counting qubits
|
|
316
|
+
2. Apply H to all search qubits (prepare uniform superposition |s⟩)
|
|
317
|
+
3. Apply controlled-G^(2^k) for each counting qubit k
|
|
318
|
+
4. Apply inverse QFT to counting qubits
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
counting_qubits (QubitSetInput): Qubits for the counting (precision) register.
|
|
322
|
+
search_qubits (QubitSetInput): Qubits for the search register.
|
|
323
|
+
marked_states (List[int]): Indices of marked computational-basis states.
|
|
324
|
+
decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Circuit: Circuit implementing the quantum counting algorithm.
|
|
328
|
+
"""
|
|
329
|
+
qc_circ = Circuit()
|
|
330
|
+
|
|
331
|
+
# Hadamard on counting qubits
|
|
332
|
+
qc_circ.h(counting_qubits)
|
|
333
|
+
|
|
334
|
+
# Hadamard on search qubits (prepare |s⟩)
|
|
335
|
+
qc_circ.h(search_qubits)
|
|
336
|
+
|
|
337
|
+
# Controlled-G^(2^k)
|
|
338
|
+
for ii, qubit in enumerate(reversed(counting_qubits)):
|
|
339
|
+
power = 2**ii
|
|
340
|
+
qc_circ.add_circuit(
|
|
341
|
+
controlled_grover_circuit(
|
|
342
|
+
qubit, search_qubits, marked_states, power, decompose_ccnot
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Inverse QFT on counting qubits
|
|
347
|
+
qc_circ.add_circuit(inverse_qft_for_counting(counting_qubits))
|
|
348
|
+
|
|
349
|
+
return qc_circ
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def run_quantum_counting(
|
|
353
|
+
circuit: Circuit,
|
|
354
|
+
device: Device,
|
|
355
|
+
shots: int = 1000,
|
|
356
|
+
) -> QuantumTask:
|
|
357
|
+
"""Run the quantum counting circuit on a device.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
circuit (Circuit): The quantum counting circuit to run.
|
|
361
|
+
device (Device): Braket device backend.
|
|
362
|
+
shots (int): Number of measurement shots (default is 1000).
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
QuantumTask: Task from running quantum counting.
|
|
366
|
+
"""
|
|
367
|
+
task = device.run(circuit, shots=shots)
|
|
368
|
+
return task
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def get_quantum_counting_results(
|
|
372
|
+
task,
|
|
373
|
+
counting_qubits: QubitSetInput,
|
|
374
|
+
search_qubits: QubitSetInput,
|
|
375
|
+
verbose: bool = False,
|
|
376
|
+
) -> Dict[str, Any]:
|
|
377
|
+
"""Post-process quantum counting results to estimate the number of marked items.
|
|
378
|
+
|
|
379
|
+
After measuring the counting qubits, the most likely outcome y gives
|
|
380
|
+
an estimate of the phase φ = y / 2^t. The number of marked items M is:
|
|
381
|
+
M = N · sin²(π · φ)
|
|
382
|
+
where N = 2^n_search.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
task (QuantumTask): The task holding quantum counting results.
|
|
386
|
+
counting_qubits (QubitSetInput): Qubits of the counting register.
|
|
387
|
+
search_qubits (QubitSetInput): Qubits of the search register.
|
|
388
|
+
verbose (bool): If True, print detailed results (default False).
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Dict[str, Any]: Aggregate measurement results including:
|
|
392
|
+
- measurement_counts: raw measurement counts
|
|
393
|
+
- counting_register_results: counts collapsed to the counting register
|
|
394
|
+
- phases: estimated phases φ for each measured bitstring
|
|
395
|
+
- estimated_counts: estimated M for each measured bitstring
|
|
396
|
+
- best_estimate: best estimate of M (from most frequent outcome)
|
|
397
|
+
- search_space_size: N = 2^n_search
|
|
398
|
+
"""
|
|
399
|
+
result = task.result()
|
|
400
|
+
measurement_counts = result.measurement_counts
|
|
401
|
+
n_counting = len(counting_qubits)
|
|
402
|
+
n_search = len(search_qubits)
|
|
403
|
+
N = 2**n_search
|
|
404
|
+
|
|
405
|
+
# Aggregate results on counting register (trace out search qubits)
|
|
406
|
+
counting_register_results: Dict[str, int] = {}
|
|
407
|
+
if measurement_counts:
|
|
408
|
+
for key in measurement_counts.keys():
|
|
409
|
+
counting_bits = key[:n_counting]
|
|
410
|
+
counting_register_results[counting_bits] = (
|
|
411
|
+
counting_register_results.get(counting_bits, 0) + measurement_counts[key]
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# Convert counting register bitstrings to phase estimates and M estimates
|
|
415
|
+
phases, estimated_counts = _get_counting_estimates(counting_register_results, n_counting, N)
|
|
416
|
+
|
|
417
|
+
# Best estimate from most frequent outcome
|
|
418
|
+
if counting_register_results:
|
|
419
|
+
best_key = max(counting_register_results, key=counting_register_results.get)
|
|
420
|
+
best_y = int(best_key, 2)
|
|
421
|
+
raw_phase = best_y / (2**n_counting)
|
|
422
|
+
best_phase = raw_phase
|
|
423
|
+
best_M = N * (np.sin(np.pi * best_phase) ** 2)
|
|
424
|
+
else:
|
|
425
|
+
best_key = None
|
|
426
|
+
best_M = None
|
|
427
|
+
|
|
428
|
+
aggregate_results = {
|
|
429
|
+
"measurement_counts": measurement_counts,
|
|
430
|
+
"counting_register_results": counting_register_results,
|
|
431
|
+
"phases": phases,
|
|
432
|
+
"estimated_counts": estimated_counts,
|
|
433
|
+
"best_estimate": best_M,
|
|
434
|
+
"search_space_size": N,
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if verbose:
|
|
438
|
+
print(f"Search space size N = {N}")
|
|
439
|
+
sorted_cr = sorted(
|
|
440
|
+
counting_register_results.items(), key=lambda x: x[1], reverse=True
|
|
441
|
+
)
|
|
442
|
+
print("\nCounting register distribution (top outcomes):")
|
|
443
|
+
for bitstring, count in sorted_cr[:6]:
|
|
444
|
+
y_val = int(bitstring, 2)
|
|
445
|
+
raw_ph = y_val / (2**n_counting)
|
|
446
|
+
ph = raw_ph
|
|
447
|
+
m_est = N * (np.sin(np.pi * ph) ** 2)
|
|
448
|
+
print(f" |{bitstring}>: {count} counts -> phase = {ph:.4f}, M ~ {m_est:.4f}")
|
|
449
|
+
if len(sorted_cr) > 6:
|
|
450
|
+
print(f" ... ({len(sorted_cr) - 6} more outcomes)")
|
|
451
|
+
print(f"\nBest estimate of M: {best_M}")
|
|
452
|
+
|
|
453
|
+
return aggregate_results
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _get_counting_estimates(
|
|
457
|
+
counting_register_results: Dict[str, int],
|
|
458
|
+
n_counting: int,
|
|
459
|
+
N: int,
|
|
460
|
+
) -> Tuple[List[float], List[float]]:
|
|
461
|
+
"""Convert counting register bitstrings to phase and count estimates.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
counting_register_results (Dict[str, int]): Measurement results on the counting register.
|
|
465
|
+
n_counting (int): Number of counting qubits.
|
|
466
|
+
N (int): Search space size.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Tuple[List[float], List[float]]: (phases, estimated_counts) lists, one entry per unique
|
|
470
|
+
measured bitstring with counts > 0.
|
|
471
|
+
"""
|
|
472
|
+
phases = []
|
|
473
|
+
estimated_counts = []
|
|
474
|
+
|
|
475
|
+
for bitstring in counting_register_results:
|
|
476
|
+
y = int(bitstring, 2)
|
|
477
|
+
raw_phase = y / (2**n_counting)
|
|
478
|
+
phase = raw_phase
|
|
479
|
+
M_est = N * (np.sin(np.pi * phase) ** 2)
|
|
480
|
+
phases.append(phase)
|
|
481
|
+
estimated_counts.append(M_est)
|
|
482
|
+
|
|
483
|
+
return phases, estimated_counts
|
|
File without changes
|
|
File without changes
|
{amazon_braket_algorithm_library-1.7.3 → amazon_braket_algorithm_library-1.7.4}/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|