qharm-eigenbridge 0.0.2__tar.gz → 0.0.3__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.
- qharm_eigenbridge-0.0.3/PKG-INFO +77 -0
- qharm_eigenbridge-0.0.3/README.md +50 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/eigenbridge/quantum_solvers.py +136 -35
- qharm_eigenbridge-0.0.3/python/qharm_eigenbridge.egg-info/PKG-INFO +77 -0
- qharm_eigenbridge-0.0.3/python/qharm_eigenbridge.egg-info/scm_version.json +8 -0
- qharm_eigenbridge-0.0.3/tests/unit_tests/test_eigensolvers.py +187 -0
- qharm_eigenbridge-0.0.2/PKG-INFO +0 -55
- qharm_eigenbridge-0.0.2/README.md +0 -28
- qharm_eigenbridge-0.0.2/python/qharm_eigenbridge.egg-info/PKG-INFO +0 -55
- qharm_eigenbridge-0.0.2/python/qharm_eigenbridge.egg-info/scm_version.json +0 -8
- qharm_eigenbridge-0.0.2/tests/unit_tests/test_eigensolvers.py +0 -82
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/.clang-format +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/.github/workflows/merge.yaml +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/.github/workflows/pull_request.yaml +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/.gitignore +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/.licenserc.yaml +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/.pre-commit-config.yaml +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/LICENSE +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/pyproject.toml +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/eigenbridge/__init__.py +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/qharm_eigenbridge.egg-info/SOURCES.txt +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/qharm_eigenbridge.egg-info/dependency_links.txt +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/qharm_eigenbridge.egg-info/requires.txt +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/qharm_eigenbridge.egg-info/scm_file_list.json +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/qharm_eigenbridge.egg-info/top_level.txt +0 -0
- {qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/setup.cfg +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qharm-eigenbridge
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Quantum eigensolver implementation using Qiskit
|
|
5
|
+
Author-email: Jonathan Waldrop <jwaldrop@ameslab.gov>, Mahir Anand <mahiranand@outlook.com>
|
|
6
|
+
Classifier: Development Status :: 4 - Beta
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: pybind11
|
|
19
|
+
Requires-Dist: qiskit
|
|
20
|
+
Requires-Dist: qiskit-algorithms
|
|
21
|
+
Requires-Dist: qiskit-aer
|
|
22
|
+
Requires-Dist: qiskit-ibm-runtime
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
<!--
|
|
29
|
+
~ Copyright 2026 QHARM
|
|
30
|
+
~
|
|
31
|
+
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
32
|
+
~ you may not use this file except in compliance with the License.
|
|
33
|
+
~ You may obtain a copy of the License at
|
|
34
|
+
~
|
|
35
|
+
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
36
|
+
~
|
|
37
|
+
~ Unless required by applicable law or agreed to in writing, software
|
|
38
|
+
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
39
|
+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
40
|
+
~ See the License for the specific language governing permissions and
|
|
41
|
+
~ limitations under the License.
|
|
42
|
+
-->
|
|
43
|
+
|
|
44
|
+
# EigenBridge
|
|
45
|
+
|
|
46
|
+
There are two quantum solvers. VQD can return several eigenvalues; QAOA returns only the ground state.
|
|
47
|
+
|
|
48
|
+
VQD -> `run_vqd_eigensolver(matrix, k, use_noise=false)` -> lowest `k` eigenvalues (`k` defaults to all)
|
|
49
|
+
QAOA -> `run_qaoa_eigensolver(matrix, use_noise=false)` -> ground state only
|
|
50
|
+
|
|
51
|
+
**Uncertainties (`uq_values` / `uq_vectors`)**
|
|
52
|
+
- For every VQD/QAOA path (noiseless and noisy), at each final optimized circuit/state,
|
|
53
|
+
`uq_values[i] = sqrt(max(0, ⟨H²⟩ − ⟨H⟩²))`, where ⟨H⟩ is the reported eigenvalue and ⟨H²⟩
|
|
54
|
+
comes from the same estimator on `H @ H` at the same parameters.
|
|
55
|
+
This is the standard deviation of a single measurement of H in the prepared state
|
|
56
|
+
(intrinsic quantum / variational residual variance).
|
|
57
|
+
Exact eigenstates give σ ≈ 0; approximate ansatz states give nonzero σ even without noise.
|
|
58
|
+
- `uq_vectors` uses the same layout as the returned eigenvectors (column `i` = mode `i`).
|
|
59
|
+
For each physical basis index `j`,
|
|
60
|
+
`uq_vectors[j, i] = sqrt(p_j (1 − p_j))` where `p_j = ⟨|j⟩⟨j|⟩` is evaluated with the
|
|
61
|
+
**same estimator / circuit / parameters** as the energy (FakeManila when `use_noise=true`).
|
|
62
|
+
This is the std of a single-shot computational-basis occupation measurement for component `j`.
|
|
63
|
+
Caveat: it can be > 0 even for an exact delocalized eigenstate (unlike σ_H, which is 0 for
|
|
64
|
+
exact energy eigenstates). It is **not** “distance to the classical eigenvector.”
|
|
65
|
+
Unused eigenvector columns stay 0. Reported eigenvectors still come from the ideal
|
|
66
|
+
statevector of the optimized circuit; only the UQ uses the estimator/noise path.
|
|
67
|
+
- Non–power-of-2 dimensions pad to the next power of two and place
|
|
68
|
+
`penalty = max(1.0, Gershgorin row-sum bound)` on unused diagonal entries so pad
|
|
69
|
+
eigenvalues stay outside the original spectrum while avoiding the old 10× entry-scale
|
|
70
|
+
inflation of ⟨H²⟩ on small problems.
|
|
71
|
+
- Noisy VQD keeps exact statevector overlaps; only the energy / projector estimator uses
|
|
72
|
+
FakeManila noise (demo). Expect worse eigenvalues, especially for higher states.
|
|
73
|
+
|
|
74
|
+
Unit tests check that reported `uq_values` / filled `uq_vectors` entries are finite and
|
|
75
|
+
nonnegative (filled columns not identically zero on the demo matrix), and that on the
|
|
76
|
+
demo 3×3 matrix the ground-state interval `[⟨H⟩−σ, ⟨H⟩+σ]` contains the classical ground
|
|
77
|
+
eigenvalue for noisy paths. They do not require noisy eigenvalues to match the classical answer.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
~ Copyright 2026 QHARM
|
|
3
|
+
~
|
|
4
|
+
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
~ you may not use this file except in compliance with the License.
|
|
6
|
+
~ You may obtain a copy of the License at
|
|
7
|
+
~
|
|
8
|
+
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
~
|
|
10
|
+
~ Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
~ See the License for the specific language governing permissions and
|
|
14
|
+
~ limitations under the License.
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
# EigenBridge
|
|
18
|
+
|
|
19
|
+
There are two quantum solvers. VQD can return several eigenvalues; QAOA returns only the ground state.
|
|
20
|
+
|
|
21
|
+
VQD -> `run_vqd_eigensolver(matrix, k, use_noise=false)` -> lowest `k` eigenvalues (`k` defaults to all)
|
|
22
|
+
QAOA -> `run_qaoa_eigensolver(matrix, use_noise=false)` -> ground state only
|
|
23
|
+
|
|
24
|
+
**Uncertainties (`uq_values` / `uq_vectors`)**
|
|
25
|
+
- For every VQD/QAOA path (noiseless and noisy), at each final optimized circuit/state,
|
|
26
|
+
`uq_values[i] = sqrt(max(0, ⟨H²⟩ − ⟨H⟩²))`, where ⟨H⟩ is the reported eigenvalue and ⟨H²⟩
|
|
27
|
+
comes from the same estimator on `H @ H` at the same parameters.
|
|
28
|
+
This is the standard deviation of a single measurement of H in the prepared state
|
|
29
|
+
(intrinsic quantum / variational residual variance).
|
|
30
|
+
Exact eigenstates give σ ≈ 0; approximate ansatz states give nonzero σ even without noise.
|
|
31
|
+
- `uq_vectors` uses the same layout as the returned eigenvectors (column `i` = mode `i`).
|
|
32
|
+
For each physical basis index `j`,
|
|
33
|
+
`uq_vectors[j, i] = sqrt(p_j (1 − p_j))` where `p_j = ⟨|j⟩⟨j|⟩` is evaluated with the
|
|
34
|
+
**same estimator / circuit / parameters** as the energy (FakeManila when `use_noise=true`).
|
|
35
|
+
This is the std of a single-shot computational-basis occupation measurement for component `j`.
|
|
36
|
+
Caveat: it can be > 0 even for an exact delocalized eigenstate (unlike σ_H, which is 0 for
|
|
37
|
+
exact energy eigenstates). It is **not** “distance to the classical eigenvector.”
|
|
38
|
+
Unused eigenvector columns stay 0. Reported eigenvectors still come from the ideal
|
|
39
|
+
statevector of the optimized circuit; only the UQ uses the estimator/noise path.
|
|
40
|
+
- Non–power-of-2 dimensions pad to the next power of two and place
|
|
41
|
+
`penalty = max(1.0, Gershgorin row-sum bound)` on unused diagonal entries so pad
|
|
42
|
+
eigenvalues stay outside the original spectrum while avoiding the old 10× entry-scale
|
|
43
|
+
inflation of ⟨H²⟩ on small problems.
|
|
44
|
+
- Noisy VQD keeps exact statevector overlaps; only the energy / projector estimator uses
|
|
45
|
+
FakeManila noise (demo). Expect worse eigenvalues, especially for higher states.
|
|
46
|
+
|
|
47
|
+
Unit tests check that reported `uq_values` / filled `uq_vectors` entries are finite and
|
|
48
|
+
nonnegative (filled columns not identically zero on the demo matrix), and that on the
|
|
49
|
+
demo 3×3 matrix the ground-state interval `[⟨H⟩−σ, ⟨H⟩+σ]` contains the classical ground
|
|
50
|
+
eigenvalue for noisy paths. They do not require noisy eigenvalues to match the classical answer.
|
|
@@ -99,7 +99,9 @@ def _matrix_to_observable(flat_matrix, n):
|
|
|
99
99
|
if next_pow2 != n:
|
|
100
100
|
padded_mat = np.zeros((next_pow2, next_pow2))
|
|
101
101
|
padded_mat[:n, :n] = mat
|
|
102
|
-
|
|
102
|
+
# Pad unused diagonal with Gershgorin bound so fake eigenvalues stay outside.
|
|
103
|
+
gershgorin = float(np.max(np.sum(np.abs(mat), axis=1)))
|
|
104
|
+
penalty = max(1.0, gershgorin)
|
|
103
105
|
for i in range(n, next_pow2):
|
|
104
106
|
padded_mat[i, i] = penalty
|
|
105
107
|
mat = padded_mat
|
|
@@ -119,12 +121,12 @@ def _make_estimator(use_noise):
|
|
|
119
121
|
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
|
|
120
122
|
except ImportError as exc:
|
|
121
123
|
raise ImportError(
|
|
122
|
-
"Noisy
|
|
124
|
+
"Noisy solvers require qiskit-aer and qiskit-ibm-runtime. "
|
|
123
125
|
"Install with: pip install qiskit-aer qiskit-ibm-runtime"
|
|
124
126
|
) from exc
|
|
125
127
|
|
|
126
128
|
fake_backend = FakeManilaV2()
|
|
127
|
-
noise_model = NoiseModel.from_backend(fake_backend
|
|
129
|
+
noise_model = NoiseModel.from_backend(fake_backend)
|
|
128
130
|
aer_simulator = AerSimulator(noise_model=noise_model)
|
|
129
131
|
pass_manager = generate_preset_pass_manager(
|
|
130
132
|
optimization_level=1, backend=aer_simulator
|
|
@@ -147,21 +149,36 @@ def _expect_energy(estimator, circuit, observable, parameters):
|
|
|
147
149
|
return float(np.real(evs[0]))
|
|
148
150
|
|
|
149
151
|
|
|
150
|
-
def
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
def _quantum_variance_std(estimator, circuit, observable, parameters, h_mean):
|
|
153
|
+
# Quantum std of H in this state: sqrt(max(0, <H^2> - <H>^2)).
|
|
154
|
+
h2 = _expect_energy(
|
|
155
|
+
estimator, circuit, observable @ observable, parameters
|
|
153
156
|
)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
return float(np.sqrt(max(0.0, h2 - h_mean**2)))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _occupation_stds(estimator, circuit, parameters, n, next_pow2):
|
|
161
|
+
# Occupation std per component: sqrt(p (1 - p)) for basis outcome probability p.
|
|
162
|
+
stds = np.zeros(n, dtype=float)
|
|
163
|
+
for j in range(n):
|
|
164
|
+
proj = np.zeros((next_pow2, next_pow2), dtype=float)
|
|
165
|
+
proj[j, j] = 1.0
|
|
166
|
+
p_j = _expect_energy(
|
|
167
|
+
estimator, circuit, SparsePauliOp.from_operator(proj), parameters
|
|
168
|
+
)
|
|
169
|
+
p_j = min(1.0, max(0.0, p_j))
|
|
170
|
+
stds[j] = float(np.sqrt(max(0.0, p_j - p_j * p_j)))
|
|
171
|
+
return stds
|
|
159
172
|
|
|
160
173
|
|
|
161
|
-
def run_vqd_eigensolver(flat_matrix, n, k=None):
|
|
174
|
+
def run_vqd_eigensolver(flat_matrix, n, k=None, use_noise=False):
|
|
162
175
|
"""
|
|
163
176
|
Takes a flat matrix of size n*n, and returns the lowest k eigenvalues
|
|
164
177
|
and matching eigenvectors using VQD.
|
|
178
|
+
|
|
179
|
+
With use_noise=True, only the energy estimator is noisy (FakeManila);
|
|
180
|
+
VQD still recognizes distinct states perfectly.
|
|
181
|
+
Expect worse eigenvalues, especially for k > 1.
|
|
165
182
|
"""
|
|
166
183
|
if k is None:
|
|
167
184
|
k = n
|
|
@@ -172,9 +189,14 @@ def run_vqd_eigensolver(flat_matrix, n, k=None):
|
|
|
172
189
|
num_qubits = int(np.log2(next_pow2))
|
|
173
190
|
|
|
174
191
|
ansatz = EfficientSU2(num_qubits, reps=1, entanglement="linear")
|
|
175
|
-
|
|
176
|
-
|
|
192
|
+
estimator, pass_manager = _make_estimator(use_noise)
|
|
193
|
+
# Keep exact fidelity even under noise: overlaps stay exact; only energy
|
|
194
|
+
# is FakeManila-noisy (same demo style as QAOA).
|
|
177
195
|
fidelity = _ExactStatevectorFidelity()
|
|
196
|
+
if use_noise:
|
|
197
|
+
optimizer = COBYLA(maxiter=50)
|
|
198
|
+
else:
|
|
199
|
+
optimizer = SLSQP(maxiter=1000, ftol=1e-9)
|
|
178
200
|
|
|
179
201
|
# Overlap weights on the scale of the original matrix, not the padding.
|
|
180
202
|
beta = 10.0 * max(
|
|
@@ -191,7 +213,18 @@ def run_vqd_eigensolver(flat_matrix, n, k=None):
|
|
|
191
213
|
rng.uniform(-np.pi, np.pi, ansatz.num_parameters)
|
|
192
214
|
)
|
|
193
215
|
|
|
194
|
-
|
|
216
|
+
if use_noise:
|
|
217
|
+
vqd = VQD(
|
|
218
|
+
estimator,
|
|
219
|
+
fidelity,
|
|
220
|
+
ansatz,
|
|
221
|
+
optimizer,
|
|
222
|
+
k=k,
|
|
223
|
+
betas=betas,
|
|
224
|
+
transpiler=pass_manager,
|
|
225
|
+
)
|
|
226
|
+
else:
|
|
227
|
+
vqd = VQD(estimator, fidelity, ansatz, optimizer, k=k, betas=betas)
|
|
195
228
|
vqd.initial_point = initial_points
|
|
196
229
|
|
|
197
230
|
result = vqd.compute_eigenvalues(observable)
|
|
@@ -199,13 +232,38 @@ def run_vqd_eigensolver(flat_matrix, n, k=None):
|
|
|
199
232
|
eigenvalues = [float(np.real(e)) for e in result.eigenvalues]
|
|
200
233
|
eigenvectors = np.zeros((n, n), dtype=float)
|
|
201
234
|
for i in range(k):
|
|
235
|
+
optimal_circuit = result.optimal_circuits[i]
|
|
236
|
+
optimal_parameters = result.optimal_points[i]
|
|
202
237
|
eigenvectors[:, i] = _statevector_to_real_eigenvector(
|
|
203
|
-
|
|
238
|
+
optimal_circuit, optimal_parameters, n
|
|
204
239
|
)
|
|
240
|
+
|
|
205
241
|
uq_values = [0.0] * len(eigenvalues)
|
|
206
|
-
uq_vectors =
|
|
242
|
+
uq_vectors = np.zeros((n, n), dtype=float)
|
|
243
|
+
for i in range(k):
|
|
244
|
+
optimal_circuit = result.optimal_circuits[i]
|
|
245
|
+
optimal_parameters = result.optimal_points[i]
|
|
246
|
+
uq_values[i] = _quantum_variance_std(
|
|
247
|
+
estimator,
|
|
248
|
+
optimal_circuit,
|
|
249
|
+
observable,
|
|
250
|
+
optimal_parameters,
|
|
251
|
+
eigenvalues[i],
|
|
252
|
+
)
|
|
253
|
+
uq_vectors[:, i] = _occupation_stds(
|
|
254
|
+
estimator,
|
|
255
|
+
optimal_circuit,
|
|
256
|
+
optimal_parameters,
|
|
257
|
+
n,
|
|
258
|
+
next_pow2,
|
|
259
|
+
)
|
|
207
260
|
|
|
208
|
-
return
|
|
261
|
+
return (
|
|
262
|
+
eigenvalues,
|
|
263
|
+
eigenvectors.ravel().tolist(),
|
|
264
|
+
uq_values,
|
|
265
|
+
uq_vectors.ravel().tolist(),
|
|
266
|
+
)
|
|
209
267
|
|
|
210
268
|
|
|
211
269
|
def run_qaoa_eigensolver(flat_matrix, n, use_noise=False, reps=3):
|
|
@@ -213,7 +271,7 @@ def run_qaoa_eigensolver(flat_matrix, n, use_noise=False, reps=3):
|
|
|
213
271
|
Takes a flat matrix of size n*n, and returns the ground eigenvalue,
|
|
214
272
|
and matching eigenvector using QAOA.
|
|
215
273
|
"""
|
|
216
|
-
observable,
|
|
274
|
+
observable, next_pow2 = _matrix_to_observable(flat_matrix, n)
|
|
217
275
|
ansatz = QAOAAnsatz(observable, reps=reps)
|
|
218
276
|
estimator, pass_manager = _make_estimator(use_noise)
|
|
219
277
|
if use_noise:
|
|
@@ -227,44 +285,87 @@ def run_qaoa_eigensolver(flat_matrix, n, use_noise=False, reps=3):
|
|
|
227
285
|
|
|
228
286
|
result = vqe.compute_minimum_eigenvalue(observable)
|
|
229
287
|
|
|
288
|
+
optimal_circuit = result.optimal_circuit
|
|
289
|
+
optimal_parameters = result.optimal_point
|
|
290
|
+
|
|
230
291
|
eigenvalues = [float(np.real(result.eigenvalue))]
|
|
231
292
|
eigenvectors = np.zeros((n, n), dtype=float)
|
|
232
293
|
eigenvectors[:, 0] = _statevector_to_real_eigenvector(
|
|
233
|
-
|
|
294
|
+
optimal_circuit, optimal_parameters, n
|
|
234
295
|
)
|
|
235
296
|
|
|
236
297
|
uq_values = [0.0] * len(eigenvalues)
|
|
237
|
-
uq_vectors =
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
298
|
+
uq_vectors = np.zeros((n, n), dtype=float)
|
|
299
|
+
uq_values[0] = _quantum_variance_std(
|
|
300
|
+
estimator,
|
|
301
|
+
optimal_circuit,
|
|
302
|
+
observable,
|
|
303
|
+
optimal_parameters,
|
|
304
|
+
eigenvalues[0],
|
|
305
|
+
)
|
|
306
|
+
uq_vectors[:, 0] = _occupation_stds(
|
|
307
|
+
estimator,
|
|
308
|
+
optimal_circuit,
|
|
309
|
+
optimal_parameters,
|
|
310
|
+
n,
|
|
311
|
+
next_pow2,
|
|
312
|
+
)
|
|
242
313
|
|
|
243
|
-
return
|
|
314
|
+
return (
|
|
315
|
+
eigenvalues,
|
|
316
|
+
eigenvectors.ravel().tolist(),
|
|
317
|
+
uq_values,
|
|
318
|
+
uq_vectors.ravel().tolist(),
|
|
319
|
+
)
|
|
244
320
|
|
|
245
321
|
|
|
246
322
|
# Quick Test
|
|
247
323
|
if __name__ == "__main__":
|
|
248
324
|
# The same 3x3 matrix from the C++ test file
|
|
249
325
|
test_matrix = [3.0, 5.0, 2.0, 5.0, 1.0, 3.0, 2.0, 3.0, 2.0]
|
|
250
|
-
|
|
251
|
-
print(
|
|
252
|
-
|
|
326
|
+
|
|
327
|
+
print("=== VQD (noiseless) ===")
|
|
328
|
+
values, vectors, vqd_uq_v, vqd_uq_vec = run_vqd_eigensolver(test_matrix, 3)
|
|
329
|
+
print(f"Eigenvalues: {values}")
|
|
330
|
+
print(f"Eigenvectors: {vectors}")
|
|
331
|
+
print(f"uq_values: {vqd_uq_v}")
|
|
332
|
+
print(f"uq_vectors: {vqd_uq_vec}")
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
print("\n=== VQD (noise) ===")
|
|
336
|
+
(
|
|
337
|
+
vqd_noisy_values,
|
|
338
|
+
vqd_noisy_vectors,
|
|
339
|
+
vqd_noisy_uq_v,
|
|
340
|
+
vqd_noisy_uq_vec,
|
|
341
|
+
) = run_vqd_eigensolver(test_matrix, 3, use_noise=True)
|
|
342
|
+
print(f"Eigenvalues: {vqd_noisy_values}")
|
|
343
|
+
print(f"Eigenvectors: {vqd_noisy_vectors}")
|
|
344
|
+
print(f"uq_values: {vqd_noisy_uq_v}")
|
|
345
|
+
print(f"uq_vectors: {vqd_noisy_uq_vec}")
|
|
346
|
+
except ImportError as exc:
|
|
347
|
+
print(f"Skipping noisy VQD demo: {exc}")
|
|
348
|
+
|
|
349
|
+
print("\n=== QAOA (noiseless) ===")
|
|
253
350
|
qaoa_values, qaoa_vectors, qaoa_uq_v, qaoa_uq_vec = run_qaoa_eigensolver(
|
|
254
351
|
test_matrix, 3
|
|
255
352
|
)
|
|
256
|
-
print(f"
|
|
257
|
-
print(f"
|
|
258
|
-
print(f"
|
|
353
|
+
print(f"Eigenvalue: {qaoa_values}")
|
|
354
|
+
print(f"Eigenvector: {qaoa_vectors}")
|
|
355
|
+
print(f"uq_values: {qaoa_uq_v}")
|
|
356
|
+
print(f"uq_vectors: {qaoa_uq_vec}")
|
|
357
|
+
|
|
259
358
|
try:
|
|
359
|
+
print("\n=== QAOA (noise) ===")
|
|
260
360
|
(
|
|
261
361
|
qaoa_noisy_values,
|
|
262
362
|
qaoa_noisy_vectors,
|
|
263
363
|
noisy_uq_v,
|
|
264
364
|
noisy_uq_vec,
|
|
265
365
|
) = run_qaoa_eigensolver(test_matrix, 3, use_noise=True)
|
|
266
|
-
print(f"
|
|
267
|
-
print(f"
|
|
268
|
-
print(f"
|
|
366
|
+
print(f"Eigenvalue: {qaoa_noisy_values}")
|
|
367
|
+
print(f"Eigenvector: {qaoa_noisy_vectors}")
|
|
368
|
+
print(f"uq_values: {noisy_uq_v}")
|
|
369
|
+
print(f"uq_vectors: {noisy_uq_vec}")
|
|
269
370
|
except ImportError as exc:
|
|
270
371
|
print(f"Skipping noisy QAOA demo: {exc}")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qharm-eigenbridge
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Quantum eigensolver implementation using Qiskit
|
|
5
|
+
Author-email: Jonathan Waldrop <jwaldrop@ameslab.gov>, Mahir Anand <mahiranand@outlook.com>
|
|
6
|
+
Classifier: Development Status :: 4 - Beta
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: pybind11
|
|
19
|
+
Requires-Dist: qiskit
|
|
20
|
+
Requires-Dist: qiskit-algorithms
|
|
21
|
+
Requires-Dist: qiskit-aer
|
|
22
|
+
Requires-Dist: qiskit-ibm-runtime
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
<!--
|
|
29
|
+
~ Copyright 2026 QHARM
|
|
30
|
+
~
|
|
31
|
+
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
32
|
+
~ you may not use this file except in compliance with the License.
|
|
33
|
+
~ You may obtain a copy of the License at
|
|
34
|
+
~
|
|
35
|
+
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
36
|
+
~
|
|
37
|
+
~ Unless required by applicable law or agreed to in writing, software
|
|
38
|
+
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
39
|
+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
40
|
+
~ See the License for the specific language governing permissions and
|
|
41
|
+
~ limitations under the License.
|
|
42
|
+
-->
|
|
43
|
+
|
|
44
|
+
# EigenBridge
|
|
45
|
+
|
|
46
|
+
There are two quantum solvers. VQD can return several eigenvalues; QAOA returns only the ground state.
|
|
47
|
+
|
|
48
|
+
VQD -> `run_vqd_eigensolver(matrix, k, use_noise=false)` -> lowest `k` eigenvalues (`k` defaults to all)
|
|
49
|
+
QAOA -> `run_qaoa_eigensolver(matrix, use_noise=false)` -> ground state only
|
|
50
|
+
|
|
51
|
+
**Uncertainties (`uq_values` / `uq_vectors`)**
|
|
52
|
+
- For every VQD/QAOA path (noiseless and noisy), at each final optimized circuit/state,
|
|
53
|
+
`uq_values[i] = sqrt(max(0, ⟨H²⟩ − ⟨H⟩²))`, where ⟨H⟩ is the reported eigenvalue and ⟨H²⟩
|
|
54
|
+
comes from the same estimator on `H @ H` at the same parameters.
|
|
55
|
+
This is the standard deviation of a single measurement of H in the prepared state
|
|
56
|
+
(intrinsic quantum / variational residual variance).
|
|
57
|
+
Exact eigenstates give σ ≈ 0; approximate ansatz states give nonzero σ even without noise.
|
|
58
|
+
- `uq_vectors` uses the same layout as the returned eigenvectors (column `i` = mode `i`).
|
|
59
|
+
For each physical basis index `j`,
|
|
60
|
+
`uq_vectors[j, i] = sqrt(p_j (1 − p_j))` where `p_j = ⟨|j⟩⟨j|⟩` is evaluated with the
|
|
61
|
+
**same estimator / circuit / parameters** as the energy (FakeManila when `use_noise=true`).
|
|
62
|
+
This is the std of a single-shot computational-basis occupation measurement for component `j`.
|
|
63
|
+
Caveat: it can be > 0 even for an exact delocalized eigenstate (unlike σ_H, which is 0 for
|
|
64
|
+
exact energy eigenstates). It is **not** “distance to the classical eigenvector.”
|
|
65
|
+
Unused eigenvector columns stay 0. Reported eigenvectors still come from the ideal
|
|
66
|
+
statevector of the optimized circuit; only the UQ uses the estimator/noise path.
|
|
67
|
+
- Non–power-of-2 dimensions pad to the next power of two and place
|
|
68
|
+
`penalty = max(1.0, Gershgorin row-sum bound)` on unused diagonal entries so pad
|
|
69
|
+
eigenvalues stay outside the original spectrum while avoiding the old 10× entry-scale
|
|
70
|
+
inflation of ⟨H²⟩ on small problems.
|
|
71
|
+
- Noisy VQD keeps exact statevector overlaps; only the energy / projector estimator uses
|
|
72
|
+
FakeManila noise (demo). Expect worse eigenvalues, especially for higher states.
|
|
73
|
+
|
|
74
|
+
Unit tests check that reported `uq_values` / filled `uq_vectors` entries are finite and
|
|
75
|
+
nonnegative (filled columns not identically zero on the demo matrix), and that on the
|
|
76
|
+
demo 3×3 matrix the ground-state interval `[⟨H⟩−σ, ⟨H⟩+σ]` contains the classical ground
|
|
77
|
+
eigenvalue for noisy paths. They do not require noisy eigenvalues to match the classical answer.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Copyright 2026 QHARM
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import unittest
|
|
16
|
+
|
|
17
|
+
import eigenbridge
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestEigensolvers(unittest.TestCase):
|
|
21
|
+
def setUp(self):
|
|
22
|
+
# Define a 3x3 matrix
|
|
23
|
+
self.flat_matrix = [3.0, 5.0, 2.0, 5.0, 1.0, 3.0, 2.0, 3.0, 2.0]
|
|
24
|
+
# Correct results
|
|
25
|
+
self.correct_eigenvalues = [
|
|
26
|
+
-3.3610452994996525,
|
|
27
|
+
0.5038738768058354,
|
|
28
|
+
8.8571714226938152,
|
|
29
|
+
]
|
|
30
|
+
self.correct_eigenvectors = [
|
|
31
|
+
-0.5518254419285664,
|
|
32
|
+
-0.5057445690691208,
|
|
33
|
+
-0.6631071651682192,
|
|
34
|
+
0.7984036023978691,
|
|
35
|
+
-0.0906811731252565,
|
|
36
|
+
-0.5952550818924042,
|
|
37
|
+
-0.2409156892326610,
|
|
38
|
+
0.8579040480716453,
|
|
39
|
+
-0.4538284642723896,
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
def _assert_finite_nonnegative(self, uq):
|
|
43
|
+
self.assertTrue(uq == uq and abs(uq) != float("inf"))
|
|
44
|
+
self.assertGreaterEqual(uq, 0.0)
|
|
45
|
+
|
|
46
|
+
def _assert_finite_positive(self, uq):
|
|
47
|
+
self._assert_finite_nonnegative(uq)
|
|
48
|
+
self.assertGreater(uq, 0.0)
|
|
49
|
+
|
|
50
|
+
def _check_uq_vectors(self, eigenvectors, uq_vectors, n=3, k_filled=None):
|
|
51
|
+
# uq_vectors: sqrt(p(1-p)) occupation stds; unused columns stay 0.
|
|
52
|
+
self.assertEqual(len(uq_vectors), len(eigenvectors))
|
|
53
|
+
self.assertEqual(len(uq_vectors), n * n)
|
|
54
|
+
for uq in uq_vectors:
|
|
55
|
+
self._assert_finite_nonnegative(uq)
|
|
56
|
+
if k_filled is None:
|
|
57
|
+
k_filled = n
|
|
58
|
+
for col in range(n):
|
|
59
|
+
col_uq = [uq_vectors[row * n + col] for row in range(n)]
|
|
60
|
+
if col < k_filled:
|
|
61
|
+
self.assertGreater(max(col_uq), 0.0)
|
|
62
|
+
else:
|
|
63
|
+
self.assertTrue(all(u == 0.0 for u in col_uq))
|
|
64
|
+
|
|
65
|
+
def _check_results(
|
|
66
|
+
self,
|
|
67
|
+
results,
|
|
68
|
+
val_places=14,
|
|
69
|
+
vec_places=14,
|
|
70
|
+
require_positive_uq=False,
|
|
71
|
+
k_filled=None,
|
|
72
|
+
):
|
|
73
|
+
# Check the results
|
|
74
|
+
eigenvalues, eigenvectors, uq_values, uq_vectors = results
|
|
75
|
+
for i in zip(eigenvalues, self.correct_eigenvalues):
|
|
76
|
+
self.assertAlmostEqual(i[0], i[1], places=val_places)
|
|
77
|
+
for i in zip(eigenvectors, self.correct_eigenvectors):
|
|
78
|
+
self.assertAlmostEqual(abs(i[0]), abs(i[1]), places=vec_places)
|
|
79
|
+
# Energy UQs for returned modes: >= 0, or > 0 if require_positive_uq.
|
|
80
|
+
n_returned = sum(1 for v in self.correct_eigenvalues if v != 0.0)
|
|
81
|
+
if n_returned == 0:
|
|
82
|
+
n_returned = len(eigenvalues)
|
|
83
|
+
for i, uq in enumerate(uq_values):
|
|
84
|
+
if i < n_returned:
|
|
85
|
+
if require_positive_uq:
|
|
86
|
+
self._assert_finite_positive(uq)
|
|
87
|
+
else:
|
|
88
|
+
self._assert_finite_nonnegative(uq)
|
|
89
|
+
else:
|
|
90
|
+
self.assertEqual(uq, 0.0)
|
|
91
|
+
if k_filled is None:
|
|
92
|
+
k_filled = n_returned
|
|
93
|
+
self._check_uq_vectors(
|
|
94
|
+
eigenvectors, uq_vectors, n=3, k_filled=k_filled
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def test_vqd_eigensolver(self):
|
|
98
|
+
results = eigenbridge.run_vqd_eigensolver(self.flat_matrix, n=3)
|
|
99
|
+
self._check_results(results, val_places=8, vec_places=4, k_filled=3)
|
|
100
|
+
eigenvalues, _, uq_values, _ = results
|
|
101
|
+
self.assertLessEqual(
|
|
102
|
+
eigenvalues[0] - uq_values[0], self.correct_eigenvalues[0]
|
|
103
|
+
)
|
|
104
|
+
self.assertLessEqual(
|
|
105
|
+
self.correct_eigenvalues[0], eigenvalues[0] + uq_values[0]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def test_vqd_eigensolver_noise(self):
|
|
109
|
+
# Does not require eigenvalues to match LAPACK, since Noisy VQD is a demo.
|
|
110
|
+
try:
|
|
111
|
+
(
|
|
112
|
+
eigenvalues,
|
|
113
|
+
eigenvectors,
|
|
114
|
+
uq_values,
|
|
115
|
+
uq_vectors,
|
|
116
|
+
) = eigenbridge.run_vqd_eigensolver(
|
|
117
|
+
self.flat_matrix, n=3, use_noise=True
|
|
118
|
+
)
|
|
119
|
+
except ImportError as exc:
|
|
120
|
+
self.skipTest(f"Noisy VQD deps missing: {exc}")
|
|
121
|
+
|
|
122
|
+
self.assertEqual(len(eigenvalues), 3)
|
|
123
|
+
self.assertEqual(len(uq_values), 3)
|
|
124
|
+
for uq in uq_values:
|
|
125
|
+
self._assert_finite_positive(uq)
|
|
126
|
+
self._check_uq_vectors(eigenvectors, uq_vectors, n=3, k_filled=3)
|
|
127
|
+
# Sanity check on this demo matrix only — not a universal guarantee.
|
|
128
|
+
self.assertLessEqual(
|
|
129
|
+
eigenvalues[0] - uq_values[0], self.correct_eigenvalues[0]
|
|
130
|
+
)
|
|
131
|
+
self.assertLessEqual(
|
|
132
|
+
self.correct_eigenvalues[0], eigenvalues[0] + uq_values[0]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def test_qaoa_eigensolver(self):
|
|
136
|
+
# Adjust the correct eigenvalues and eigenvectors for QAOA's output
|
|
137
|
+
# The QAOA solver will not return all eigenvalues/eigenvectors, so we
|
|
138
|
+
# zero out the ones that are not expected to be returned.
|
|
139
|
+
for i in range(len(self.correct_eigenvalues)):
|
|
140
|
+
if i % 3 != 0:
|
|
141
|
+
self.correct_eigenvalues[i] = 0.0
|
|
142
|
+
for i in range(len(self.correct_eigenvectors)):
|
|
143
|
+
if i % 3 != 0:
|
|
144
|
+
self.correct_eigenvectors[i] = 0.0
|
|
145
|
+
results = eigenbridge.run_qaoa_eigensolver(
|
|
146
|
+
self.flat_matrix, n=3, use_noise=False
|
|
147
|
+
)
|
|
148
|
+
# New gershgorin padding changes the QAOA cost Hamiltonian / landscape slightly
|
|
149
|
+
self._check_results(
|
|
150
|
+
results,
|
|
151
|
+
val_places=0,
|
|
152
|
+
vec_places=0,
|
|
153
|
+
require_positive_uq=True,
|
|
154
|
+
k_filled=1,
|
|
155
|
+
)
|
|
156
|
+
eigenvalues, _, uq_values, _ = results
|
|
157
|
+
self.assertLessEqual(
|
|
158
|
+
eigenvalues[0] - uq_values[0], self.correct_eigenvalues[0]
|
|
159
|
+
)
|
|
160
|
+
self.assertLessEqual(
|
|
161
|
+
self.correct_eigenvalues[0], eigenvalues[0] + uq_values[0]
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def test_qaoa_eigensolver_noise(self):
|
|
165
|
+
try:
|
|
166
|
+
(
|
|
167
|
+
eigenvalues,
|
|
168
|
+
eigenvectors,
|
|
169
|
+
uq_values,
|
|
170
|
+
uq_vectors,
|
|
171
|
+
) = eigenbridge.run_qaoa_eigensolver(
|
|
172
|
+
self.flat_matrix, n=3, use_noise=True
|
|
173
|
+
)
|
|
174
|
+
except ImportError as exc:
|
|
175
|
+
self.skipTest(f"Noisy QAOA deps missing: {exc}")
|
|
176
|
+
|
|
177
|
+
self._assert_finite_positive(uq_values[0])
|
|
178
|
+
for uq in uq_values[1:]:
|
|
179
|
+
self.assertEqual(uq, 0.0)
|
|
180
|
+
self._check_uq_vectors(eigenvectors, uq_vectors, n=3, k_filled=1)
|
|
181
|
+
# Sanity check on this demo matrix only, not a universal guarantee.
|
|
182
|
+
self.assertLessEqual(
|
|
183
|
+
eigenvalues[0] - uq_values[0], self.correct_eigenvalues[0]
|
|
184
|
+
)
|
|
185
|
+
self.assertLessEqual(
|
|
186
|
+
self.correct_eigenvalues[0], eigenvalues[0] + uq_values[0]
|
|
187
|
+
)
|
qharm_eigenbridge-0.0.2/PKG-INFO
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: qharm-eigenbridge
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Summary: Quantum eigensolver implementation using Qiskit
|
|
5
|
-
Author-email: Jonathan Waldrop <jwaldrop@ameslab.gov>, Mahir Anand <mahiranand@outlook.com>
|
|
6
|
-
Classifier: Development Status :: 4 - Beta
|
|
7
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
-
Requires-Python: >=3.9
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE
|
|
17
|
-
Requires-Dist: numpy
|
|
18
|
-
Requires-Dist: pybind11
|
|
19
|
-
Requires-Dist: qiskit
|
|
20
|
-
Requires-Dist: qiskit-algorithms
|
|
21
|
-
Requires-Dist: qiskit-aer
|
|
22
|
-
Requires-Dist: qiskit-ibm-runtime
|
|
23
|
-
Provides-Extra: dev
|
|
24
|
-
Requires-Dist: pre-commit; extra == "dev"
|
|
25
|
-
Requires-Dist: pytest; extra == "dev"
|
|
26
|
-
Dynamic: license-file
|
|
27
|
-
|
|
28
|
-
<!--
|
|
29
|
-
~ Copyright 2026 QHARM
|
|
30
|
-
~
|
|
31
|
-
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
32
|
-
~ you may not use this file except in compliance with the License.
|
|
33
|
-
~ You may obtain a copy of the License at
|
|
34
|
-
~
|
|
35
|
-
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
36
|
-
~
|
|
37
|
-
~ Unless required by applicable law or agreed to in writing, software
|
|
38
|
-
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
39
|
-
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
40
|
-
~ See the License for the specific language governing permissions and
|
|
41
|
-
~ limitations under the License.
|
|
42
|
-
-->
|
|
43
|
-
|
|
44
|
-
# EigenBridge
|
|
45
|
-
|
|
46
|
-
There are two quantum solvers. VQD can return several eigenvalues; QAOA returns only the ground state.
|
|
47
|
-
|
|
48
|
-
VQD -> `run_vqd_eigensolver(matrix, k)` -> `solve_vqd` -> lowest `k` eigenvalues (`k` defaults to all)
|
|
49
|
-
QAOA -> `run_qaoa_eigensolver(matrix, use_noise=false)` -> `solve_qaoa` -> ground state only
|
|
50
|
-
|
|
51
|
-
**Uncertainties (`uq_values` / `uq_vectors`)**
|
|
52
|
-
- Noiseless solvers (LAPACK, VQD, noiseless QAOA): `uq_* = 0`
|
|
53
|
-
- Noisy QAOA (`use_noise=true`): at the final circuit, `uq_values[0] = |E_noisy − E_exact|`; Unused slots and `uq_vectors` stay 0
|
|
54
|
-
|
|
55
|
-
The noisy QAOA unit test checks that `uq_values[0] = |E_noisy − E_exact|` (at the final circuit) and `|λ_noisy − λ_LAPACK|` agree within a factor of 10 (ratio < 10).
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
~ Copyright 2026 QHARM
|
|
3
|
-
~
|
|
4
|
-
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
~ you may not use this file except in compliance with the License.
|
|
6
|
-
~ You may obtain a copy of the License at
|
|
7
|
-
~
|
|
8
|
-
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
~
|
|
10
|
-
~ Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
~ See the License for the specific language governing permissions and
|
|
14
|
-
~ limitations under the License.
|
|
15
|
-
-->
|
|
16
|
-
|
|
17
|
-
# EigenBridge
|
|
18
|
-
|
|
19
|
-
There are two quantum solvers. VQD can return several eigenvalues; QAOA returns only the ground state.
|
|
20
|
-
|
|
21
|
-
VQD -> `run_vqd_eigensolver(matrix, k)` -> `solve_vqd` -> lowest `k` eigenvalues (`k` defaults to all)
|
|
22
|
-
QAOA -> `run_qaoa_eigensolver(matrix, use_noise=false)` -> `solve_qaoa` -> ground state only
|
|
23
|
-
|
|
24
|
-
**Uncertainties (`uq_values` / `uq_vectors`)**
|
|
25
|
-
- Noiseless solvers (LAPACK, VQD, noiseless QAOA): `uq_* = 0`
|
|
26
|
-
- Noisy QAOA (`use_noise=true`): at the final circuit, `uq_values[0] = |E_noisy − E_exact|`; Unused slots and `uq_vectors` stay 0
|
|
27
|
-
|
|
28
|
-
The noisy QAOA unit test checks that `uq_values[0] = |E_noisy − E_exact|` (at the final circuit) and `|λ_noisy − λ_LAPACK|` agree within a factor of 10 (ratio < 10).
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: qharm-eigenbridge
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Summary: Quantum eigensolver implementation using Qiskit
|
|
5
|
-
Author-email: Jonathan Waldrop <jwaldrop@ameslab.gov>, Mahir Anand <mahiranand@outlook.com>
|
|
6
|
-
Classifier: Development Status :: 4 - Beta
|
|
7
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
-
Requires-Python: >=3.9
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE
|
|
17
|
-
Requires-Dist: numpy
|
|
18
|
-
Requires-Dist: pybind11
|
|
19
|
-
Requires-Dist: qiskit
|
|
20
|
-
Requires-Dist: qiskit-algorithms
|
|
21
|
-
Requires-Dist: qiskit-aer
|
|
22
|
-
Requires-Dist: qiskit-ibm-runtime
|
|
23
|
-
Provides-Extra: dev
|
|
24
|
-
Requires-Dist: pre-commit; extra == "dev"
|
|
25
|
-
Requires-Dist: pytest; extra == "dev"
|
|
26
|
-
Dynamic: license-file
|
|
27
|
-
|
|
28
|
-
<!--
|
|
29
|
-
~ Copyright 2026 QHARM
|
|
30
|
-
~
|
|
31
|
-
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
32
|
-
~ you may not use this file except in compliance with the License.
|
|
33
|
-
~ You may obtain a copy of the License at
|
|
34
|
-
~
|
|
35
|
-
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
36
|
-
~
|
|
37
|
-
~ Unless required by applicable law or agreed to in writing, software
|
|
38
|
-
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
39
|
-
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
40
|
-
~ See the License for the specific language governing permissions and
|
|
41
|
-
~ limitations under the License.
|
|
42
|
-
-->
|
|
43
|
-
|
|
44
|
-
# EigenBridge
|
|
45
|
-
|
|
46
|
-
There are two quantum solvers. VQD can return several eigenvalues; QAOA returns only the ground state.
|
|
47
|
-
|
|
48
|
-
VQD -> `run_vqd_eigensolver(matrix, k)` -> `solve_vqd` -> lowest `k` eigenvalues (`k` defaults to all)
|
|
49
|
-
QAOA -> `run_qaoa_eigensolver(matrix, use_noise=false)` -> `solve_qaoa` -> ground state only
|
|
50
|
-
|
|
51
|
-
**Uncertainties (`uq_values` / `uq_vectors`)**
|
|
52
|
-
- Noiseless solvers (LAPACK, VQD, noiseless QAOA): `uq_* = 0`
|
|
53
|
-
- Noisy QAOA (`use_noise=true`): at the final circuit, `uq_values[0] = |E_noisy − E_exact|`; Unused slots and `uq_vectors` stay 0
|
|
54
|
-
|
|
55
|
-
The noisy QAOA unit test checks that `uq_values[0] = |E_noisy − E_exact|` (at the final circuit) and `|λ_noisy − λ_LAPACK|` agree within a factor of 10 (ratio < 10).
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# Copyright 2026 QHARM
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
import unittest
|
|
16
|
-
|
|
17
|
-
import eigenbridge
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class TestEigensolvers(unittest.TestCase):
|
|
21
|
-
def setUp(self):
|
|
22
|
-
# Define a 3x3 matrix
|
|
23
|
-
self.flat_matrix = [3.0, 5.0, 2.0, 5.0, 1.0, 3.0, 2.0, 3.0, 2.0]
|
|
24
|
-
# Correct results
|
|
25
|
-
self.correct_eigenvalues = [
|
|
26
|
-
-3.3610452994996525,
|
|
27
|
-
0.5038738768058354,
|
|
28
|
-
8.8571714226938152,
|
|
29
|
-
]
|
|
30
|
-
self.correct_eigenvectors = [
|
|
31
|
-
-0.5518254419285664,
|
|
32
|
-
-0.5057445690691208,
|
|
33
|
-
-0.6631071651682192,
|
|
34
|
-
0.7984036023978691,
|
|
35
|
-
-0.0906811731252565,
|
|
36
|
-
-0.5952550818924042,
|
|
37
|
-
-0.2409156892326610,
|
|
38
|
-
0.8579040480716453,
|
|
39
|
-
-0.4538284642723896,
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
def _check_results(
|
|
43
|
-
self,
|
|
44
|
-
results,
|
|
45
|
-
val_places=14,
|
|
46
|
-
vec_places=14,
|
|
47
|
-
uq_val=1e-16,
|
|
48
|
-
uq_vec=1e-16,
|
|
49
|
-
):
|
|
50
|
-
# Check the results
|
|
51
|
-
eigenvalues, eigenvectors, uq_values, uq_vectors = results
|
|
52
|
-
for i in zip(eigenvalues, self.correct_eigenvalues):
|
|
53
|
-
self.assertAlmostEqual(i[0], i[1], places=val_places)
|
|
54
|
-
for i in zip(eigenvectors, self.correct_eigenvectors):
|
|
55
|
-
self.assertAlmostEqual(abs(i[0]), abs(i[1]), places=vec_places)
|
|
56
|
-
for i in uq_values:
|
|
57
|
-
self.assertEqual(i, uq_val)
|
|
58
|
-
for i in uq_vectors:
|
|
59
|
-
self.assertEqual(i, uq_vec)
|
|
60
|
-
|
|
61
|
-
def test_vqd_eigensolver(self):
|
|
62
|
-
results = eigenbridge.run_vqd_eigensolver(self.flat_matrix, n=3)
|
|
63
|
-
self._check_results(
|
|
64
|
-
results, val_places=8, vec_places=4, uq_val=0.0, uq_vec=0.0
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
def test_qaoa_eigensolver(self):
|
|
68
|
-
# Adjust the correct eigenvalues and eigenvectors for QAOA's output
|
|
69
|
-
# The QAOA solver will not return all eigenvalues/eigenvectors, so we
|
|
70
|
-
# zero out the ones that are not expected to be returned.
|
|
71
|
-
for i in range(len(self.correct_eigenvalues)):
|
|
72
|
-
if i % 3 != 0:
|
|
73
|
-
self.correct_eigenvalues[i] = 0.0
|
|
74
|
-
for i in range(len(self.correct_eigenvectors)):
|
|
75
|
-
if i % 3 != 0:
|
|
76
|
-
self.correct_eigenvectors[i] = 0.0
|
|
77
|
-
results = eigenbridge.run_qaoa_eigensolver(
|
|
78
|
-
self.flat_matrix, n=3, use_noise=False
|
|
79
|
-
)
|
|
80
|
-
self._check_results(
|
|
81
|
-
results, val_places=8, vec_places=4, uq_val=0.0, uq_vec=0.0
|
|
82
|
-
)
|
|
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
|
{qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/qharm_eigenbridge.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/qharm_eigenbridge.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
{qharm_eigenbridge-0.0.2 → qharm_eigenbridge-0.0.3}/python/qharm_eigenbridge.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|