Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement detecting and transforming index order of two body electronic integrals #520

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4e59ea7
Implement detecting and transforming index order of two body electron…
jlapeyre Jan 26, 2022
cb8ef79
Merge branch 'main' of https://github.com/Qiskit/qiskit-nature into t…
javabster May 2, 2022
fb0eb54
Fixed typos
javabster May 2, 2022
f2cbc31
Rename utils file and first pass at enum refaactor
javabster May 2, 2022
e1f488f
:sparkles: lint :sparkles: and reno
javabster May 13, 2022
70c18c2
Added tests
javabster May 13, 2022
646e7cd
Merge branch 'main' into two-body-integral-index-transform
javabster May 13, 2022
4a36d1d
:sparkles: lint! :sparkles:
javabster May 13, 2022
86b6b09
:sparkles: lint again! :sparkles:
javabster May 13, 2022
2b24491
Updated test constants
javabster May 16, 2022
dcf4dc7
Remove comments
javabster May 16, 2022
a44e8fa
Added functions and tests for converting intermediates
javabster May 23, 2022
abe473f
:sparkles: Lint :sparkles:
javabster May 23, 2022
c5b3d58
Add error handling for unknown index
javabster May 23, 2022
4f56d27
Fix lint error
javabster May 23, 2022
0fffc8b
Added unknown index test
javabster May 24, 2022
2713697
converted tests to ddt
javabster Jun 7, 2022
69382cb
Merge branch 'main' into two-body-integral-index-transform
jlapeyre Jul 25, 2022
a138890
Merge branch 'main' into two-body-integral-index-transform
jlapeyre Aug 19, 2022
f341bd8
Merge branch 'main' into two-body-integral-index-transform
mrossinek Sep 15, 2022
6311ea4
Migrate code to new second_q stack
mrossinek Sep 15, 2022
f55155d
docs: update naming, type hints and docstrings
mrossinek Sep 15, 2022
373fe1d
refactor: move module
mrossinek Sep 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,14 @@ hcore
hdf
heidelberg
heisenberg
helgaker
hermite
hermitian
hijkls
hijs
hilbert
histidine
HJO
html
https
hubbard
Expand Down Expand Up @@ -247,6 +249,7 @@ jernigan
ji
jl
jordan
jørgensen
josé
json
jupyter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tensor_ordering
===============================================

.. automodule:: qiskit_nature.second_q.operators.tensor_ordering
:no-members:
:no-inherited-members:
:no-special-members:
8 changes: 8 additions & 0 deletions qiskit_nature/second_q/operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
SecondQuantizedOp
VibrationalOp
PolynomialTensor

Modules
-------

.. autosummary::
:toctree:

tensor_ordering
"""

from .fermionic_op import FermionicOp
Expand Down
244 changes: 244 additions & 0 deletions qiskit_nature/second_q/operators/tensor_ordering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# 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.

"""
Tensor Ordering Utilities (:mod:`qiskit_nature.second_q.operators.tensor_ordering`)
===================================================================================

.. currentmodule:: qiskit_nature.second_q.operators.tensor_ordering

Utility functions to detect and transform the index-ordering convention of two-body integrals

.. autosummary::
:toctree: ../stubs/

to_chemist_ordering
to_physicist_ordering
find_index_order
IndexType

"""

from __future__ import annotations

from enum import Enum

import numpy as np

from qiskit_nature import QiskitNatureError


def to_chemist_ordering(two_body_tensor: np.ndarray) -> np.ndarray:
"""Convert the rank-four tensor `two_body_tensor` representing two-body integrals from
physicists', or intermediate, index order to chemists' index order: i,j,k,l -> i,l,j,k

Args:
two_body_tensor: the rank-four tensor to be converted.

Returns:
The same rank-four tensor, now in chemists' index order.

