PolynomialTensor#

class PolynomialTensor(data, *, validate=True)[ソース]#

ベースクラス: LinearMixin, GroupMixin, TolerancesMixin, Mapping

A container class to store arbitrary operator coefficients.

This class generalizes the storing of operator coefficients in tensor format. Actual operators can be extracted from it using the qiskit_nature.second_q.operators.SparseLabelOp.from_polynomial_tensor() method on the respective subclasses of the SparseLabelOp.

Internally, this class stores tensors as instances of Tensor. Refer to its documentation for more details. The storage format maps from string keys to these Tensor objects. By design, no assumptions are made about the contents of the keys. However, the length of each key determines the dimension of the tensor which it maps, too. For example (using numpy arrays for the sake of simplicity):

import numpy as np

data = {}
# the empty string, maps to a 0-dimensional matrix, a single number
data[""] = 1.0
# a string of length 1, must map to a 1-dimensional array
data["+"] = np.array([1, 2])
# a string of length 2, must map to a 2-dimensional array
data["+-"] = np.array([[1, 2], [3, 4]])
# ... and so on

In general, the idea is that each character in a key will be associated with the corresponding axis of the tensor, when an operator gets built from the PolynomialTensor instance. This means, that the previous example would expand for example like so:

from qiskit_nature.second_q.operators import FermionicOp, PolynomialTensor

tensor = PolynomialTensor(data)
operator = FermionicOp.from_polynomial_tensor(tensor)

print(operator)
# Fermionic Operator
# number spin orbitals=2, number terms=7
#   1.0
# + 1 * ( +_0 )
# + 2 * ( +_1 )
# + 1 * ( +_0 -_0 )
# + 2 * ( +_0 -_1 )
# + 3 * ( +_1 -_0 )
# + 4 * ( +_1 -_1 )

Algebra

This class supports the following basic arithmetic operations: addition, subtraction, scalar multiplication, and operator multiplication. For example,

Addition

matrix = np.array([[0, 1], [2, 3]], dtype=float)
0.5 * PolynomialTensor({"+-": matrix}) + PolynomialTensor({"+-": matrix})

Operator multiplication

tensor = PolynomialTensor({"+-": matrix})
print(tensor @ tensor)

Tensor multiplication

print(tensor ^ tensor)

You can also implement more advanced arithmetic via the apply() and einsum() methods.

print(PolynomialTensor.apply(np.transpose, tensor))
print(PolynomialTensor.apply(np.conjugate, 1j * tensor))
print(PolynomialTensor.apply(np.kron, tensor, tensor))

print(PolynomialTensor.einsum({"ij,ji": ("+-", "+-", "")}, tensor, tensor))

Sparse Arrays

Furthermore, since the PolynomialTensor is building on top of the Tensor class it supports both, dense numpy arrays and sparse arrays. Since it needs to support more than 2-dimensional arrays, we rely on the sparse library.

import sparse as sp

sparse_matrix = sp.as_coo(matrix)
print(PolynomialTensor({"+-": sparse_matrix}))

One can convert between dense and sparse representation of the same tensor via the to_dense() and to_sparse() methods, respectively.

パラメータ:
  • data (Mapping[str, np.ndarray | SparseArray | complex | Tensor]) – mapping of string-based operator keys to coefficient tensor values. If the values are not already of type Tensor, they will automatically be wrapped into one.

  • validate (bool) – when set to False the data will not be validated. Disable this setting with care!

例外:
  • 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.

Attributes

atol = 1e-08#
register_length#

The size of the operator that can be generated from this PolynomialTensor.

rtol = 1e-05#

Methods

classmethod apply(function, *operands, multi=False, validate=True)[ソース]#

Applies the provided function to the common set of keys of the provided tensors.

The usage of this method is best explained by some examples:

import numpy as np
from qiskit_nature.second_q.opertors import PolynomialTensor
rand_a = np.random.random((2, 2))
rand_b = np.random.random((2, 2))
a = PolynomialTensor({"+-": rand_a})
b = PolynomialTensor({"+": np.random.random(2), "+-": rand_b})

# transpose
a_transpose = PolynomialTensor.apply(np.transpose, a)
print(a_transpose == PolynomialTensor({"+-": rand_a.transpose()}))  # True

