Skip to content

Commit

Permalink
Ilc ansatz (#132)
Browse files Browse the repository at this point in the history
* Add ILC ansatz class
* updates to qmf, qcc, and ilc ansatz classes
* enable QMF and ILC classes to read-in and process data from OpenFermion *.hdf5 files and other small fixes.

Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com>
  • Loading branch information
MPCoons and ValentinS4t1qbit authored May 11, 2022
1 parent d8eda02 commit bf24c41
Show file tree
Hide file tree
Showing 12 changed files with 878 additions and 172 deletions.
54 changes: 46 additions & 8 deletions tangelo/algorithms/variational/tests/test_vqe_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
from tangelo.algorithms import BuiltInAnsatze, VQESolver
from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_cation_sto3g, mol_NaH_sto3g, mol_H4_sto3g_symm
from tangelo.toolboxes.ansatz_generator.uccsd import UCCSD
from tangelo.toolboxes.ansatz_generator.qmf import QMF
from tangelo.toolboxes.ansatz_generator.qcc import QCC
from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping
from tangelo.toolboxes.molecular_computation.rdms import matricize_2rdm
from tangelo.toolboxes.optimizers.rotosolve import rotosolve
Expand Down Expand Up @@ -119,7 +117,7 @@ def test_simulate_qmf_h2(self):
"""

vqe_options = {"molecule": mol_H2_sto3g, "ansatz": BuiltInAnsatze.QMF, "qubit_mapping": "jw",
"verbose": True}
"verbose": False}
vqe_solver = VQESolver(vqe_options)
vqe_solver.build()

Expand All @@ -131,7 +129,20 @@ def test_simulate_qcc_h2(self):
parameters, exact simulator.
"""
vqe_options = {"molecule": mol_H2_sto3g, "ansatz": BuiltInAnsatze.QCC, "qubit_mapping": "jw",
"verbose": True}
"verbose": False}
vqe_solver = VQESolver(vqe_options)
vqe_solver.build()

energy = vqe_solver.simulate()
self.assertAlmostEqual(energy, -1.137270, delta=1e-4)

def test_simulate_ilc_h2(self):
"""Run VQE on H2 molecule, with ILC ansatz, JW qubit mapping, initial
parameters, exact simulator.
"""

vqe_options = {"molecule": mol_H2_sto3g, "ansatz": BuiltInAnsatze.ILC, "qubit_mapping": "jw",
"verbose": False}
vqe_solver = VQESolver(vqe_options)
vqe_solver.build()

Expand Down Expand Up @@ -209,7 +220,7 @@ def test_simulate_qmf_h4(self):
"""

vqe_options = {"molecule": mol_H4_sto3g, "ansatz": BuiltInAnsatze.QMF, "qubit_mapping": "jw",
"verbose": True}
"verbose": False}
vqe_solver = VQESolver(vqe_options)
vqe_solver.build()

Expand All @@ -220,14 +231,28 @@ def test_simulate_qcc_h4(self):
"""Run VQE on H4 molecule, with QCC ansatz, JW qubit mapping, initial
parameters, exact simulator.
"""

vqe_options = {"molecule": mol_H4_sto3g, "ansatz": BuiltInAnsatze.QCC, "qubit_mapping": "jw",
"verbose": True}
"verbose": False}
vqe_solver = VQESolver(vqe_options)
vqe_solver.build()

energy = vqe_solver.simulate()
self.assertAlmostEqual(energy, -1.963270, delta=1e-4)

def test_simulate_ilc_h4(self):
"""Run VQE on H4 molecule, with ILC ansatz, JW qubit mapping, initial
parameters, exact simulator.
"""

vqe_options = {"molecule": mol_H4_sto3g, "ansatz": BuiltInAnsatze.ILC, "qubit_mapping": "jw",
"verbose": False}
vqe_solver = VQESolver(vqe_options)
vqe_solver.build()

energy = vqe_solver.simulate()
self.assertAlmostEqual(energy, -1.960877, delta=1e-4)

def test_simulate_h4_open(self):
"""Run VQE on H4 molecule, with UCCSD ansatz, JW qubit mapping, initial parameters, exact simulator """
vqe_options = {"molecule": mol_H4_cation_sto3g, "ansatz": BuiltInAnsatze.UCCSD, "qubit_mapping": "jw",
Expand All @@ -244,7 +269,7 @@ def test_simulate_qmf_h4_open(self):
"""

vqe_options = {"molecule": mol_H4_cation_sto3g, "ansatz": BuiltInAnsatze.QMF, "qubit_mapping": "jw",
"verbose": True}
"verbose": False}
vqe_solver = VQESolver(vqe_options)
vqe_solver.build()

