Source code for openfermioncirq.trotter.algorithms.split_operator

#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""A Trotter algorithm using a split-operator approach."""

from typing import cast, Optional, Sequence, Tuple

import cirq
from openfermion import DiagonalCoulombHamiltonian, QuadraticHamiltonian

from openfermioncirq import rot11, rot111, bogoliubov_transform, swap_network

from openfermioncirq.trotter.trotter_algorithm import (
        Hamiltonian,
        TrotterStep,
        TrotterAlgorithm)


[docs]class SplitOperatorTrotterAlgorithm(TrotterAlgorithm): """A Trotter algorithm using a split-operator approach. This algorithm simulates a DiagonalCoulombHamiltonian. It uses Bogoliubov transformations to switch between a basis in which the one-body terms are convenient to simulate and a basis in which the two-body terms are convenient to simulate. The Bogoliubov transformations are implemented using Givens rotations. This algorithm is described in arXiv:1706.00023. """ # TODO Maybe use FFFT supported_types = {DiagonalCoulombHamiltonian} def symmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return SymmetricSplitOperatorTrotterStep(hamiltonian) def asymmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return AsymmetricSplitOperatorTrotterStep(hamiltonian) def controlled_symmetric(self, hamiltonian: Hamiltonian ) -> Optional[TrotterStep]: return ControlledSymmetricSplitOperatorTrotterStep(hamiltonian) def controlled_asymmetric(self, hamiltonian: Hamiltonian ) -> Optional[TrotterStep]: return ControlledAsymmetricSplitOperatorTrotterStep(hamiltonian)
SPLIT_OPERATOR = SplitOperatorTrotterAlgorithm() class SplitOperatorTrotterStep(TrotterStep): def __init__(self, hamiltonian: DiagonalCoulombHamiltonian) -> None: quad_ham = QuadraticHamiltonian(hamiltonian.one_body) # Get the basis change matrix that diagonalizes the one-body term # and associated orbital energies self.orbital_energies, self.basis_change_matrix, _ = ( quad_ham.diagonalizing_bogoliubov_transform() ) super().__init__(hamiltonian) class SymmetricSplitOperatorTrotterStep(SplitOperatorTrotterStep): def prepare(self, qubits: Sequence[cirq.Qid], control_qubits: Optional[cirq.Qid]=None ) -> cirq.OP_TREE: # Change to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) def trotter_step( self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid]=None ) -> cirq.OP_TREE: n_qubits = len(qubits) # Simulate the one-body terms for half of the full time yield (cirq.rz(rads= -0.5 * self.orbital_energies[i] * time).on(qubits[i]) for i in range(n_qubits)) # Rotate to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield rot11(rads= -2 * self.hamiltonian.two_body[p, q] * time).on(a, b) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate back to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for half of the full time yield (cirq.rz(rads= -0.5 * self.orbital_energies[i] * time).on(qubits[i]) for i in range(n_qubits)) def step_qubit_permutation(self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid]=None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], None def finish(self, qubits: Sequence[cirq.Qid], n_steps: int, control_qubit: Optional[cirq.Qid]=None, omit_final_swaps: bool=False ) -> cirq.OP_TREE: # Rotate back to the computational basis yield bogoliubov_transform( qubits, self.basis_change_matrix) # If the number of Trotter steps is odd, possibly swap qubits back if n_steps & 1 and not omit_final_swaps: yield swap_network(qubits) class ControlledSymmetricSplitOperatorTrotterStep(SplitOperatorTrotterStep): def prepare(self, qubits: Sequence[cirq.Qid], control_qubits: Optional[cirq.Qid]=None ) -> cirq.OP_TREE: # Change to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) def trotter_step( self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid]=None ) -> cirq.OP_TREE: n_qubits = len(qubits) if not isinstance(control_qubit, cirq.Qid): raise TypeError('Control qudit must be specified.') # Simulate the one-body terms for half of the full time yield (rot11(rads= -0.5 * self.orbital_energies[i] * time).on( control_qubit, qubits[i]) for i in range(n_qubits)) # Rotate to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield rot111(-2 * self.hamiltonian.two_body[p, q] * time).on( cast(cirq.Qid, control_qubit), a, b) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate back to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for half of the full time yield (rot11(rads= -0.5 * self.orbital_energies[i] * time).on( control_qubit, qubits[i]) for i in range(n_qubits)) # Apply phase from constant term yield cirq.rz(rads= -self.hamiltonian.constant * time).on(control_qubit) def step_qubit_permutation(self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid]=None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], control_qubit def finish(self, qubits: Sequence[cirq.Qid], n_steps: int, control_qubit: Optional[cirq.Qid]=None, omit_final_swaps: bool=False ) -> cirq.OP_TREE: # Rotate back to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # If the number of Trotter steps is odd, possibly swap qubits back if n_steps & 1 and not omit_final_swaps: yield swap_network(qubits) class AsymmetricSplitOperatorTrotterStep(SplitOperatorTrotterStep): def trotter_step( self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid]=None ) -> cirq.OP_TREE: n_qubits = len(qubits) # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield rot11(rads= -2 * self.hamiltonian.two_body[p, q] * time).on(a, b) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for the full time yield (cirq.rz(rads= -self.orbital_energies[i] * time).on(qubits[i]) for i in range(n_qubits)) # Rotate back to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) def step_qubit_permutation(self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid]=None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], None def finish(self, qubits: Sequence[cirq.Qid], n_steps: int, control_qubit: Optional[cirq.Qid]=None, omit_final_swaps: bool=False ) -> cirq.OP_TREE: # If the number of Trotter steps is odd, possibly swap qubits back if n_steps & 1 and not omit_final_swaps: yield swap_network(qubits) class ControlledAsymmetricSplitOperatorTrotterStep(SplitOperatorTrotterStep): def trotter_step( self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid]=None ) -> cirq.OP_TREE: n_qubits = len(qubits) if not isinstance(control_qubit, cirq.Qid): raise TypeError('Control qudit must be specified.') # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield rot111(-2 * self.hamiltonian.two_body[p, q] * time).on( cast(cirq.Qid, control_qubit), a, b) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate to the basis in which the one-body term is diagonal yield cirq.inverse( bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for the full time yield (rot11(rads= -self.orbital_energies[i] * time).on( control_qubit, qubits[i]) for i in range(n_qubits)) # Rotate back to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # Apply phase from constant term yield cirq.rz(rads= -self.hamiltonian.constant * time).on(control_qubit) def step_qubit_permutation(self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid]=None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], control_qubit def finish(self, qubits: Sequence[cirq.Qid], n_steps: int, control_qubit: Optional[cirq.Qid]=None, omit_final_swaps: bool=False ) -> cirq.OP_TREE: # If the number of Trotter steps is odd, possibly swap qubits back if n_steps & 1 and not omit_final_swaps: yield swap_network(qubits)