Source code for qiskit_cold_atom.applications.fermi_hubbard
# 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.
"""Module to build a Fermi-Hubbard problem."""
from abc import ABC, abstractmethod
from typing import List
from qiskit import QuantumCircuit
from qiskit_nature.operators.second_quantization import FermionicOp
from qiskit_cold_atom.fermions.fermion_circuit_solver import FermionicBasis
from qiskit_cold_atom.fermions.fermion_gate_library import FermiHubbard
from qiskit_cold_atom.exceptions import QiskitColdAtomError
[docs]class FermionicLattice(ABC):
"""Abstract base fermionic lattice."""
@property
@abstractmethod
def size(self) -> int:
"""The number of lattice sites of the system."""
[docs] @abstractmethod
def to_fermionic_op(self) -> FermionicOp:
"""Creates the Hamiltonian of the lattice in second quantization.
Returns:
The Hamiltonian as a FermionicOp.
"""
[docs] @abstractmethod
def to_circuit(self, time: float = 1.0) -> QuantumCircuit:
"""
Wrap the generator of the system in a QuantumCircuit.
Args:
time: Duration of the time evolution.
Returns:
A quantum circuit which corresponds to the time-evolved Hamiltonian.
"""
[docs]class FermiHubbard1D(FermionicLattice):
"""Describes a one-dimensional Fermi-Hubbard model with open boundary conditions."""
def __init__(
self,
num_sites: int,
particles_up: int,
particles_down: int,
hop_strength: float,
int_strength: float,
potential: List[float],
):
r"""
Initialize a one-dimensional fermi-hubbard system. In second quantization this system is
described by the Hamiltonian
:math:`H = \sum_{i=1,\sigma}^{L-1} - J_i (f^\dagger_{i,\sigma} f_{i+1,\sigma} +
f^\dagger_{i+1,\sigma} f_{i,\sigma}) + U \sum_{i=1}^{L} n_{i,\uparrow} n_{i,\downarrow}
+ \sum_{i=1,\sigma}^{L} \mu_i n_{i,\sigma}`
Args:
num_sites: number of lattice sites in the 1D chain.
particles_up: total number of spin-up particles in the lattice
particles_down: total number of spin-down particles in the lattice
hop_strength: strength of hopping between sites
int_strength: strength of the local interaction
potential: list of local phases, must be on length num_wires
Raises:
QiskitColdAtomError: if the length of the potential does not match the system size.
"""
# pylint: disable=invalid-name
self._size = num_sites
self.particles_up = particles_up
self.particles_down = particles_down
self.J = hop_strength
self.U = int_strength
self.basis = FermionicBasis(self.size, n_particles=[self.particles_up, self.particles_down])
if not len(potential) == self.size:
raise QiskitColdAtomError(
f"The length of the potentials {len(potential)} must match system size {self.size}"
)
self.mu = potential
@property
def size(self) -> int:
"""Return the number of sites of the problem."""
return self._size
[docs] def to_fermionic_op(self) -> FermionicOp:
"""Construct the hamiltonian of the lattice as a FermionicOp.
Returns:
A FermionicOp defining the systems Hamiltonian
"""
operator_labels = []
# add hopping terms
for idx in range(self.size - 1):
right_to_left_up = "I" * idx + "+-" + "I" * (self.size * 2 - idx - 2)
operator_labels.append((right_to_left_up, -self.J))
left_to_right_up = "I" * idx + "-+" + "I" * (self.size * 2 - idx - 2)
operator_labels.append((left_to_right_up, self.J))
right_to_left_down = "I" * (self.size + idx) + "+-" + "I" * (self.size - idx - 2)
operator_labels.append((right_to_left_down, -self.J))
left_to_right_down = "I" * (self.size + idx) + "-+" + "I" * (self.size - idx - 2)
operator_labels.append((left_to_right_down, self.J))
# add interaction terms
for idx in range(self.size):
opstring = "I" * idx + "N" + "I" * (self.size - 1) + "N" + "I" * (self.size - 1 - idx)
operator_labels.append((opstring, self.U))
# add potential terms
for idx in range(self.size):
op_up = "I" * idx + "N" + "I" * (2 * self.size - idx - 1)
operator_labels.append((op_up, self.mu[idx]))
op_down = "I" * (self.size + idx) + "N" + "I" * (self.size - idx - 1)
operator_labels.append((op_down, self.mu[idx]))
return FermionicOp(operator_labels)
[docs] def to_circuit(self, time: float = 1.0) -> QuantumCircuit:
"""
Wrap the generator of the system in a QuantumCircuit.
Args:
time: Duration of the time evolution.
Returns:
A quantum circuit which corresponds to the time-evolved Hamiltonian.
"""
circ = QuantumCircuit(2 * self.size)
circ.append(
FermiHubbard(
num_modes=2 * self.size,
j=[self.J * time] * (self.size - 1),
u=self.U * time,
mu=[mu_i * time for mu_i in self.mu],
),
qargs=range(2 * self.size),
)
return circ