# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2021, 2023.
#
# 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.
"""The Fermi-Hubbard model"""
import numpy as np
from qiskit_nature.second_q.operators import FermionicOp
from .lattice_model import LatticeModel
from .lattices import Lattice
[docs]class FermiHubbardModel(LatticeModel):
    r"""The Fermi-Hubbard model.
    This class implements the following Hamiltonian:
    .. math::
        H = \sum_{i, j}\sum_{\sigma = \uparrow, \downarrow} t_{i, j} c_{i, \sigma}^\dagger
        c_{j, \sigma} + U \sum_{i} n_{i, \uparrow} n_{i, \downarrow},
    where :math:`c_{i, \sigma}^\dagger` and :math:`c_{i, \sigma}` are creation and annihilation
    operators of a fermion at the site :math:`i` with spin :math:`\sigma`. The operator :math:`n_{i,
    \sigma}` is the number operator, which is defined by :math:`n_{i, \sigma} = c_{i,
    \sigma}^\dagger c_{i, \sigma}`. The matrix :math:`t_{i, j}` is a Hermitian matrix called the
    interaction matrix. The parameter :math:`U` represents the strength of the on-site interaction.
    This model is instantiated using a
    :class:`~qiskit_nature.second_q.hamiltonians.lattices.Lattice`. For example, using a
    :class:`~qiskit_nature.second_q.hamiltonians.lattices.LineLattice`:
    .. code-block:: python
        line_lattice = LineLattice(num_nodes=10, boundary_condition=BoundaryCondition.OPEN)
        fermi_hubbard_model = FermiHubbardModel(
            line_lattice.uniform_parameters(
                uniform_interaction=-1.0,
                uniform_onsite_potential=0.0,
            ),
            onsite_interaction=5.0,
        )
    """
    def __init__(self, lattice: Lattice, onsite_interaction: complex) -> None:
        """
        Args:
            lattice: Lattice on which the model is defined.
            onsite_interaction: The strength of the on-site interaction.
        """
        super().__init__(lattice)
        self._onsite_interaction = onsite_interaction
[docs]    def hopping_matrix(self) -> np.ndarray:
        """Return the hopping matrix."""
        return self.interaction_matrix() 
    @property
    def register_length(self) -> int:
        return 2 * self._lattice.num_nodes
[docs]    def second_q_op(self) -> FermionicOp:
        """Return the Hamiltonian of the Fermi-Hubbard model in terms of ``FermionicOp``.
        Returns:
            FermionicOp: The Hamiltonian of the Fermi-Hubbard model.
        """
        kinetic_ham = {}
        interaction_ham = {}
        weighted_edge_list = self._lattice.weighted_edge_list
        register_length = 2 * self._lattice.num_nodes
        # kinetic terms
        for spin in range(2):
            for node_a, node_b, weight in weighted_edge_list:
                if node_a == node_b:
                    index = 2 * node_a + spin
                    kinetic_ham[f"+_{index} -_{index}"] = weight
                else:
                    if node_a < node_b:
                        index_left = 2 * node_a + spin
                        index_right = 2 * node_b + spin
                        hopping_parameter = weight
                    elif node_a > node_b:
                        index_left = 2 * node_b + spin
                        index_right = 2 * node_a + spin
                        hopping_parameter = np.conjugate(weight)
                    kinetic_ham[f"+_{index_left} -_{index_right}"] = hopping_parameter
                    kinetic_ham[f"-_{index_left} +_{index_right}"] = -np.conjugate(
                        hopping_parameter
                    )
        # on-site interaction terms
        for node in self._lattice.node_indexes:
            index_up = 2 * node
            index_down = 2 * node + 1
            interaction_ham[
                f"+_{index_up} -_{index_up} +_{index_down} -_{index_down}"
            ] = self._onsite_interaction
        ham = {**kinetic_ham, **interaction_ham}
        return FermionicOp(ham, num_spin_orbitals=register_length, copy=False)