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

Add PolynomialTensor class #756

Merged
merged 36 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
65b173f
adding PolynomialTensor class
SaashaJoshi Jul 20, 2022
71c3591
adding unittest corresponding PolynomialTensor class; making changes …
SaashaJoshi Jul 21, 2022
91d1f89
adding conjugate and transpose functions
SaashaJoshi Jul 21, 2022
5d09484
made changes to polynomial_tensor and test_polyomial_tensor files
SaashaJoshi Aug 2, 2022
788ae81
Merge branch 'main' into poly_tensor
manoelmarques Aug 2, 2022
679d340
Update qiskit_nature/second_q/operators/polynomial_tensor.py
SaashaJoshi Aug 8, 2022
230a4f4
Merge branch 'main' into poly_tensor
manoelmarques Aug 8, 2022
7b38cc6
Merge branch 'Qiskit:main' into poly_tensor
SaashaJoshi Aug 8, 2022
d08e297
Merge "main" into poly_tensor
SaashaJoshi Aug 10, 2022
2d32bf8
updating polynomial_tensor and unittest
SaashaJoshi Aug 10, 2022
3f15fc0
Merge branch 'Qiskit:main' into poly_tensor
SaashaJoshi Aug 11, 2022
0d275e6
updating polynomial tensor and unittest
SaashaJoshi Aug 11, 2022
c84f5d2
Merge branch 'Qiskit:main' into poly_tensor
SaashaJoshi Aug 12, 2022
5b771b2
update unittest
SaashaJoshi Aug 12, 2022
92e6857
Merge branch 'Qiskit/main' into poly_tensor
SaashaJoshi Aug 23, 2022
326ac9b
updating polynomial_tensor file
SaashaJoshi Aug 23, 2022
034de66
Merge branch 'Qiskit/main' into poly_tensor
SaashaJoshi Sep 1, 2022
8bdfefc
Update qiskit_nature/second_q/operators/polynomial_tensor.py
SaashaJoshi Sep 1, 2022
094c6ef
Update qiskit_nature/second_q/operators/polynomial_tensor.py
SaashaJoshi Sep 1, 2022
4273205
Update qiskit_nature/second_q/operators/polynomial_tensor.py
SaashaJoshi Sep 1, 2022
497ae60
Update qiskit_nature/second_q/operators/polynomial_tensor.py
SaashaJoshi Sep 1, 2022
63f9ef1
Merge branch 'Qiskit/main' into poly_tensor
SaashaJoshi Sep 4, 2022
ab551e9
update polynomial_tensor and test_polynomial_tensor
SaashaJoshi Sep 4, 2022
7018443
Merge branch 'Qiskit/main' into poly_tensor
SaashaJoshi Sep 6, 2022
5c9d00b
adding register_length to polynomial tensor
SaashaJoshi Sep 6, 2022
5313c21
Merge remote-tracking branch 'upstream/main' into poly_tensor
SaashaJoshi Sep 7, 2022
0371db1
Update qiskit_nature/second_q/operators/polynomial_tensor.py
SaashaJoshi Sep 7, 2022
0e5c3f7
Merge branch 'main' into poly_tensor
woodsp-ibm Sep 12, 2022
01b8d50
changes to polynomial_tensor - adding register_length
SaashaJoshi Sep 12, 2022
468a1c3
adding equiv method to polynomial_tensor. adding unittests for dunders
SaashaJoshi Sep 13, 2022
5ab41ee
lint and mypy update
SaashaJoshi Sep 13, 2022
807eb1a
Update qiskit_nature/second_q/operators/polynomial_tensor.py
SaashaJoshi Sep 13, 2022
db03648
updating polynomial_tensor
SaashaJoshi Sep 13, 2022
7619b15
Update qiskit_nature/second_q/operators/polynomial_tensor.py
SaashaJoshi Sep 13, 2022
8344b60
update polynomial_tensor
SaashaJoshi Sep 13, 2022
c765c72
Merge branch 'main' into poly_tensor
mrossinek Sep 14, 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 qiskit_nature/second_q/operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,19 @@
SpinOp
SecondQuantizedOp
VibrationalOp
PolynomialTensor
"""

from .fermionic_op import FermionicOp
from .second_quantized_op import SecondQuantizedOp
from .spin_op import SpinOp
from .vibrational_op import VibrationalOp
from .polynomial_tensor import PolynomialTensor

__all__ = [
"FermionicOp",
"SecondQuantizedOp",
"SpinOp",
"VibrationalOp",
"PolynomialTensor",
]
194 changes: 194 additions & 0 deletions qiskit_nature/second_q/operators/polynomial_tensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# 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.

"""Polynomial Tensor class"""

from __future__ import annotations
from typing import Dict, Iterator
from collections.abc import Mapping
from numbers import Number
import numpy as np
from qiskit.quantum_info.operators.mixins import (
LinearMixin,
AdjointMixin,
TolerancesMixin,
)


class PolynomialTensor(LinearMixin, AdjointMixin, TolerancesMixin, Mapping):
"""Polynomial Tensor class"""

def __init__(self, data: Mapping[str, np.ndarray | Number], register_length: int) -> None:
"""
Args:
data: coefficient container;
mapping of string-based operator keys to coefficient matrix values.
Raises:
ValueError: when length of operator key does not match dimensions of value matrix.
ValueError: when value matrix does not have consistent dimensions.
ValueError: when some or all value matrices in ``data`` have different dimensions.
"""
copy_dict: Dict[str, np.ndarray] = {}

shapes = set()
for key, value in data.items():
if isinstance(value, Number):
value = np.asarray(value)

if len(value.shape) != len(key):
raise ValueError(
f"Data key {key} of length {len(key)} does not match "
f"data value matrix of dimensions {value.shape}"
)

dims = set(value.shape)

if len(dims) > 1:
raise ValueError(
f"For key {key}: dimensions of value matrix are not identical {value.shape}"
)

shapes.update(dims)
copy_dict[key] = value

if len(shapes) != 1:
raise ValueError("Dimensions of value matrices in data dictionary are not identical.")

self._data = copy_dict
self._register_length = register_length

@property
def register_length(self) -> int:
""" Returns register length of the operator key in `Polynomial Tensor` object """

return self._register_length

def __getitem__(self, __k: str) -> (np.ndarray | Number):
"""
Returns value matrix in the `Polynomial Tensor` object.