Expand All @@ -257,7 +282,20 @@ def test_simulate_qcc_h4_open(self):
"""

vqe_options = {"molecule": mol_H4_cation_sto3g, "ansatz": BuiltInAnsatze.QCC, "qubit_mapping": "jw",
"verbose": True}
"verbose": False}
vqe_solver = VQESolver(vqe_options)
vqe_solver.build()

energy = vqe_solver.simulate()
self.assertAlmostEqual(energy, -1.638020, delta=1e-4)

def test_simulate_ilc_h4_open(self):
"""Run VQE on H4 + molecule, with ILC ansatz, JW qubit mapping, initial
parameters, exact simulator.
"""

vqe_options = {"molecule": mol_H4_cation_sto3g, "ansatz": BuiltInAnsatze.ILC, "qubit_mapping": "jw",
"verbose": False}
vqe_solver = VQESolver(vqe_options)
vqe_solver.build()

Expand Down
14 changes: 9 additions & 5 deletions tangelo/algorithms/variational/vqe_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
from tangelo.toolboxes.operators import count_qubits, FermionOperator, qubitop_to_qubitham
from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping
from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz
from tangelo.toolboxes.ansatz_generator import UCCSD, RUCC, HEA, UpCCGSD, QMF, QCC, VSQS, UCCGD, VariationalCircuitAnsatz
from tangelo.toolboxes.ansatz_generator import UCCSD, RUCC, HEA, UpCCGSD, QMF, QCC, VSQS, UCCGD, ILC,\
VariationalCircuitAnsatz
from tangelo.toolboxes.ansatz_generator.penalty_terms import combined_penalty
from tangelo.toolboxes.post_processing.bootstrapping import get_resampled_frequencies
from tangelo.toolboxes.ansatz_generator.fermionic_operators import number_operator, spinz_operator, spin2_operator
Expand All @@ -47,6 +48,7 @@ class BuiltInAnsatze(Enum):
QCC = 6
VSQS = 7
UCCGD = 8
ILC = 9


class VQESolver:
Expand Down Expand Up @@ -112,11 +114,11 @@ def __init__(self, opt_dict):
if not (bool(self.molecule) ^ bool(self.qubit_hamiltonian)):
raise ValueError(f"A molecule OR qubit Hamiltonian object must be provided when instantiating {self.__class__.__name__}.")

# The QCC ansatz requires up_then_down=True when mapping="jw"
# The QCC & ILC ansatze require up_then_down=True when mapping="jw"
if isinstance(self.ansatz, BuiltInAnsatze):
if self.ansatz == BuiltInAnsatze.QCC and self.qubit_mapping.lower() == "jw" and not self.up_then_down:
warnings.warn("The QCC ansatz requires spin-orbital ordering to be all spin-up "
"first followed by all spin-down for the JW mapping.", RuntimeWarning)
if self.ansatz in (BuiltInAnsatze.QCC, BuiltInAnsatze.ILC) and self.qubit_mapping.lower() == "jw" and not self.up_then_down:
warnings.warn("Efficient generator screening for QCC-based ansatze requires spin-orbital ordering to be "
"all spin-up first followed by all spin-down for the JW mapping.", RuntimeWarning)
self.up_then_down = True

self.default_backend_options = default_backend_options
Expand Down Expand Up @@ -193,6 +195,8 @@ def build(self):
self.ansatz = VSQS(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif self.ansatz == BuiltInAnsatze.UCCGD:
self.ansatz = UCCGD(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif self.ansatz == BuiltInAnsatze.ILC:
self.ansatz = ILC(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
else:
raise ValueError(f"Unsupported ansatz. Built-in ansatze:\n\t{self.builtin_ansatze}")
elif not isinstance(self.ansatz, Ansatz):
Expand Down
1 change: 1 addition & 0 deletions tangelo/toolboxes/ansatz_generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

from .vsqs import VSQS
from .ilc import ILC
from .qcc import QCC
from .qmf import QMF
from .uccsd import UCCSD
Expand Down
43 changes: 18 additions & 25 deletions tangelo/toolboxes/ansatz_generator/_qubit_cc.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
from itertools import combinations

from tangelo.toolboxes.operators.operators import QubitOperator
from ._qubit_mf import get_op_expval
from tangelo.toolboxes.ansatz_generator._qubit_mf import get_op_expval


def construct_dis(pure_var_params, qubit_ham, qcc_deriv_thresh, verbose=False):
def construct_dis(qubit_ham, pure_var_params, deqcc_dtau_thresh):
"""Construct the DIS of QCC generators, which proceeds as follows:
1. Identify the flip indices of all Hamiltonian terms and group terms by flip indices.
2. Construct a representative generator using flip indices from each candidate DIS group
Expand All @@ -47,43 +47,36 @@ def construct_dis(pure_var_params, qubit_ham, qcc_deriv_thresh, verbose=False):
odd number of Y operators.
Args:
pure_var_params (numpy array of float): A purified QMF variational parameter set.
qubit_ham (QubitOperator): A qubit Hamiltonian.
qcc_deriv_thresh (float): Threshold value of |dEQCC/dtau| so that if |dEQCC/dtau| >=
qcc_deriv_thresh for a generator, add its candidate group to the DIS.
verbose (bool): Flag for QCC verbosity.
pure_var_params (numpy array of float): A purified QMF variational parameter set.
deqcc_dtau_thresh (float): Threshold for |dEQCC/dtau| so that a candidate group is added
to the DIS if |dEQCC/dtau| >= deqcc_dtau_thresh for a generator.
Returns:
list of list: the DIS of QCC generators.
"""

# Use a qubit Hamiltonian and purified QMF parameter set to construct the DIS
dis, dis_groups = [], get_dis_groups(pure_var_params, qubit_ham, qcc_deriv_thresh)
dis, dis_groups = [], get_dis_groups(qubit_ham, pure_var_params, deqcc_dtau_thresh)
if dis_groups:
if verbose:
print(f"The DIS contains {len(dis_groups)} unique generator group(s).\n")
for i, dis_group in enumerate(dis_groups):
for dis_group in dis_groups:
dis_group_idxs = [int(idxs) for idxs in dis_group[0].split(" ")]
dis_group_gens = get_gens_from_idxs(dis_group_idxs)
dis.append(dis_group_gens)
if verbose:
print(f"DIS group {i} | group size = {len(dis_group_gens)} | "
f"flip indices = {dis_group_idxs} | |dEQCC/dtau| = "
f"{abs(dis_group[1])} a.u.\n")
else:
raise ValueError(f"The DIS is empty: there are no candidate DIS groups where "
f"|dEQCC/dtau| >= {qcc_deriv_thresh} a.u. Terminate the QCC simulation.\n")
f"|dEQCC/dtau| >= {deqcc_dtau_thresh} a.u. Terminate simulation.\n")
return dis


def get_dis_groups(pure_var_params, qubit_ham, qcc_deriv_thresh):
def get_dis_groups(qubit_ham, pure_var_params, deqcc_dtau_thresh):
"""Construct unique DIS groups characterized by the flip indices and |dEQCC/dtau|.
Args:
pure_var_params (numpy array of float): A purified QMF variational parameter set.
qubit_ham (QubitOperator): A qubit Hamiltonian.
qcc_deriv_thresh (float): Threshold value of |dEQCC/dtau| so that if |dEQCC/dtau| >=
qcc_deriv_thresh for a generator, add its candidate group to the DIS.
pure_var_params (numpy array of float): A purified QMF variational parameter set.
deqcc_dtau_thresh (float): Threshold for |dEQCC/dtau| so that a candidate group is added
to the DIS if |dEQCC/dtau| >= deqcc_dtau_thresh for a generator.
Returns:
list of tuple: the DIS group flip indices (str) and signed value of dEQCC/dtau (float).
Expand All @@ -94,15 +87,15 @@ def get_dis_groups(pure_var_params, qubit_ham, qcc_deriv_thresh):
for qham_items in qubit_ham.terms.items())
flip_idxs = list(filter(None, (get_idxs_deriv(q_gen[0], *q_gen[1]) for q_gen in qham_gen)))

