Skip to content

Commit

Permalink
VQESolver cleanup (#230)
Browse files Browse the repository at this point in the history
* Removed qubitop_to_qubitham function.
* Ansatz -> agen.Ansatz.
* BuiltInAnsatze as a dictionary.
  • Loading branch information
alexfleury-sb authored Oct 28, 2022
1 parent a7032a9 commit 9a9d9b8
Show file tree
Hide file tree
Showing 11 changed files with 1,488 additions and 128 deletions.
1,388 changes: 1,387 additions & 1 deletion examples/excited_states.ipynb

Large diffs are not rendered by default.

46 changes: 16 additions & 30 deletions tangelo/algorithms/variational/sa_vqe_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,11 @@
import numpy as np

from tangelo.linq import Simulator, Circuit
from tangelo.toolboxes.operators import qubitop_to_qubitham
from tangelo.toolboxes.qubit_mappings import statevector_mapping
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, HEA, UpCCGSD, UCCGD, VariationalCircuitAnsatz
from tangelo.toolboxes.ansatz_generator.penalty_terms import combined_penalty
from tangelo.algorithms.variational import BuiltInAnsatze, VQESolver
import tangelo.toolboxes.ansatz_generator as agen


class SA_VQESolver(VQESolver):
Expand Down Expand Up @@ -100,29 +98,29 @@ def __init__(self, opt_dict):
self.weights = np.array(self.weights)
self.weights = self.weights/sum(self.weights)

self.ansatz_options["reference_state"] = "zero"

def build(self):
"""Build the underlying objects required to run the SA-VQE algorithm
afterwards.
"""

if isinstance(self.ansatz, Circuit):
self.ansatz = VariationalCircuitAnsatz(self.ansatz)
self.ansatz = agen.VariationalCircuitAnsatz(self.ansatz)

# Building VQE with a molecule as input.
if self.molecule:

# Compute qubit hamiltonian for the input molecular system
qubit_op = fermion_to_qubit_mapping(fermion_operator=self.molecule.fermionic_hamiltonian,
mapping=self.qubit_mapping,
n_spinorbitals=self.molecule.n_active_sos,
n_electrons=self.molecule.n_active_electrons,
up_then_down=self.up_then_down,
spin=self.molecule.spin)
self.qubit_hamiltonian = fermion_to_qubit_mapping(fermion_operator=self.molecule.fermionic_hamiltonian,
mapping=self.qubit_mapping,
n_spinorbitals=self.molecule.n_active_sos,
n_electrons=self.molecule.n_active_electrons,
up_then_down=self.up_then_down,
spin=self.molecule.spin)

self.core_constant, self.oneint, self.twoint = self.molecule.get_active_space_integrals()

self.qubit_hamiltonian = qubitop_to_qubitham(qubit_op, self.qubit_mapping, self.up_then_down)

if self.penalty_terms:
pen_ferm = combined_penalty(self.molecule.n_active_mos, self.penalty_terms)
pen_qubit = fermion_to_qubit_mapping(fermion_operator=pen_ferm,
Expand All @@ -131,32 +129,23 @@ def build(self):
n_electrons=self.molecule.n_active_electrons,
up_then_down=self.up_then_down,
spin=self.molecule.spin)
pen_qubit = qubitop_to_qubitham(pen_qubit, self.qubit_hamiltonian.mapping, self.qubit_hamiltonian.up_then_down)
self.qubit_hamiltonian += pen_qubit

# Build / set ansatz circuit. Use user-provided circuit or built-in ansatz depending on user input.
if isinstance(self.ansatz, BuiltInAnsatze):
if self.ansatz == BuiltInAnsatze.UCCSD:
self.ansatz = UCCSD(self.molecule, self.qubit_mapping, self.up_then_down, self.molecule.spin)
elif self.ansatz == BuiltInAnsatze.HEA:
self.ansatz_options["reference_state"] = "zero"
self.ansatz = HEA(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif self.ansatz == BuiltInAnsatze.UpCCGSD:
self.ansatz = UpCCGSD(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, up_then_down=self.up_then_down)
if self.ansatz in self.builtin_ansatze:
self.ansatz = self.ansatz.value(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
else:
raise ValueError(f"Unsupported ansatz for SA_VQESolver. Built-in ansatze:\n\t{self.builtin_ansatze}")
elif not isinstance(self.ansatz, Ansatz):
elif not isinstance(self.ansatz, agen.Ansatz):
raise TypeError(f"Invalid ansatz dataype. Expecting instance of Ansatz class, or one of built-in options:\n\t{self.builtin_ansatze}")

# Building with a qubit Hamiltonian.
elif self.ansatz == BuiltInAnsatze.HEA:
self.ansatz = HEA(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif not isinstance(self.ansatz, Ansatz):
self.ansatz = self.ansatz.value(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif not isinstance(self.ansatz, agen.Ansatz):
raise TypeError(f"Invalid ansatz dataype. Expecting a custom Ansatz (Ansatz class).")

self.ansatz.default_reference_state = "zero"
self.reference_circuits = list()
for ref_state in self.ref_states:
if isinstance(ref_state, Circuit):
Expand All @@ -171,10 +160,7 @@ def build(self):
self.ansatz.build_circuit()

# Quantum circuit simulation backend options
t = self.backend_options.get("target", self.default_backend_options["target"])
ns = self.backend_options.get("n_shots", self.default_backend_options["n_shots"])
nm = self.backend_options.get("noise_model", self.default_backend_options["noise_model"])
self.backend = Simulator(target=t, n_shots=ns, noise_model=nm)
self.backend = Simulator(**self.backend_options)

def simulate(self):
"""Run the SA-VQE algorithm, using the ansatz, classical optimizer, initial
Expand Down
107 changes: 37 additions & 70 deletions tangelo/algorithms/variational/vqe_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,26 @@
from tangelo.helpers.utils import HiddenPrints
from tangelo.linq import Simulator, Circuit
from tangelo.linq.helpers.circuits.measurement_basis import measurement_basis_gates
from tangelo.toolboxes.operators import count_qubits, FermionOperator, qubitop_to_qubitham
from tangelo.toolboxes.operators import count_qubits, FermionOperator
from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping
from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_mapped_vector, vector_to_circuit
from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz
from tangelo.toolboxes.ansatz_generator import UCCSD, RUCC, HEA, UpCCGSD, QMF, QCC, VSQS, UCCGD, ILC,\
VariationalCircuitAnsatz
from tangelo.toolboxes.ansatz_generator._qubit_mf import init_qmf_from_vector
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
from tangelo.toolboxes.optimizers.rotosolve import rotosolve
from tangelo.toolboxes.optimizers import rotosolve
import tangelo.toolboxes.ansatz_generator as agen


class BuiltInAnsatze(Enum):
"""Enumeration of the ansatz circuits supported by VQE."""
UCCSD = 0
UCC1 = 1
UCC3 = 2
HEA = 3
UpCCGSD = 4
QMF = 5
QCC = 6
VSQS = 7
UCCGD = 8
ILC = 9
UCCSD = agen.UCCSD
UCC1 = agen.RUCC(1)
UCC3 = agen.RUCC(3)
HEA = agen.HEA
UpCCGSD = agen.UpCCGSD
QMF = agen.QMF
QCC = agen.QCC
VSQS = agen.VSQS
UCCGD = agen.UCCGD
ILC = agen.ILC


class VQESolver:
Expand Down Expand Up @@ -137,8 +132,9 @@ def __init__(self, opt_dict):
if self.ansatz in [BuiltInAnsatze.QCC, BuiltInAnsatze.ILC, BuiltInAnsatze.QMF]:
raise ValueError("Circuit reference state is not supported for QCC or QMF")
elif self.ref_state is not None:
self.ansatz_options["reference_state"] = "zero"
if self.ansatz in [BuiltInAnsatze.QCC, BuiltInAnsatze.ILC]:
self.ansatz_options["qmf_var_params"] = init_qmf_from_vector(self.ref_state, self.qubit_mapping, self.up_then_down)
self.ansatz_options["qmf_var_params"] = agen._qubit_mf.init_qmf_from_vector(self.ref_state, self.qubit_mapping, self.up_then_down)
self.ref_state = None
elif self.ansatz == BuiltInAnsatze.QMF:
self.ansatz_options["init_qmf"] = {"init_params": "vector", "vector": self.ref_state}
Expand All @@ -157,7 +153,8 @@ def __init__(self, opt_dict):
else:
self.reference_circuit = Circuit()

self.default_backend_options = default_backend_options
default_backend_options.update(self.backend_options)
self.backend_options = default_backend_options
self.optimal_energy = None
self.optimal_var_params = None
self.builtin_ansatze = set(BuiltInAnsatze)
Expand All @@ -168,7 +165,7 @@ def build(self):
"""

if isinstance(self.ansatz, Circuit):
self.ansatz = VariationalCircuitAnsatz(self.ansatz)
self.ansatz = agen.VariationalCircuitAnsatz(self.ansatz)

# Check compatibility of optimizer with Ansatz class
elif self.optimizer == rotosolve:
Expand All @@ -179,24 +176,21 @@ def build(self):
if self.molecule:

# Compute qubit hamiltonian for the input molecular system
qubit_op = fermion_to_qubit_mapping(fermion_operator=self.molecule.fermionic_hamiltonian,
mapping=self.qubit_mapping,
n_spinorbitals=self.molecule.n_active_sos,
n_electrons=self.molecule.n_active_electrons,
up_then_down=self.up_then_down,
spin=self.molecule.spin)

self.qubit_hamiltonian = qubitop_to_qubitham(qubit_op, self.qubit_mapping, self.up_then_down)
self.qubit_hamiltonian = fermion_to_qubit_mapping(fermion_operator=self.molecule.fermionic_hamiltonian,
mapping=self.qubit_mapping,
n_spinorbitals=self.molecule.n_active_sos,
n_electrons=self.molecule.n_active_electrons,
up_then_down=self.up_then_down,
spin=self.molecule.spin)

if self.penalty_terms:
pen_ferm = combined_penalty(self.molecule.n_active_mos, self.penalty_terms)
pen_ferm = agen.penalty_terms.combined_penalty(self.molecule.n_active_mos, self.penalty_terms)
pen_qubit = fermion_to_qubit_mapping(fermion_operator=pen_ferm,
mapping=self.qubit_mapping,
n_spinorbitals=self.molecule.n_active_sos,
n_electrons=self.molecule.n_active_electrons,
up_then_down=self.up_then_down,
spin=self.molecule.spin)
pen_qubit = qubitop_to_qubitham(pen_qubit, self.qubit_hamiltonian.mapping, self.qubit_hamiltonian.up_then_down)
self.qubit_hamiltonian += pen_qubit

# Verification of system compatibility with UCC1 or UCC3 circuits.
Expand All @@ -213,43 +207,19 @@ def build(self):

# Build / set ansatz circuit. Use user-provided circuit or built-in ansatz depending on user input.
if isinstance(self.ansatz, BuiltInAnsatze):
if self.ansatz == BuiltInAnsatze.UCCSD:
self.ansatz = UCCSD(self.molecule, self.qubit_mapping, self.up_then_down)
self.ansatz.default_reference_state = "HF" if self.ref_state is None else "zero"
elif self.ansatz == BuiltInAnsatze.UCC1:
self.ansatz = RUCC(1)
elif self.ansatz == BuiltInAnsatze.UCC3:
self.ansatz = RUCC(3)
elif self.ansatz == BuiltInAnsatze.HEA:
if self.ref_state is not None:
self.ansatz_options["reference_state"] = "zero"
self.ansatz = HEA(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif self.ansatz == BuiltInAnsatze.UpCCGSD:
self.ansatz = UpCCGSD(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
self.ansatz.default_reference_state = "HF" if self.ref_state is None else "zero"
elif self.ansatz == BuiltInAnsatze.QMF:
self.ansatz = QMF(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif self.ansatz == BuiltInAnsatze.QCC:
self.ansatz = QCC(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif self.ansatz == BuiltInAnsatze.VSQS:
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)
self.ansatz.default_reference_state = "HF" if self.ref_state is None else "zero"
elif self.ansatz == BuiltInAnsatze.ILC:
self.ansatz = ILC(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
if self.ansatz in {BuiltInAnsatze.UCC1, BuiltInAnsatze.UCC3}:
self.ansatz = self.ansatz.value
elif self.ansatz in self.builtin_ansatze:
self.ansatz = self.ansatz.value(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):
elif not isinstance(self.ansatz, agen.Ansatz):
raise TypeError(f"Invalid ansatz dataype. Expecting instance of Ansatz class, or one of built-in options:\n\t{self.builtin_ansatze}")

# Building with a qubit Hamiltonian.
elif self.ansatz in [BuiltInAnsatze.HEA, BuiltInAnsatze.VSQS]:
if self.ansatz == BuiltInAnsatze.HEA:
self.ansatz = HEA(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif self.ansatz == BuiltInAnsatze.VSQS:
self.ansatz = VSQS(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif not isinstance(self.ansatz, Ansatz):
elif self.ansatz in {BuiltInAnsatze.HEA, BuiltInAnsatze.VSQS}:
self.ansatz = self.ansatz.value(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options)
elif not isinstance(self.ansatz, agen.Ansatz):
raise TypeError(f"Invalid ansatz dataype. Expecting a custom Ansatz (Ansatz class).")

# Set ansatz initial parameters (default or use input), build corresponding ansatz circuit
Expand All @@ -260,10 +230,7 @@ def build(self):
warnings.warn("No variational gate found in the circuit.", RuntimeWarning)

# Quantum circuit simulation backend options
t = self.backend_options.get("target", self.default_backend_options["target"])
ns = self.backend_options.get("n_shots", self.default_backend_options["n_shots"])
nm = self.backend_options.get("noise_model", self.default_backend_options["noise_model"])
self.backend = Simulator(target=t, n_shots=ns, noise_model=nm)
self.backend = Simulator(**self.backend_options)

def simulate(self):
"""Run the VQE algorithm, using the ansatz, classical optimizer, initial
Expand Down Expand Up @@ -376,11 +343,11 @@ def operator_expectation(self, operator, var_params=None, n_active_mos=None, n_a
raise KeyError("Must supply n_active_mos when a QubitHamiltonian has initialized VQESolver"
" and requesting the expectation of 'N', 'Sz', or 'S^2'")
if operator == "N":
exp_op = number_operator(n_active_mos, up_then_down=False)
exp_op = agen.fermionic_operators.number_operator(n_active_mos, up_then_down=False)
elif operator == "Sz":
exp_op = spinz_operator(n_active_mos, up_then_down=False)
exp_op = agen.fermionic_operators.spinz_operator(n_active_mos, up_then_down=False)
elif operator == "S^2":
exp_op = spin2_operator(n_active_mos, up_then_down=False)
exp_op = agen.fermionic_operators.spin2_operator(n_active_mos, up_then_down=False)
else:
raise ValueError('Only expectation values of N, Sz and S^2')
elif isinstance(operator, FermionOperator):
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 @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from .ansatz import Ansatz
from .vsqs import VSQS
from .ilc import ILC
from .qcc import QCC
Expand Down
11 changes: 7 additions & 4 deletions tangelo/toolboxes/ansatz_generator/ilc.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,14 @@ class ILC(Ansatz):
max_ilc_gens (int or None): Maximum number of generators allowed in the ansatz. If None,
one generator from each DIS group is selected. If int, then min(|DIS|, max_ilc_gens)
generators are selected in order of decreasing |dEILC/dtau|. Default, None.
reference_state (string): The reference state id for the ansatz. The
supported reference states are stored in the supported_reference_state
attributes. Default, "HF".
"""

def __init__(self, molecule, mapping="jw", up_then_down=False, acs=None,
qmf_circuit=None, qmf_var_params=None, qubit_ham=None, ilc_tau_guess=1e-2,
deilc_dtau_thresh=1e-3, max_ilc_gens=None):
deilc_dtau_thresh=1e-3, max_ilc_gens=None, reference_state="HF"):

if not molecule and not (isinstance(molecule, SecondQuantizedMolecule) and isinstance(molecule, dict)):
raise ValueError("An instance of SecondQuantizedMolecule or a dict is required for "
Expand Down Expand Up @@ -136,7 +139,7 @@ def __init__(self, molecule, mapping="jw", up_then_down=False, acs=None,
self.supported_initial_var_params = {"qmf_state", "ilc_tau_guess", "random", "diag"}

# Default starting parameters for initialization
self.default_reference_state = "HF"
self.reference_state = reference_state
self.var_params_default = "diag"
self.var_params = None
self.rebuild_dis = False
Expand Down Expand Up @@ -184,10 +187,10 @@ def prepare_reference_state(self):
wavefunction with HF, multi-reference state, etc). These preparations must be consistent
with the transform used to obtain the qubit operator. """

if self.default_reference_state not in self.supported_reference_state:
if self.reference_state not in self.supported_reference_state:
raise ValueError(f"Only supported reference state methods are: "
f"{self.supported_reference_state}.")
if self.default_reference_state == "HF":
if self.reference_state == "HF":
reference_state_circuit = get_qmf_circuit(self.qmf_var_params, True)
return reference_state_circuit

Expand Down
Loading

0 comments on commit 9a9d9b8

Please sign in to comment.