Args:
__k: operator key string in the `Polynomial Tensor` object
Returns:
Value matrix corresponding to the operator key `__k`
"""

return self._data.__getitem__(__k)

def __len__(self) -> int:
"""
Returns length of `Polynomial Tensor` object
"""

return self._data.__len__()

def __iter__(self) -> Iterator[str]:
"""
Returns iterator of the `Polynomial Tensor` object
"""

return self._data.__iter__()

def _multiply(self, other: complex):
"""Scalar multiplication of PolynomialTensor with complex

Args:
other: scalar to be multiplied with the ``Polynomial Tensor`` object.
Returns:
the new ``Polynomial Tensor`` product object.
Raises:
TypeError: if ``other`` is not a ``Number``.
"""

if not isinstance(other, Number):
raise TypeError(f"other {other} must be a number")

prod_dict: Dict[str, np.ndarray] = {}
for key, matrix in self._data.items():
prod_dict[key] = np.multiply(matrix, other)
return PolynomialTensor(prod_dict, self._register_length)

def _add(self, other: PolynomialTensor, qargs=None):
"""Addition of PolynomialTensors

Args:
other: second``Polynomial Tensor`` object to be added to the first.
Returns:
the new summed ``Polynomial Tensor`` object.
Raises:
TypeError: when ``other`` is not a ``Polynomial Tensor`` object.
ValueError: when values corresponding to keys in ``other`` and
the first ``Polynomial Tensor`` object do not match.
"""

if not isinstance(other, PolynomialTensor):
raise TypeError("Incorrect argument type: other should be PolynomialTensor")

sum_dict: Dict[str, np.ndarray] = self._data.copy()
for other_key, other_value in other._data.items():
if other_key in sum_dict.keys():
if other_value.shape == np.shape(sum_dict[other_key]):
sum_dict[other_key] = np.add(other_value, sum_dict[other_key])
else:
raise ValueError(
f"For key {other_key}: "
f"corresponding data value of shape {np.shape(sum_dict[other_key])} "
f"does not match other value matrix of shape {other_value.shape}"
)
else:
sum_dict[other_key] = other_value
return PolynomialTensor(sum_dict, self._register_length)

def __eq__(self, other):
"""Check equality of PolynomialTensors

Args:
other: second``Polynomial Tensor`` object to be compared with the first.
Returns:
True when ``Polynomial Tensor`` objects are equal, False when unequal.
"""

if not isinstance(other, PolynomialTensor):
return False

if self._data.keys() != other._data.keys():
return False
for key, value in self._data.items():
if not np.allclose(value, other._data[key], atol=self.atol, rtol=self.rtol):
return False
return True

def conjugate(self) -> PolynomialTensor:
"""Conjugate of PolynomialTensors

Returns:
the complex conjugate of the ``Polynomial Tensor`` object.
"""

conj_dict: Dict[str, np.ndarray] = {}
for key, value in self._data.items():
conj_dict[key] = np.conjugate(value)

return PolynomialTensor(conj_dict, self._register_length)

def transpose(self) -> PolynomialTensor:
"""Transpose of PolynomialTensor

