QuadraticHamiltonian#

class QuadraticHamiltonian(hermitian_part=None, antisymmetric_part=None, constant=0.0, *, num_modes=None, validate=True, rtol=None, atol=None)[source]#

Bases: PolynomialTensor, Hamiltonian

A Hamiltonian that is quadratic in the fermionic ladder operators.

A quadratic Hamiltonian is an operator of the form

\[\sum_{p, q} M_{pq} a^\dagger_p a_q + \frac12 \sum_{p, q} (\Delta_{pq} a^\dagger_p a^\dagger_q + \text{h.c.}) + \text{constant},\]

where \(M\) is a Hermitian matrix and \(\Delta\) is an antisymmetric matrix.

নোট

The FermionicOp class can also be used to represent any quadratic Hamiltonian. The reason to have a class specifically for quadratic Hamiltonians is that they support special numerical routines that involve performing linear algebra on the matrices \(M\) and \(\Delta\). The internal representation format of FermionicOp is not suitable for these routines.

প্যারামিটার:
  • hermitian_part (np.ndarray | None) -- The matrix \(M\) containing the coefficients of the terms that conserve particle number.

  • antisymmetric_part (np.ndarray | None) -- The matrix \(\Delta\) containing the coefficients of the terms that do not conserve particle number.

  • constant (float) -- An additive constant term.

  • num_modes (int | None) -- Number of fermionic modes. This should be consistent with hermitian_part and antisymmetric_part if they are specified.

  • validate (bool) -- Whether to validate the inputs.

  • rtol (float | None) -- Relative numerical tolerance for input validation. The default behavior is to use self.rtol.

  • atol (float | None) -- Absolute numerical tolerance for input validation. The default behavior is to use self.atol.

রেইজেস:
  • ValueError -- Either Hermitian part, antisymmetric part, or number of modes must be specified.

  • ValueError -- Hermitian part and antisymmetric part must have same shape.

  • ValueError -- Hermitian part must have shape num_modes x num_modes.

  • ValueError -- Hermitian part must be Hermitian.

  • ValueError -- Antisymmetric part must have shape num_modes x num_modes.

  • ValueError -- Antisymmetric part must be antisymmetric.

নোট

The rtol and atol arguments are only used for input validation and are discarded afterwards. They do not affect the class attributes QuadraticHamiltonian.rtol and QuadraticHamiltonian.atol.

Attributes

antisymmetric_part#

The matrix of coefficients of terms that do not conserve particle number.

atol = 1e-08#
constant#

The constant.

hermitian_part#

The matrix of coefficients of terms that conserve particle number.

num_modes#

The number of modes this operator acts on.

register_length#
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).

conserves_particle_number()[source]#

Whether the Hamiltonian conserves particle number.

রিটার্ন টাইপ:

bool

diagonalizing_bogoliubov_transform()[source]#

Return the transformation matrix that diagonalizes a quadratic Hamiltonian.

Recall that a quadratic Hamiltonian has the form

\[\sum_{p, q} M_{pq} a^\dagger_p a_q + \frac12 \sum_{p, q} (\Delta_{pq} a^\dagger_p a^\dagger_q + \text{h.c.}) + \text{constant}\]

where the \(a^\dagger_j\) are fermionic creation operations. A quadratic Hamiltonian can always be rewritten in the form

\[\sum_{j} \varepsilon_j b^\dagger_j b_j + \text{constant},\]

where the \(b^\dagger_j\) are a new set of fermionic creation operators that also satisfy the canonical anticommutation relations. These new creation operators are linear combinations of the old ladder operators. When the Hamiltonian conserves particle number (\(\Delta = 0\)) then only creation operators need to be mixed together:

\[\begin{split}\begin{pmatrix} b^\dagger_1 \\ \vdots \\ b^\dagger_N \\ \end{pmatrix} = W \begin{pmatrix} a^\dagger_1 \\ \vdots \\ a^\dagger_N \\ \end{pmatrix},\end{split}\]

where \(W\) is an \(N \times N\) unitary matrix. However, in the general case, both creation and annihilation operators are mixed together:

\[\begin{split}\begin{pmatrix} b^\dagger_1 \\ \vdots \\ b^\dagger_N \\ \end{pmatrix} = W \begin{pmatrix} a^\dagger_1 \\ \vdots \\ a^\dagger_N \\ a_1 \\ \vdots \\ a_N \end{pmatrix},\end{split}\]

where now \(W\) is an \(N \times 2N\) matrix with orthonormal rows (which satisfies additional constraints).

রিটার্নস:

  • The matrix \(W\), which is either an \(N \times N\) (when \(\Delta = 0\)) or an \(N \times 2N\) (when \(\Delta \neq 0\)) matrix

  • A numpy array containing the orbital energies \(\varepsilon_j\) sorted in ascending order

  • The constant

রিটার্ন টাইপ:

tuple[numpy.ndarray, numpy.ndarray, float]

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.#
interpret(result)#

Interprets an EigenstateResult in this hamiltonians context.

প্যারামিটার:

result (EigenstateResult) -- the result to add meaning to.

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#
majorana_form()[source]#

Return the Majorana representation of the Hamiltonian.

The Majorana representation of a quadratic Hamiltonian is

\[\frac{i}{2} \sum_{j, k} A_{jk} f_j f_k + \text{constant}\]

where \(A\) is a real antisymmetric matrix and the \(f_i\) are normalized Majorana fermion operators, which satisfy the relations:

\[ \begin{align}\begin{aligned}f_j = \frac{1}{\sqrt{2}} (a^\dagger_j + a_j)\\f_{j + N} = \frac{i}{\sqrt{2}} (a^\dagger_j - a_j)\end{aligned}\end{align} \]
রিটার্নস:

  • The matrix \(A\)

  • The constant

রিটার্ন টাইপ:

tuple[numpy.ndarray, float]

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.

second_q_op()[source]#

Convert to FermionicOp.

রিটার্ন টাইপ:

FermionicOp

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#