# conjugate
a_complex = 1j * a
a_conjugate = PolynomialTensor.apply(np.conjugate, a_complex)
print(a_conjugate == PolynomialTensor({"+-": -1j * rand_a}))  # True

# kronecker product
ab_kron = PolynomialTensor.apply(np.kron, a, b)
print(ab_kron == PolynomialTensor({"+-": np.kron(rand_a, rand_b)}))  # True
# Note: that ab_kron does NOT contain the "+" and "+-+" keys although b contained the
# "+" key. That is because the function only gets applied to the keys which are common
# to all tensors passed to it.

# computing eigenvectors
hermi_a = np.array([[1, -2j], [2j, 5]])
a = PolynomialTensor({"+-": hermi_a})
_, eigenvectors = PolynomialTensor.apply(np.linalg.eigh, a, multi=True, validate=False)
print(eigenvectors == PolynomialTensor({"+-": np.eigh(hermi_a)[1]}))  # True

注釈

The provided function will only be applied to the internal arrays of the common keys of all provided PolynomialTensor instances. That means, that no cross-products will be generated.

パラメータ:
  • function (Callable[[...], ndarray | SparseArray | complex]) – the function to apply to the internal arrays of the provided operands. This function must take numpy (or sparse) arrays as its positional arguments. The number of arguments must match the number of provided operands.

  • operands (PolynomialTensor) – a sequence of PolynomialTensor instances on which to operate.

  • multi (bool) – when set to True this indicates that the provided numpy function will return multiple new numpy arrays which will each be wrapped into a PolynomialTensor instance separately.

  • validate (bool) – when set to False the data will not be validated. Disable this setting with care!

戻り値:

A new PolynomialTensor instance with the resulting arrays.

戻り値の型:

PolynomialTensor | list[qiskit_nature.second_q.operators.polynomial_tensor.PolynomialTensor]

compose(other, qargs=None, front=False)[ソース]#

Returns the matrix multiplication with another PolynomialTensor.

パラメータ:
  • other (PolynomialTensor) – the other PolynomialTensor.

  • qargs (None) – UNUSED.

  • front (bool) – If True, composition uses right matrix multiplication, otherwise left multiplication is used (the default).

例外:

NotImplementedError – when the two tensors do not have the same register_length.

戻り値:

The tensor resulting from the composition.

戻り値の型:

PolynomialTensor

注釈

Composition (&) by default is defined as left matrix multiplication for operators, while @ (equivalent to dot()) is defined as right matrix multiplication. This means that A & B == A.compose(B) is equivalent to B @ A == B.dot(A) when A and B are of the same type.

Setting the front=True keyword argument changes this to right matrix multiplication which is equivalent to the dot() method A.dot(B) == A.compose(B, front=True).

dot(other, qargs=None)#

Return the right multiplied operator self * other.

パラメータ:
  • other (Operator) – an operator object.

  • qargs (list or None) – Optional, a list of subsystem positions to apply other on. If None apply on all subsystems (default: None).

戻り値:

The right matrix multiplied Operator.

戻り値の型:

Operator

注釈

The dot product can be obtained using the @ binary operator. Hence a.dot(b) is equivalent to a @ b.

classmethod einsum(einsum_map, *operands, validate=True)[ソース]#

Applies the various Einsum convention operations to the provided tensors.

This method wraps the numpy.einsum() function, allowing very complex operations to be applied efficiently to the matrices stored inside the provided PolynomialTensor operands.

As an example, let us compute the exact exchange term of an qiskit_nature.second_q.hamiltonians.ElectronicEnergy hamiltonian:

# a PolynomialTensor containing the two-body terms of an ElectronicEnergy hamiltonian
two_body = PolynomialTensor({"++--": ...})

# an electronic density:
density = PolynomialTensor({"+-": ...})

# computes the ElectronicEnergy.exchange operator
exchange = PolynomialTensor.einsum(
    {"pqrs,qs->pr": ("++--", "+-", "+-")},
    two_body,
    density,
)
# result will be contained in exchange["+-"]

Another example is the mapping from the AO to MO basis, as implemented by the qiskit_nature.second_q.transformers.BasisTransformer.

# the one- and two-body integrals of a hamiltonian
hamiltonian = PolynomialTensor({"+-": ..., "++--": ...})

# the AO-to-MO transformation coefficients
mo_coeff = PolynomialTensor({"+-": ...})