Raises:
QiskitNatureError: when an unknown index type is encountered.
"""
index_order = find_index_order(two_body_tensor)
if index_order == IndexType.CHEMIST:
return two_body_tensor
if index_order == IndexType.PHYSICIST:
chem_tensor = _phys_to_chem(two_body_tensor)
return chem_tensor
if index_order == IndexType.INTERMEDIATE:
chem_tensor = _chem_to_phys(two_body_tensor)
return chem_tensor
else:
raise QiskitNatureError(
"""
Unknown index order type, input tensor must be chemists', physicists',
or intermediate index order
"""
)


def to_physicist_ordering(two_body_tensor: np.ndarray) -> np.ndarray:
"""Convert the rank-four tensor `two_body_tensor` representing two-body integrals from
chemists', or intermediate, index order to physicists' index order: i,j,k,l -> i,l,j,k

Args:
two_body_tensor: the rank-four tensor to be converted.

Returns:
The same rank-four tensor, now in physicists' index order.

Raises:
QiskitNatureError: when an unknown index type is encountered.
"""
index_order = find_index_order(two_body_tensor)
if index_order == IndexType.PHYSICIST:
return two_body_tensor
if index_order == IndexType.CHEMIST:
phys_tensor = _chem_to_phys(two_body_tensor)
return phys_tensor
if index_order == IndexType.INTERMEDIATE:
phys_tensor = _phys_to_chem(two_body_tensor)
return phys_tensor
else:
raise QiskitNatureError(
"""
Unknown index order type, input tensor must be chemists', physicists',
or intermediate index order
"""
)


def _phys_to_chem(two_body_tensor: np.ndarray) -> np.ndarray:
"""Convert the rank-four tensor `two_body_tensor` representing two-body integrals from
physicists' index order to chemists' index order: i,j,k,l -> i,l,j,k

See also `_chem_to_phys`, `_check_two_body_symmetries`.

.. note::
Denote `_chem_to_phys` by `g` and `_phys_to_chem` by `h`. The elements `g`, `h`, `I` form
a group with `gh = hg = I`, `g^2=h`, and `h^2=g`.

Args:
two_body_tensor: the rank-four tensor in physicists' to be converted.

Returns:
The same rank-four tensor, now in chemists' index order.
"""
permuted_tensor = np.einsum("ijkl->iljk", two_body_tensor)
return permuted_tensor


def _chem_to_phys(two_body_tensor: np.ndarray) -> np.ndarray:
"""Convert the rank-four tensor `two_body_tensor` representing two-body integrals from chemists'
index order to physicists' index order: i,j,k,l -> i,k,l,j

See also `_phys_to_chem`, `_check_two_body_symmetries`.

.. note::
Denote `_chem_to_phys` by `g` and `_phys_to_chem` by `h`. The elements `g`, `h`, `I` form
a group with `gh = hg = I`, `g^2=h`, and `h^2=g`.

Args:
two_body_tensor: the rank-four tensor in chemists' to be converted.

Returns:
The same rank-four tensor, now in physicists' index order.
"""
permuted_tensor = np.einsum("ijkl->iklj", two_body_tensor)
return permuted_tensor


def _check_two_body_symmetry(two_body_tensor: np.ndarray, permutation: str) -> bool:
"""Return whether the provided tensor remains identical under the provided permutation.

Args:
two_body_tensor: the tensor to test.
permutation: the einsum permutation to apply.

Returns:
Whether the tensor remains unchanged under the applied permutation.
"""
permuted_tensor = np.einsum(permutation, two_body_tensor)
return np.allclose(two_body_tensor, permuted_tensor)


def _check_two_body_symmetries(two_body_tensor: np.ndarray, chemist: bool = True) -> bool:
"""Return whether a tensor has the required symmetries to represent two-electron terms.

Return `True` if the rank-4 tensor `two_body_tensor` has the required symmetries for
coefficients of the two-electron terms. If `chemist` is `True`, assume the input is in
chemists' order, otherwise in physicists' order.

If `two_body_tensor` is a correct tensor of indices, with the correct index order, it must pass
the tests. If `two_body_tensor` is a correct tensor of indices, but the flag `chemist` is
incorrect, it will fail the tests, unless the tensor has accidental symmetries. This test may be
used with care to discriminate between the orderings.