# Group Hamiltonian terms with the same flip indices and sum signed dEQCC/tau values
# Group Hamiltonian terms with the same flip indices and sum of the signed dEQCC/tau values
candidates = dict()
for idxs in flip_idxs:
deriv_old = candidates.get(idxs[0], 0.)
candidates[idxs[0]] = idxs[1] + deriv_old

# Return a sorted list of flip indices and signed dEQCC/dtau values for each DIS group
dis_groups = [idxs_deriv for idxs_deriv in candidates.items()
if abs(idxs_deriv[1]) >= qcc_deriv_thresh]
if abs(idxs_deriv[1]) >= deqcc_dtau_thresh]
return sorted(dis_groups, key=lambda deriv: abs(deriv[1]), reverse=True)


Expand All @@ -124,18 +117,18 @@ def get_idxs_deriv(qham_term, *qham_qmf_data):
"""

coef, pure_params = qham_qmf_data
idxs, gen_list, idxs_deriv = "", [], None
idxs, gen_tup, idxs_deriv = "", tuple(), None
for pauli_factor in qham_term:
# The indices of X and Y operators are flip indices
idx, pauli_op = pauli_factor
if "X" in pauli_op or "Y" in pauli_op:
gen = (idx, "Y") if idxs == "" else (idx, "X")
idxs = idxs + f" {idx}" if idxs != "" else f"{idx}"
gen_list.append(gen)
gen_tup += (gen, )
# Generators must have at least two flip indices
if len(gen_list) > 1:
if len(gen_tup) > 1:
qham_gen_comm = QubitOperator(qham_term, -1j * coef)
qham_gen_comm *= QubitOperator(tuple(gen_list), 1.)
qham_gen_comm *= QubitOperator(gen_tup, 1.)
deriv = get_op_expval(qham_gen_comm, pure_params).real
idxs_deriv = (idxs, deriv)
return idxs_deriv
Expand Down
Loading

0 comments on commit bf24c41

Please sign in to comment.