Source code for qc2.algorithms.pennylane.pebase

"""Module defining QPE algorithm for PennyLane."""
from scipy.linalg import expm
import pennylane as qml
from pennylane import numpy as np
from pennylane import QNode
from pennylane.operation import Operator
from qc2.algorithms.utils.active_space import ActiveSpace
from qc2.algorithms.utils.mappers import FermionicToQubitMapper
from qc2.algorithms.algorithms_results import QPEResults
from qc2.algorithms.base.base_algorithm import BaseAlgorithm

[docs] class PEBase(BaseAlgorithm): def __init__( self, qc2data=None, active_space=None, mapper=None, device=None, reference_state=None, verbose=0 ):
[docs] self.qc2data = qc2data
[docs] self.format = "pennylane"
[docs] self.verbose = verbose
[docs] self.circuit = None
[docs] self.num_evaluation_qubits = None
# init active space and mapper
[docs] self.active_space = ( ActiveSpace((2, 2), 2) if active_space is None else active_space )
[docs] self.device = "default.qubit" if device is None else device
[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.qubits, self.electrons) if reference_state is None else reference_state )
@staticmethod
[docs] def _get_default_reference(qubits: int, electrons: int) -> np.ndarray: """Generate the default reference state for the ansatz. Args: qubits (int): Number of qubits in the circuit. electrons (int): Number of electrons in the system. Returns: np.ndarray: Reference state vector. """ return qml.qchem.hf_state(electrons, qubits)
[docs] def _init_qubit_hamiltonian(self): """ Initializes the qubit Hamiltonian for the quantum phase estimation algorithm. This method retrieves the qubit Hamiltonian representation of the target molecule from the `qc2data` object. It requires prior initialization of `qc2data` with the molecular data. Raises: ValueError: If `qc2data` is not set correctly. Attributes: e_core (float): The core energy of the system. qubit_op (Operator): The qubit operator representing the Hamiltonian. """ 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
@staticmethod
[docs] def _build_circuit( dev: str, qubits: int, num_estimation_wires: int, reference_state: np.ndarray, unitary_op: Operator, device_args=None, device_kwargs=None, qnode_args=None, qnode_kwargs=None ) -> QNode: """ Constructs and returns a PennyLane QNode for quantum phase estimation. This method sets up a quantum circuit on a specified quantum device using the provided parameters. It initializes the qubits, applies necessary quantum operations including the unitary operator, and prepares the reference state. Args: dev (str): Identifier for the PennyLane quantum device to be used. qubits (int): Total number of qubits in the circuit. num_estimation_wires (int): Number of qubits designated for phase estimation. reference_state (np.ndarray): Initial state of the qubits, typically representing the Hartree-Fock state. unitary_op (Operator): Operator that defines the unitary evolution in the circuit. device_args (list, optional): Additional positional arguments for the quantum device. Defaults to None. device_kwargs (dict, optional): Additional keyword arguments for the quantum device. Defaults to None. qnode_args (list, optional): Additional positional arguments for the QNode. Defaults to None. qnode_kwargs (dict, optional): Additional keyword arguments for the QNode. Defaults to None. Returns: QNode: The constructed QNode with the specified ansatz for phase estimation. """ raise NotImplementedError('Implement a _build_circuit method.')
[docs] def get_phase(self): """ Retrieves the estimated phase after the QPE algorithm has been run. Returns: float: The estimated phase. """ raise NotImplementedError('Implement a get_phase method.')
[docs] def run(self, *args, **kwargs) -> QPEResults: """Executes QPE algorithm. Args: *args: - device_args (optional): ``qml.device`` arguments. - qnode_args (optional): ``qml.qnode`` arguments. **kwargs: - device_kwargs (optional): ``qml.device`` keyword arguments. - qnode_kwargs (optional): ``qml.qnode`` keyword arguments. Returns: VQEResults: An instance of :class:`qc2.algorithms.pennylane.vqe.VQEResults` class with all VQE info. **Example** >>> from ase.build import molecule >>> from qc2.ase import PySCF >>> from qc2.data import qc2Data >>> from qc2.algorithms.pennylane 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 ... ), ... num_evaluation_qubits=3, ... device="default.qubit" ... ) >>> results = qc2data.algorithm.run() """ print(">>> Optimizing circuit parameters...") # create Hamiltonian self._init_qubit_hamiltonian() # create the unitary operator unitary_op = qml.exp(self.qubit_op, coeff=1j) # build circuit after building the qubit hamiltonian self.circuit = self._build_circuit( self.device, self.qubits, self.num_evaluation_qubits, self.reference_state, unitary_op, *args, **kwargs ) # extract the phase phase = self.get_phase() # get the energy energy = self._phase_to_energy(phase) # instantiate VQEResults results = QPEResults() results.optimal_energy = energy + self.e_core results.eigenvalue = energy results.phase = phase print(f"=== PENNYLANE {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