Skip to content

Commit 0092a84

Browse files
thuytr18nylser
authored andcommitted
feat: calculate fidelity (#28)
* added TODO and a method for calculating the fidelity * feat: added library to calculate ground state * added hamiltonian and circuit to OptimizationResult * added ground state and fidelity flag to Job class * hamiltonian, circuit is None for FCI * hamiltonian, circuit is returned for spa * fidelity method to calculate fidelity between states * add parameter for fidelity in generate jobs * ground state method * updated execute job for calculating optimized ground state * added parameter ground state and calculate fidelity to job * changed create result to get extra column for fidelity * changed create result to get extra column for fidelity * added fidelity as argument * Changed ground state Hamiltonian to hardcore boson hamiltonian * saved normal circuit * changed ground state hamiltonian * used Circuit to calculate optimized state * deleted Hamiltonian * deleted Hamiltonian * add test and deleted unnecessary code * add molecule parameter to OptimizationResult * deleted ground_state parameter in Job * OptimizationResult returns molecule too * made get_ground_state a class method * deleted ground_state parameter from Job * added molecule from optimization result and some comments * feat: optimize fidelity code for degenerated states * chore: organize imports * added warning if num_of_atoms greater than 8 * chore: optimized code and deleted commits * chore: fix formatting * chore: changed warning
1 parent 9280605 commit 0092a84

File tree

1 file changed

+96
-7
lines changed

1 file changed

+96
-7
lines changed

quanti_gin/data_generator.py

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22
import argparse
33
import importlib
44
import logging
5-
import sys
65
import multiprocessing
6+
import sys
7+
import warnings
78
from dataclasses import dataclass, field
89
from pathlib import Path
910
from random import Random
10-
from typing import Callable, Sequence, TypedDict
11+
from typing import Any, Callable, Sequence, TypedDict
1112

1213
import numpy as np
14+
import openfermion as of
1315
import pandas as pd
16+
import scipy
1417
import tequila as tq
1518
from numpy import eye, floating
1619
from numpy.typing import NDArray
@@ -40,6 +43,8 @@ class OptimizationResult(TypedDict):
4043
orbital_coefficients: NDArray | None
4144
orbital_transformation: NDArray | None
4245
variables: dict | None
46+
circuit: Any | None
47+
molecule: Any | None
4348
custom_data: Sequence[CustomData] | None
4449

4550

@@ -55,6 +60,9 @@ class Job:
5560
# custom optimization algorithm
5661
custom_job_data: Sequence[CustomData] = field(default_factory=list)
5762

63+
# Flag to determine whether fidelity should be calculated
64+
calculate_fidelity: bool = False
65+
5866
# custom arguments that one might want to pass to the optimization execution
5967
kwargs: dict = field(default_factory=dict)
6068

@@ -134,6 +142,7 @@ def run_fci_optimization(cls, molecule: QuantumChemistryBase, *args, **kwargs):
134142
orbital_transformation=None,
135143
variables=None,
136144
custom_data=None,
145+
circuit=None,
137146
)
138147

139148
@classmethod
@@ -145,7 +154,7 @@ def run_spa_optimization(
145154
edges=edges, vertices=coordinates
146155
).T
147156

148-
U = molecule.make_ansatz(name="HCB-SPA", edges=edges)
157+
U = molecule.make_ansatz(name="HCB-SPA", edges=edges) # Hardcore Boson Circuit
149158

