"""Module defining the QPE algorithm for qiskit"""
import numpy as np
from scipy.linalg import expm
from qiskit import QuantumCircuit
from qiskit.primitives import Sampler
from qiskit.circuit.library import UnitaryGate
from qiskit_nature.second_q.circuit.library import HartreeFock
from qc2.algorithms.base.base_algorithm import BaseAlgorithm
from qc2.algorithms.utils.mappers import FermionicToQubitMapper
from qc2.algorithms.utils.active_space import ActiveSpace
from qiskit_nature.second_q.mappers import QubitMapper
from qc2.algorithms.algorithms_results import QPEResults
[docs]
class PEBase(BaseAlgorithm):
def __init__(self,
qc2data=None,
active_space=None,
mapper=None,
sampler=None,
reference_state=None,
verbose=0):
# init active space and mapper
[docs]
self.active_space = (
ActiveSpace((2, 2), 2) if active_space is None else active_space
)
[docs]
self.mapper = (
FermionicToQubitMapper.from_string('jw')()
if mapper is None
else FermionicToQubitMapper.from_string(mapper)()
)
[docs]
self.qubits = 2 * self.active_space.num_active_spatial_orbitals
[docs]
self.electrons = sum(self.active_space.num_active_electrons)
[docs]
self.reference_state = (
self._get_default_reference(self.active_space, self.mapper)
if reference_state is None
else reference_state
)
[docs]
self.sampler = Sampler() if sampler is None else sampler
@staticmethod
[docs]
def _get_default_reference(
active_space: ActiveSpace, mapper: QubitMapper
) -> QuantumCircuit:
"""Set up the default reference state circuit based on Hartree Fock.
Args:
active_space (ActiveSpace): description of the active space.
mapper (mapper): mapper class instance.
Returns:
QuantumCircuit: Hartree-Fock circuit as the reference state.
"""
return HartreeFock(
active_space.num_active_spatial_orbitals,
active_space.num_active_electrons,
mapper,
)
[docs]
def _init_qubit_hamiltonian(self):
if self.qc2data is None:
raise ValueError("qc2data attribute set incorrectly in QPE.")
self.e_core, self.qubit_op = self.qc2data.get_qubit_hamiltonian(
self.active_space.num_active_electrons,
self.active_space.num_active_spatial_orbitals,
self.mapper,
format=self.format,
)
@staticmethod
[docs]
def _phase_to_energy(phase: float) -> float:
"""
Convert a phase from 0 to 1 to an energy from -pi to pi.
Args:
phase (float): The phase to convert.
Returns:
float: The energy corresponding to the given phase.
"""
return (phase - 1) * 2*np.pi
[docs]
def run(self) -> QPEResults:
"""
Executes the Quantum Phase Estimation (QPE) algorithm to estimate the energy
of the electronic ground state of a molecule.
Initializes the qubit Hamiltonian, constructs the unitary matrix, runs the
QPE algorithm, and calculates the phase and energy. The results are
encapsulated in a `QPEResults` object.
Returns:
QPEResults: An instance of the `QPEResults` class containing the optimal
energy, eigenvalue, and phase obtained from the QPE algorithm.
**Example**
>>> from ase.build import molecule
>>> from qc2.ase import PySCF
>>> from qc2.data import qc2Data
>>> from qc2.algorithms.qiskit import QPE
>>> from qc2.algorithms.utils import ActiveSpace
>>>
>>> mol = molecule('H2O')
>>>
>>> hdf5_file = 'h2o.hdf5'
>>> qc2data = qc2Data(hdf5_file, mol, schema='qcschema')
>>> qc2data.molecule.calc = PySCF()
>>> qc2data.run()
>>> qc2data.algorithm = QPE(
... active_space=ActiveSpace(
... num_active_electrons=(2, 2),
... num_active_spatial_orbitals=4
... ),
... mapper='parity',
... num_evaluation_qubits=9
... )
>>> results = qc2data.algorithm.run()
"""
# create Hamiltonian
self._init_qubit_hamiltonian()
# create the unitary matrix from the qubit operator
unitary = UnitaryGate(expm(1j*self.qubit_op.to_matrix()))
# run QPE algorithm
qiskit_res = self.solver.estimate(unitary, self.reference_state)
# get the energy
energy = self._phase_to_energy(qiskit_res.phase)
# instantiate VQEResults
results = QPEResults()
results.optimal_energy = energy + self.e_core
results.eigenvalue = energy
results.phase = qiskit_res.phase
print(f"=== QISKIT {self.__class__.__name__} RESULTS ===")
print("* Electronic ground state "
f"energy (Hartree): {results.eigenvalue}")
print(f"* Inactive core energy (Hartree): {self.e_core}")
print(">>> Total ground state "
f"energy (Hartree): {results.optimal_energy}\n")
return results