unitaria.Subspace

class unitaria.Subspace(tensor_factors: list[SubspaceFactor] | str = [])[source]

Bases: object

Subspace of the statespace of a number of qubits.

In unitaria, subspaces are always the span of vectors in the computational basis, so this object just stores the indices of these computational basis states.

Constructors

If you just need a subspace with a given dimension, use Subspace.from_dim.

Otherwise, the preferred way to construct subspaces is using the string constructor. It takes a string consisting of # and 0, respectively representing a bit that can be in either state, or a bit that should be in its 0 state.

>>> import unitaria as ut
>>> ut.Subspace("#").enumerate_basis()
array([0, 1], dtype=int32)
>>> ut.Subspace("0").enumerate_basis()
array([0], dtype=int32)

Alternatively, the subspace can be constructed according to its internal representation, see below.

When given multiple symbols, they are combined using tensor products as expected.

>>> import unitaria as ut
>>> ut.Subspace("##0").enumerate_basis()
array([0, 2, 4, 6], dtype=int32)

Subspaces can be combined using the operators & (tensor product) or | direct sum.

>>> import unitaria as ut
>>> (ut.Subspace("#") & ut.Subspace("0")).enumerate_basis()
array([0, 2], dtype=int32)
>>> (ut.Subspace("#") | ut.Subspace("0")).enumerate_basis()
array([0, 1, 2], dtype=int32)

Intutively, with the | operator the highest bit decides the subspace of the lower bits. So in the example above, when the highes bit is 0 then the lower bit can either be 0 or 1, but when the highest bit is 1 then the lower bit has to be 0.

Reading the subspace

The method enumerate_basis gives a list of all the indices in the subspace, but this should typically only be used for verification. It does not make sense as part of an efficient quantum algorithm, since the complexity of enumerate_basis is always linear in the dimension.

The methods test_basis on the other hand is efficient. It checks whether a given index is in the subspace.

The dimension of the subspace can be obtained using Subspace.dimension. The dimension of the super-space can be caluclated from the number of qubits, which is stored in Subspace.total_qubits. Specifically, the super-space dimension is 2 ** subspace.total_qubits.

>>> import unitaria as ut
>>> ut.Subspace("0#0").dimension
2
>>> ut.Subspace("0#0").total_qubits
3

Internal representation

Typically one does not need to inspect Subspace objects, most properties can be derived using its methods. Internally, the object stores a decomposition of the subspace into tensor factors. This decomposition is simplified at construction and so does not have to correspond to the factors that are put in. Any of the factors can be either

  • a ZeroQubitSubspace, indicating the subspace of the space of one qubit, where this qubit is zero, or

  • a ControlledSubspace, indicating that a subspace, where the most siginificant bit determines the subspaces of the lower bits. These subspaces are given by ControlledSubspace.case_zero and ControlledSubspace.case_one. With this one can also construct the full subspace of a qubit by ControlledSubspace(Subspace(), Subspace()). (For this there is also the constant FullQubitSubspace.)

param tensor_factors:

List or string of factors making up the subspace, see below.

raises ValueError:

If a string with characters other than # or 0 is given or a list with anything that is not a SubspaceFactor

static from_dim(dim: int, bits: int | None = None) Subspace[source]

Create a subspace of a given dimension. :param dim: The dimension of the subspace :param bits: The number of qubits of the super-space, if not given, the smallest number of qubits such that the dimension fits will be used. :return: A subspace of the given dimension.

case_one() Subspace | None[source]

Returns the subspace where the highest relevant bit is one.

The highest relevant bit is the first bit that does not correspond to a zero factor, see Subspace.initial_zeros.

Returns:

The subspace, or none, if all factors are zero.

case_zero() Subspace | None[source]

Returns the subspace where the highest relevant bit is zero.

The highest relevant bit is the first bit that does not correspond to a zero factor, see Subspace.initial_zeros.

Returns:

The subspace, or none, if all factors are zero.

circuit(target: Sequence[int], flag: int, ancillae: Sequence[int]) Circuit[source]

A circuit which checks whether a state is inside the subspace.

The result of the check will be stored in a flag qubit where the index is the output of total_qubits. Specifically, this qubit will be flipped if the other qubits represent a state outside the embedded vector space.

clean_ancilla_count() int[source]
draw_tree() str[source]

Draws a tree representation of the subspace according to its internal representation. :return: A string representing the tree structure of the subspace.

enumerate_basis() ndarray[source]

Enumerates the basis states inside the subspace

initial_zeros() int[source]

Counts the number of initial factors that are zero qubits

match_nonzero(other: Subspace) bool[source]

Tests whether the nonzero factors of two subspaces are equal. :return: True if the nonzero factors are equal, False otherwise

nonzero_factors() Subspace[source]
project(vector: ndarray) ndarray[source]

Projects a state vector the embedding

test_basis(bits: int) bool[source]

Tests whether the given basis state is inside the subspace

truncate(dimension: int) Subspace[source]

Return the subspace, which contains the first dimension basis vectors of this subspace.

The number of qubits remains unchanged.

Parameters:

dimension – The dimension of the truncated subspace

Returns:

The truncated subspace

Raises:

ValueError – If dimension is not in the range (0, self.dimension].

verify_circuit()[source]
dimension[source]

The dimension of the subspace

tensor_factors: list[SubspaceFactor]
total_qubits[source]

The number of qubits of the state space in which the subspace lives

The dimension of the state space is 2 ** total_qubits