Source code for qiskit_cold_atom.applications.time_evolution_solver

# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""A solver for time-evolution problems."""

from typing import List

from qiskit_nature.operators.second_quantization import FermionicOp
from qiskit_nature.mappers.second_quantization import (
    JordanWignerMapper,
    BravyiKitaevMapper,
    ParityMapper,
)
from qiskit_nature.converters.second_quantization import QubitConverter

from qiskit import QuantumCircuit, QuantumRegister
from qiskit.opflow.evolutions import PauliTrotterEvolution
from qiskit import execute

from qiskit_cold_atom.applications.fermionic_evolution_problem import (
    FermionicEvolutionProblem,
)
from qiskit_cold_atom.fermions.base_fermion_backend import BaseFermionBackend


[docs]class TimeEvolutionSolver: """ Solver class that solves time evolution problem by either analog simulation on fermionic hardware or trotterized time evolution on qubit hardware. The computation that this time evolution solver will do depends on the type of the backend. """ MAPPER_DICT = { "bravyi_kitaev": BravyiKitaevMapper(), "jordan_wigner": JordanWignerMapper(), "parity": ParityMapper(), } def __init__( self, backend, map_type: str = None, trotter_steps: int = None, shots: int = 1000, ): """ Initialize a time evolution solver Args: backend: The backend on which to execute the problem, may be qubit or fermionic. map_type: The fermion-to-qubit mapping required if a qubit backend is given trotter_steps: The amount of trotter steps to approximate time evolution on qubit backends shots: number of measurements taken of the constructed circuits """ self.backend = backend self.map_type = map_type self.trotter_steps = trotter_steps self.shots = shots
[docs] def solve(self, problem: FermionicEvolutionProblem) -> List[float]: """Solve the problem using the provided backend Args: problem: The FermionicEvolutionProblem to solve. Returns: A list of expectation values of the observable of the problem. This list has the same length as the list of times for which to compute the time evolution. """ if isinstance(self.backend, BaseFermionBackend): qc_load = self.backend.initialize_circuit(problem.initial_state.occupations) circuits = problem.circuits(qc_load) observable_evs = self.backend.measure_observable_expectation( circuits, problem.observable, self.shots ) else: # use qubit pipeline circuits = self.construct_qubit_circuits(problem) # construct observable mapper = self.MAPPER_DICT[self.map_type] qubit_observable = mapper.map(problem.observable) observable_mat = qubit_observable.to_spmatrix() observable_evs = [0.0] * len(problem.evolution_times) for idx, circuit in enumerate(circuits): circuit.measure_all() job = execute(circuit, self.backend, shots=self.shots) counts = job.result().get_counts().int_outcomes() for outcome_ind in counts: prob = counts[outcome_ind] / self.shots observable_evs[idx] += prob * observable_mat.diagonal()[outcome_ind].real return observable_evs
[docs] def construct_qubit_circuits(self, problem: FermionicEvolutionProblem) -> List[QuantumCircuit]: """Convert the problem to a trotterized qubit circuit using the specified map_type Args: problem: The fermionic evolution problem specifying the system, evolution-time and observable to be measured Returns: a list of quantum circuits that simulate the time evolution. There is one circuit for each evolution time specified in the problem' """ psi_0 = problem.initial_state system = problem.system hamiltonian = system.to_fermionic_op() mapper = self.MAPPER_DICT[self.map_type] circuits = [] # construct circuit of initial state: label = ["+" if bit else "I" for bit in psi_0.occupations_flat] bitstr_op = FermionicOp("".join(label)) qubit_op = QubitConverter(mapper).convert(bitstr_op)[0] init_circ = QuantumCircuit(QuantumRegister(qubit_op.num_qubits, "q")) for i, pauli_label in enumerate(qubit_op.primitive.paulis[0].to_label()[::-1]): if pauli_label == "X": init_circ.x(i) elif pauli_label == "Y": init_circ.y(i) elif pauli_label == "Z": init_circ.z(i) for time in problem.evolution_times: # time-step of zero will cause PauliTrotterEvolution to fail if time == 0.0: time += 1e-10 # map fermionic hamiltonian to qubits qubit_hamiltonian = mapper.map(hamiltonian * time) # get time evolution operator by exponentiating exp_op = qubit_hamiltonian.exp_i() # perform trotterization evolved_op = PauliTrotterEvolution(reps=self.trotter_steps).convert(exp_op) trotter_circ = evolved_op.to_circuit_op().to_circuit() circuits.append(init_circ.compose(trotter_circ)) return circuits