Source code for pennylane.qchem.spin

# Copyright 2018-2022 Xanadu Quantum Technologies Inc.

# 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.
"""
This module contains the functions needed for computing the spin observables.
"""
import numpy as np
from pennylane.fermi import FermiSentence, FermiWord

from .observable_hf import qubit_observable


def _spin2_matrix_elements(sz):
    r"""Builds the table of matrix elements
    :math:`\langle \bm{\alpha}, \bm{\beta} \vert \hat{s}_1 \cdot \hat{s}_2 \vert
    \bm{\gamma}, \bm{\delta} \rangle` of the two-particle spin operator
    :math:`\hat{s}_1 \cdot \hat{s}_2`.

    The matrix elements are evaluated using the expression

    .. math::

        \langle ~ (\alpha, s_{z_\alpha});~ (\beta, s_{z_\beta}) ~ \vert \hat{s}_1 &&
        \cdot \hat{s}_2 \vert ~ (\gamma, s_{z_\gamma}); ~ (\delta, s_{z_\gamma}) ~ \rangle =
        \delta_{\alpha,\delta} \delta_{\beta,\gamma} \\
        && \times \left( \frac{1}{2} \delta_{s_{z_\alpha}, s_{z_\delta}+1}
        \delta_{s_{z_\beta}, s_{z_\gamma}-1} + \frac{1}{2} \delta_{s_{z_\alpha}, s_{z_\delta}-1}
        \delta_{s_{z_\beta}, s_{z_\gamma}+1} + s_{z_\alpha} s_{z_\beta}
        \delta_{s_{z_\alpha}, s_{z_\delta}} \delta_{s_{z_\beta}, s_{z_\gamma}} \right),

    where :math:`\alpha` and :math:`s_{z_\alpha}` refer to the quantum numbers of the spatial
    function and the spin projection, respectively, of the single-particle state
    :math:`\vert \bm{\alpha} \rangle \equiv \vert \alpha, s_{z_\alpha} \rangle`.

    Args:
        sz (array[float]): spin-projection of the single-particle states

    Returns:
        array: NumPy array with the table of matrix elements. The first four columns
        contain the indices :math:`\bm{\alpha}`, :math:`\bm{\beta}`, :math:`\bm{\gamma}`,
        :math:`\bm{\delta}` and the fifth column stores the computed matrix element.

    **Example**

    >>> sz = np.array([0.5, -0.5])
    >>> print(_spin2_matrix_elements(sz))
    [[ 0.    0.    0.    0.    0.25]
     [ 0.    1.    1.    0.   -0.25]
     [ 1.    0.    0.    1.   -0.25]
     [ 1.    1.    1.    1.    0.25]
     [ 0.    1.    0.    1.    0.5 ]
     [ 1.    0.    1.    0.    0.5 ]]
    """

    n = np.arange(sz.size)

    alpha = n.reshape(-1, 1, 1, 1)
    beta = n.reshape(1, -1, 1, 1)
    gamma = n.reshape(1, 1, -1, 1)
    delta = n.reshape(1, 1, 1, -1)

    # we only care about indices satisfying the following boolean mask
    mask = np.logical_and(alpha // 2 == delta // 2, beta // 2 == gamma // 2)

    # diagonal elements
    diag_mask = np.logical_and(sz[alpha] == sz[delta], sz[beta] == sz[gamma])
    diag_indices = np.argwhere(np.logical_and(mask, diag_mask))
    diag_values = (sz[alpha] * sz[beta]).flatten()

    diag = np.vstack([diag_indices.T, diag_values]).T

    # off-diagonal elements
    m1 = np.logical_and(sz[alpha] == sz[delta] + 1, sz[beta] == sz[gamma] - 1)
    m2 = np.logical_and(sz[alpha] == sz[delta] - 1, sz[beta] == sz[gamma] + 1)

    off_diag_mask = np.logical_and(mask, np.logical_or(m1, m2))
    off_diag_indices = np.argwhere(off_diag_mask)
    off_diag_values = np.full([len(off_diag_indices)], 0.5)

    off_diag = np.vstack([off_diag_indices.T, off_diag_values]).T

    # combine the off diagonal and diagonal tables into a single table
    return np.vstack([diag, off_diag])


[docs]def spin2(electrons, orbitals): r"""Compute the total spin observable :math:`\hat{S}^2`. The total spin observable :math:`\hat{S}^2` is given by .. math:: \hat{S}^2 = \frac{3}{4}N + \sum_{ \bm{\alpha}, \bm{\beta}, \bm{\gamma}, \bm{\delta} } \langle \bm{\alpha}, \bm{\beta} \vert \hat{s}_1 \cdot \hat{s}_2 \vert \bm{\gamma}, \bm{\delta} \rangle ~ \hat{c}_\bm{\alpha}^\dagger \hat{c}_\bm{\beta}^\dagger \hat{c}_\bm{\gamma} \hat{c}_\bm{\delta}, where the two-particle matrix elements are computed as, .. math:: \langle \bm{\alpha}, \bm{\beta} \vert \hat{s}_1 \cdot \hat{s}_2 \vert \bm{\gamma}, \bm{\delta} \rangle = && \delta_{\alpha,\delta} \delta_{\beta,\gamma} \\ && \times \left( \frac{1}{2} \delta_{s_{z_\alpha}, s_{z_\delta}+1} \delta_{s_{z_\beta}, s_{z_\gamma}-1} + \frac{1}{2} \delta_{s_{z_\alpha}, s_{z_\delta}-1} \delta_{s_{z_\beta}, s_{z_\gamma}+1} + s_{z_\alpha} s_{z_\beta} \delta_{s_{z_\alpha}, s_{z_\delta}} \delta_{s_{z_\beta}, s_{z_\gamma}} \right). In the equations above :math:`N` is the number of electrons, :math:`\alpha` refer to the quantum numbers of the spatial wave function and :math:`s_{z_\alpha}` is the spin projection of the single-particle state :math:`\vert \bm{\alpha} \rangle \equiv \vert \alpha, s_{z_\alpha} \rangle`. The operators :math:`\hat{c}^\dagger` and :math:`\hat{c}` are the particle creation and annihilation operators, respectively. Args: electrons (int): Number of electrons. If an active space is defined, this is the number of active electrons. orbitals (int): Number of *spin* orbitals. If an active space is defined, this is the number of active spin-orbitals. Returns: pennylane.Hamiltonian: the total spin observable :math:`\hat{S}^2` Raises: ValueError: If electrons or orbitals is less than or equal to 0 **Example** >>> electrons = 2 >>> orbitals = 4 >>> print(spin2(electrons, orbitals)) (0.75) [I0] + (0.375) [Z1] + (-0.375) [Z0 Z1] + (0.125) [Z0 Z2] + (0.375) [Z0] + (-0.125) [Z0 Z3] + (-0.125) [Z1 Z2] + (0.125) [Z1 Z3] + (0.375) [Z2] + (0.375) [Z3] + (-0.375) [Z2 Z3] + (0.125) [Y0 X1 Y2 X3] + (0.125) [Y0 Y1 X2 X3] + (0.125) [Y0 Y1 Y2 Y3] + (-0.125) [Y0 X1 X2 Y3] + (-0.125) [X0 Y1 Y2 X3] + (0.125) [X0 X1 X2 X3] + (0.125) [X0 X1 Y2 Y3] + (0.125) [X0 Y1 X2 Y3] """ if electrons <= 0: raise ValueError(f"'electrons' must be greater than 0; got for 'electrons' {electrons}") if orbitals <= 0: raise ValueError(f"'orbitals' must be greater than 0; got for 'orbitals' {orbitals}") sz = np.where(np.arange(orbitals) % 2 == 0, 0.5, -0.5) table = _spin2_matrix_elements(sz) sentence = FermiSentence({FermiWord({}): 3 / 4 * electrons}) for i in table: sentence.update( { FermiWord( { (0, int(i[0])): "+", (1, int(i[1])): "+", (2, int(i[2])): "-", (3, int(i[3])): "-", } ): i[4] } ) sentence.simplify() return qubit_observable(sentence)
[docs]def spinz(orbitals): r"""Computes the total spin projection observable :math:`\hat{S}_z`. The total spin projection operator :math:`\hat{S}_z` is given by .. math:: \hat{S}_z = \sum_{\alpha, \beta} \langle \alpha \vert \hat{s}_z \vert \beta \rangle ~ \hat{c}_\alpha^\dagger \hat{c}_\beta, ~~ \langle \alpha \vert \hat{s}_z \vert \beta \rangle = s_{z_\alpha} \delta_{\alpha,\beta}, where :math:`s_{z_\alpha} = \pm 1/2` is the spin-projection of the single-particle state :math:`\vert \alpha \rangle`. The operators :math:`\hat{c}^\dagger` and :math:`\hat{c}` are the particle creation and annihilation operators, respectively. Args: orbitals (str): Number of *spin* orbitals. If an active space is defined, this is the number of active spin-orbitals. Returns: pennylane.Hamiltonian: the total spin projection observable :math:`\hat{S}_z` Raises: ValueError: If orbitals is less than or equal to 0 **Example** >>> orbitals = 4 >>> print(spinz(orbitals)) (-0.25) [Z0] + (0.25) [Z1] + (-0.25) [Z2] + (0.25) [Z3] """ if orbitals <= 0: raise ValueError(f"'orbitals' must be greater than 0; got for 'orbitals' {orbitals}") r = np.arange(orbitals) sz_orb = np.where(r % 2 == 0, 0.5, -0.5) table = np.vstack([r, r, sz_orb]).T sentence = FermiSentence({}) for i in table: sentence.update( { FermiWord( { (0, int(i[0])): "+", (1, int(i[1])): "-", } ): i[2] } ) sentence.simplify() return qubit_observable(sentence)