References: HJO Molecular Electronic-Structure Theory (1.4.17), (1.4.38)

See also `_phys_to_chem`, `_chem_to_phys`.

Args:
two_body_tensor: the tensor to test.
chemist: whether to assume that the tensor is in chemists' order.

Returns:
Whether the tensor has the required symmetries to represent two-electron terms.
"""
if not chemist:
two_body_tensor = _phys_to_chem(two_body_tensor)
for permutation in _ChemIndexPermutations:
if not _check_two_body_symmetry(two_body_tensor, permutation.value):
return False
return True


def find_index_order(two_body_tensor: np.ndarray) -> IndexType:
"""Return the index-order convention of the provided rank-four tensor.

The index convention is determined by checking symmetries of the tensor.
If the indexing convention can be determined, then one of :class:`IndexType.CHEMIST`,
:class:`IndexType.PHYSICIST`, or :class:`IndexType.INTERMEDIATE` is returned. The
:class:`IndexType.INTERMEDIATE` indexing may be obtained by applying :meth:`_chem_to_phys` to
the physicists' convention or :meth:`_phys_to_chem` to the chemists' convention. If the tests
for each of these conventions fail, then :class:`IndexType.UNKNOWN` is returned.

See also `_chem_to_phys`, `_phys_to_chem`.

.. note::
The first of :class:`IndexType.CHEMIST`, :class:`IndexType.PHYSICIST`, and
:class:`IndexType.INTERMEDIATE`, in that order, to pass the tests is returned. If
`two_body_tensor` has accidental symmetries, it may in fact satisfy more than one set of
symmetry tests. For example, if all elements have the same value, then the symmetries for all
three index orders are satisfied.

Args:
two_body_tensor: the rank-four tensor whose index order to determine.

Returns:
The index order of the provided rank-four tensor.
"""
if _check_two_body_symmetries(two_body_tensor):
return IndexType.CHEMIST
permuted_tensor = _phys_to_chem(two_body_tensor)
if _check_two_body_symmetries(permuted_tensor):
return IndexType.PHYSICIST
permuted_tensor = _phys_to_chem(permuted_tensor)
if _check_two_body_symmetries(permuted_tensor):
return IndexType.INTERMEDIATE
else:
return IndexType.UNKNOWN


class _ChemIndexPermutations(Enum):
"""This ``Enum`` defines the permutation symmetries satisfied by a rank-4 tensor of real
two-body integrals in chemists' index order, naming each permutation in order of appearance
in Molecular Electronic Structure Theory by Helgaker, Jørgensen, Olsen (HJO)."""

PERM_1 = "pqrs->rspq" # HJO (1.4.17)
PERM_2_AB = "pqrs->qprs" # HJO (1.4.38)
PERM_2_AC = "pqrs->pqsr" # HJO (1.4.38)
PERM_2_AD = "pqrs->qpsr" # HJO (1.4.38)
PERM_3 = "pqrs->rsqp" # PERM_2_AB and PERM_1
PERM_4 = "pqrs->srpq" # PERM_2_AC and PERM_1
PERM_5 = "pqrs->srqp" # PERM_2_AD and PERM_1


class IndexType(Enum):
"""This ``Enum`` names the different permutation index orders that could be encountered."""

CHEMIST = "chemist"
PHYSICIST = "physicist"
INTERMEDIATE = "intermediate"
UNKNOWN = "unknown"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
Adds utility functions for converting between chemists' and physicists' index ordering
for two-body integrals. :meth:`qiskit_nature.second_q.operators.tensor_ordering.to_chemist_ordering`
converts from physicists' or intermediate order to chemists', whereas
:meth:`qiskit_nature.second_q.operators.tensor_ordering.to_physicist_ordering` converts to physicists'.
:meth:`qiskit_nature.second_q.operators.tensor_ordering.find_index_order` returns the
index-order type for a given two-body tensor.
Loading