Returns:
the transpose of the ``Polynomial Tensor`` object.
"""

transpose_dict: Dict[str, np.ndarray] = {}
for key, value in self._data.items():
transpose_dict[key] = np.transpose(value)

return PolynomialTensor(transpose_dict, self._register_length)
172 changes: 172 additions & 0 deletions test/second_q/operators/test_polynomial_tensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# 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.

"""Test for Polynomial Tensor"""

from __future__ import annotations
import unittest
from test import QiskitNatureTestCase
from ddt import ddt, idata
import numpy as np
from qiskit_nature.second_q.operators import PolynomialTensor


@ddt
class TestPolynomialTensor(QiskitNatureTestCase):
"""Tests for PolynomialTensor class"""

def setUp(self) -> None:
super().setUp()

self.og_poly = {
"": 1.0,
"+": self.build_matrix(4, 1),
"+-": self.build_matrix(4, 2),
"++--": self.build_matrix(4, 4),
}

self.sample_poly_1 = {
"": 1.0,
"++": self.build_matrix(4, 1),
"+-": self.build_matrix(4, 2),
"++--": self.build_matrix(4, 4),
}

self.sample_poly_2 = {
"": 1.0,
"+": self.build_matrix(4, 1),
"+-": self.build_matrix(4, 2),
"++--": np.arange(1, 13).reshape(1, 2, 3, 2),
}

self.sample_poly_3 = {
"": 1.0,
"+": self.build_matrix(4, 1),
"+-": self.build_matrix(2, 2),
"++--": self.build_matrix(4, 4),
}

self.sample_poly_4 = {
"": 1.0,
"+": self.build_matrix(2, 1),
"+-": self.build_matrix(2, 2),
"++--": self.build_matrix(2, 4),
}

self.expected_conjugate_poly = {
"": 1.0,
"+": self.build_matrix(4, 1).conjugate(),
"+-": self.build_matrix(4, 2).conjugate(),
"++--": self.build_matrix(4, 4).conjugate(),
}

self.expected_transpose_poly = {
"": 1.0,
"+": self.build_matrix(4, 1).transpose(),
"+-": self.build_matrix(4, 2).transpose(),
"++--": self.build_matrix(4, 4).transpose(),
}

self.expected_sum_poly = {
"": 2.0,
"+": np.add(self.build_matrix(4, 1), self.build_matrix(4, 1)),
"+-": np.add(self.build_matrix(4, 2), self.build_matrix(4, 2)),
"++--": np.add(self.build_matrix(4, 4), self.build_matrix(4, 4)),
}

@staticmethod
def build_matrix(dim_size, num_dim, val=1):
"""Build dictionary value matrix"""

return (np.arange(1, dim_size**num_dim + 1) * val).reshape((dim_size,) * num_dim)

def test_init(self):
"""Test for errors in constructor for Polynomial Tensor"""

with self.assertRaisesRegex(
ValueError,
r"Data key .* of length \d does not match data value matrix of dimensions \(\d+, *\)",
):
_ = PolynomialTensor(self.sample_poly_1, register_length=4)

with self.assertRaisesRegex(
ValueError, r"For key (.*): dimensions of value matrix are not identical \(\d+, .*\)"
):
_ = PolynomialTensor(self.sample_poly_2, register_length=4)

with self.assertRaisesRegex(
ValueError, r"Dimensions of value matrices in data dictionary are not identical."
):
_ = PolynomialTensor(self.sample_poly_3, register_length=4)

def test_get_item(self):
pass

def test_len(self):
pass

def test_iter(self):
pass

def test_register_length(self):
pass

@idata(np.linspace(0, 3, 5))
def test_mul(self, other):
"""Test for scalar multiplication"""

expected_prod_poly = {
"": 1.0 * other,
"+": self.build_matrix(4, 1, other),
"+-": self.build_matrix(4, 2, other),
"++--": self.build_matrix(4, 4, other),
}

result = PolynomialTensor(self.og_poly, 4) * other
self.assertEqual(result, PolynomialTensor(expected_prod_poly, 4))

with self.assertRaisesRegex(TypeError, r"other .* must be a number"):
_ = PolynomialTensor(self.og_poly, 4) * PolynomialTensor(self.og_poly, 4)

def test_add(self):
"""Test for addition of Polynomial Tensors"""

result = PolynomialTensor(self.og_poly, 4) + PolynomialTensor(self.og_poly, 4)
self.assertEqual(result, PolynomialTensor(self.expected_sum_poly, 4))

with self.assertRaisesRegex(
TypeError, "Incorrect argument type: other should be PolynomialTensor"
):
_ = PolynomialTensor(self.og_poly, 4) + 5

with self.assertRaisesRegex(
ValueError,
r"For key (.*): corresponding data value of shape \(\d+, *\) "
r"does not match other value matrix of shape \(\d+, *\)",
):
_ = PolynomialTensor(self.og_poly, 4) + PolynomialTensor(self.sample_poly_4, 4)

def test_conjugate(self):
"""Test for conjugate of Polynomial Tensor"""

result = PolynomialTensor(self.og_poly, 4).conjugate()
self.assertEqual(result, PolynomialTensor(self.expected_conjugate_poly, 4))

def test_transpose(self):
"""Test for transpose of Polynomial Tensor"""

result = PolynomialTensor(self.og_poly, 4).transpose()
self.assertEqual(result, PolynomialTensor(self.expected_transpose_poly, 4))


if __name__ == "__main__":
unittest.main()