einsum_map = {
    "jk,ji,kl->il": ("+-", "+-", "+-", "+-"),
    "prsq,pi,qj,rk,sl->iklj": ("++--", "+-", "+-", "+-", "+-", "++--"),
}

transformed = PolynomialTensor.einsum(
    einsum_map, hamiltonian, mo_coeff, mo_coeff, mo_coeff, mo_coeff
)
# results will be contained in transformed["+-"] and transformed["++--"], respectively

注釈

sparse.SparseArray supports opt_einsum.contract` if ``opt_einsum is installed. It does not support numpy.einsum. In this case, the resultant PolynomialTensor will contain all dense numpy arrays. If a user would like to work with a sparse array instead, they should install opt_einsum or they should convert it explicitly using the to_sparse() method.

パラメータ:
  • einsum_map (dict[str, tuple[str, ...]]) – a dictionary, mapping from numpy.einsum() subscripts to a tuple of strings. These strings correspond to the keys of matrices to be extracted from the provided PolynomialTensor operands. The last string in this tuple indicates the key under which to store the result in the returned PolynomialTensor.

  • operands (PolynomialTensor) – a sequence of PolynomialTensor instances on which to operate.

  • validate (bool) – when set to False the data will not be validated. Disable this setting with care!

戻り値:

A new PolynomialTensor.

戻り値の型:

PolynomialTensor

classmethod empty()[ソース]#

Constructs an empty tensor.

戻り値:

The empty tensor.

戻り値の型:

PolynomialTensor

equiv(other)[ソース]#

Check equivalence of PolynomialTensor instances.

注釈

This check only asserts the internal matrix elements for equivalence but ignores the type of the matrices. As such, it will indicate equivalence of two PolynomialTensor instances even if one contains sparse and the other dense numpy arrays, as long as their elements match.

パラメータ:

other (object) – the second PolynomialTensor object to be compared with the first.

戻り値:

True when the PolynomialTensor objects are equivalent, False when not.

戻り値の型:

bool

expand(other)[ソース]#

Returns the reverse-order tensor product with another PolynomialTensor.

パラメータ:

other (PolynomialTensor) – the other PolynomialTensor.

例外:

NotImplementedError – when the two tensors do not have the same register_length.

戻り値:

The tensor resulting from the tensor product, \(other \otimes self\).

戻り値の型:

PolynomialTensor

注釈

Expand is the opposite operator ordering to tensor(). For two tensors of the same type a.expand(b) = b.tensor(a).

get(k[, d]) D[k] if k in D, else d.  d defaults to None.#
is_dense()[ソース]#

Returns whether all matrices in this tensor are dense.

戻り値の型:

bool

is_empty()[ソース]#

Returns whether this tensor is empty or not.

戻り値の型:

bool

is_sparse()[ソース]#

Returns whether all matrices in this tensor are sparse.

戻り値の型:

bool

items() a set-like object providing a view on D's items#
keys() a set-like object providing a view on D's keys#
power(n)#

Return the compose of a operator with itself n times.

パラメータ:

n (int) – the number of times to compose with self (n>0).

戻り値:

the n-times composed operator.

戻り値の型:

Clifford

例外:

QiskitError – if the input and output dimensions of the operator are not equal, or the power is not a positive integer.

split(function, indices_or_sections, *, validate=True)[ソース]#

Splits the acted on tensor instance using the given numpy splitting function.

The usage of this method is best explained by some examples:

import numpy as np
from qiskit_nature.second_q.opertors import PolynomialTensor
rand_ab = np.random.random((4, 4))
ab = PolynomialTensor({"+-": rand_ab})

# np.hsplit
a, b = ab.split(np.hsplit, [2], validate=False)
print(a == PolynomialTensor({"+-": np.hsplit(ab, [2])[0], validate=False)}))  # True
print(b == PolynomialTensor({"+-": np.hsplit(ab, [2])[1], validate=False)}))  # True

# np.vsplit
a, b = ab.split(np.vsplit, [2], validate=False)
print(a == PolynomialTensor({"+-": np.vsplit(ab, [2])[0], validate=False)}))  # True
print(b == PolynomialTensor({"+-": np.vsplit(ab, [2])[1], validate=False)}))  # True

注釈

When splitting arrays this will likely lead to array shapes which would fail the shape validation check (as you can see from the examples above where we explicitly disable them). This is considered an advanced use case which is why the user is left to disable this check themselves, to ensure they know what they are doing.

パラメータ:
  • function (Callable[[...], ndarray | SparseArray | Number]) – the splitting function to use. This function must take a single numpy (or sparse) array as its first input followed by a sequence of indices to split on. You should use functools.partial if you need to provide keyword arguments (e.g. partial(np.split, axis=-1)). Common methods to use here are numpy.hsplit() and numpy.vsplit().

  • indices_or_sections (int | Sequence[int]) – a single index or sequence of indices to split on.

  • validate (bool) – when set to False the data will not be validated. Disable this setting with care!

戻り値:

New PolynomialTensor instances containing the split arrays.

戻り値の型:

list[qiskit_nature.second_q.operators.polynomial_tensor.PolynomialTensor]

classmethod stack(function, operands, *, validate=True)[ソース]#

Stacks the provided sequence of tensors using the given numpy stacking function.

The usage of this method is best explained by some examples:

import numpy as np
from qiskit_nature.second_q.opertors import PolynomialTensor
rand_a = np.random.random((2, 2))
rand_b = np.random.random((2, 2))
a = PolynomialTensor({"+-": rand_a})
b = PolynomialTensor({"+": np.random.random(2), "+-": rand_b})

# np.hstack
ab_hstack = PolynomialTensor.stack(np.hstack, [a, b], validate=False)
print(ab_hstack == PolynomialTensor({"+-": np.hstack([a, b], validate=False)}))  # True

# np.vstack
ab_vstack = PolynomialTensor.stack(np.vstack, [a, b], validate=False)
print(ab_vstack == PolynomialTensor({"+-": np.vstack([a, b], validate=False)}))  # True

注釈

The provided function will only be applied to the internal arrays of the common keys of all provided PolynomialTensor instances. That means, that no cross-products will be generated.

注釈

When stacking arrays this will likely lead to array shapes which would fail the shape validation check (as you can see from the examples above where we explicitly disable them). This is considered an advanced use case which is why the user is left to disable this check themselves, to ensure they know what they are doing.

パラメータ:
  • function (Callable[[...], ndarray | SparseArray | Number]) – the stacking function to apply to the internal arrays of the provided operands. This function must take a sequence of numpy (or sparse) arrays as its first argument. You should use functools.partial if you need to provide keyword arguments (e.g. partial(np.stack, axis=-1)). Common methods to use here are numpy.hstack() and numpy.vstack().

  • operands (Sequence[PolynomialTensor]) – a sequence of PolynomialTensor instances on which to operate.

  • validate (bool) – when set to False the data will not be validated. Disable this setting with care!

戻り値:

A new PolynomialTensor instance with the resulting arrays.

戻り値の型:

PolynomialTensor

tensor(other)[ソース]#

Returns the tensor product with another PolynomialTensor.

パラメータ:

other (PolynomialTensor) – the other PolynomialTensor.

例外:

NotImplementedError – when the two tensors do not have the same register_length.

戻り値:

The tensor resulting from the tensor product, \(self \otimes other\).

戻り値の型:

PolynomialTensor

注釈

The tensor product can be obtained using the ^ binary operator. Hence a.tensor(b) is equivalent to a ^ b.

注釈

Tensor uses reversed operator ordering to expand(). For two tensors of the same type a.tensor(b) = b.expand(a).

to_dense()[ソース]#

Returns a new instance where all matrices are now dense tensors.

If the instance on which this method was called already fulfilled this requirement, it is returned unchanged.

戻り値の型:

PolynomialTensor

to_sparse(*, sparse_type=<class 'qiskit_nature.second_q.operators.polynomial_tensor.COO'>)[ソース]#

Returns a new instance where all matrices are now sparse tensors.

If the instance on which this method was called already fulfilled this requirement, it is returned unchanged.

パラメータ:

sparse_type (Type[COO] | Type[DOK] | Type[GCXS]) – the type to use for the conversion to sparse matrices. Note, that this will only be applied to matrices which were previously dense tensors. Sparse arrays of another type will not be explicitly converted.

戻り値:

A new PolynomialTensor with all its matrices converted to the requested sparse array type.

戻り値の型:

PolynomialTensor

values() an object providing a view on D's values#