150159
opt = tq.chemistry.optimize_orbitals(
151160
molecule.use_native_orbitals(),
@@ -160,14 +169,40 @@ def run_spa_optimization(
160169

161170
E = tq.ExpectationValue(H=H, U=U)
162171
result = tq.minimize(E, silent=True)
172+
173+
U_x = mol.hcb_to_me(U)
174+
163175
return OptimizationResult(
164176
energy=result.energy,
165177
orbital_coefficients=opt.molecule.integral_manager.orbital_coefficients,
166178
orbital_transformation=opt.mo_coeff,
167179
variables=result.variables,
180+
circuit=U_x,
181+
molecule=mol,
168182
custom_data=None,
169183
)
170184

185+
@classmethod
186+
def get_ground_states(cls, molecule, num_states=10):
187+
188+
H = molecule.make_hamiltonian().to_openfermion()
189+
H_sparse = of.linalg.get_sparse_operator(H)
190+
v, vv = scipy.sparse.linalg.eigsh(
191+
H_sparse, k=num_states, ncv=100, sigma=molecule.compute_energy("fci")
192+
)
193+
194+
wfns = [tq.QubitWaveFunction.from_array(vv[:, i]) for i in range(num_states)]
195+
energies = v.tolist()
196+
197+
return wfns, energies
198+
199+
@classmethod
200+
def calculate_fidelity(cls, true_state, optimized_state):
201+
inner_product = true_state.inner(optimized_state)
202+
fidelity = abs(inner_product) ** 2
203+
204+
return fidelity
205+
171206
@classmethod
172207
def generate_jobs(
173208
cls,
@@ -177,6 +212,7 @@ def generate_jobs(
177212
method="spa",
178213
custom_method=None,
179214
compare_to=[],
215+
calculate_fidelity=False, # parameter for fidelity between methods
180216
):
181217
def get_algorithm_from_method(method) -> Callable:
182218
if callable(method):
@@ -202,13 +238,16 @@ def get_algorithm_from_method(method) -> Callable:
202238

203239
custom_job_data = []
204240

241+
fidelity_flag = calculate_fidelity and method != "fci"
242+
205243
if custom_method:
206244
job = Job(
207245
id=i,
208246
geometry=geometry,
209247
coordinates=coordinates,
210248
optimization_algorithm=custom_method,
211249
custom_job_data=custom_job_data,
250+
calculate_fidelity=fidelity_flag,
212251
)
213252
jobs.append(job)
214253
else:
@@ -218,6 +257,7 @@ def get_algorithm_from_method(method) -> Callable:
218257
coordinates=coordinates,
219258
optimization_algorithm=get_algorithm_from_method(method),
220259
custom_job_data=custom_job_data,
260+
calculate_fidelity=fidelity_flag,
221261
)
222262
jobs.append(job)
223263

@@ -226,12 +266,14 @@ def get_algorithm_from_method(method) -> Callable:
226266
# do not duplicate methods
227267
if compare == method and not custom_method:
228268
continue
269+
fidelity_flag = calculate_fidelity and compare != "fci"
229270
job = Job(
230271
id=i,
231272
geometry=geometry,
232273
coordinates=coordinates,
233274
optimization_algorithm=get_algorithm_from_method(compare),
234275
custom_job_data=custom_job_data,
276+
calculate_fidelity=fidelity_flag,
235277
)
236278
jobs.append(job)
237279

@@ -240,13 +282,40 @@ def get_algorithm_from_method(method) -> Callable:
240282
@classmethod
241283
def execute_job(cls, job: Job, basis_set="sto-3g"):
242284
mol = tq.Molecule(geometry=job.geometry, basis_set=basis_set)
243-
return job.optimization_algorithm(
285+
286+
result = job.optimization_algorithm(
244287
mol, coordinates=job.coordinates, **job.kwargs
245288
)
246289

290+
if job.calculate_fidelity:
291+
if "circuit" in result:
292+
mol = result["molecule"] # use same molecule as in the optimization
293+
optimized_variables = result["variables"]
294+
U = result["circuit"]
295+
296+
state = tq.simulate(U, optimized_variables)
297+
298+
wfns, energies = cls.get_ground_states(molecule=mol)
299+
fidelity = 0.0
300+
k = 0
301+
302+
while k < len(wfns) - 1:
303+
fidelity += cls.calculate_fidelity(state, wfns[k])
304+
if fidelity < 1e-6:
305+
k += 1
306+
continue
307+
if abs(energies[k] - energies[k + 1]) < 1e-6:
308+
break
309+
310+
k += 1
311+
312+
return {"result": result, "fidelity": fidelity}
313+
else:
314+
return {"result": result, "fidelity": None}
315+
247316
@classmethod
248317
def create_result_df(
249-
cls, jobs: Sequence[Job], job_results: Sequence[OptimizationResult]
318+
cls, jobs: Sequence[Job], job_results: Sequence[dict[str, Any]]
250319
) -> pd.DataFrame:
251320
max_variable_count = max(
252321
[0]
@@ -263,15 +332,19 @@ def create_result_df(
263332
f"{job.optimization_algorithm.__module__}.{job.optimization_algorithm.__name__}"
264333
for job in jobs
265334
],
266-
"optimized_energy": [result["energy"] for result in job_results],
335+
"optimized_energy": [
336+
entry["result"]["energy"] for entry in job_results
337+
],
338+
"fidelity": [entry["fidelity"] for entry in job_results],
267339
"optimized_variable_count": max_variable_count,
268340
"atom_count": [len(job.coordinates) for job in jobs],
269341
"edge_count": [len(job.coordinates) // 2 for job in jobs],
270342
}
271343
)
272344

273345
# apply custom data to data frame
274-
for i, result in enumerate(job_results):
346+
for i, entry in enumerate(job_results):
347+
result = entry["result"]
275348
if "custom_data" not in result:
276349
continue
277350
custom_data = result["custom_data"]
@@ -423,6 +496,14 @@ def main():
423496
help="parameter for the job generator: influences the distance between atoms",
424497
)
425498

499+
parser.add_argument(
500+
"--fidelity",
501+
"-F",
502+
action="store_true",
503+
default=False,
504+
help="calculate the fidelity between the true ground state and the optimized state",
505+
)
506+
426507
parser.add_argument(
427508
"-v",
428509
"--verbose",
@@ -448,13 +529,21 @@ def main():
448529

449530
job_generator = DataGenerator.generate_jobs
450531

532+
if number_of_atoms >= 10 and args.fidelity == True:
533+
warnings.warn(
534+
"The calculations may be very slow due to the high number of atoms, and enabled fidelity calculation .",
535+
UserWarning,
536+
2,
537+
)
538+
451539
jobs = job_generator(
452540
number_of_atoms=number_of_atoms,
453541
number_of_jobs=number_of_jobs,
454542
size=args.size,
455543
method=args.method,
456544
custom_method=opt_method,
457545
compare_to=args.compare_to,
546+
calculate_fidelity=args.fidelity,
458547
)
459548

460549
job_results = []

0 commit comments

Comments
 (0)