When working with quantum objects in QuTiP, the library provides the concept of a kind
, allowing the use of state vectors, dual vectors, density operators, unitary operators, and superoperators all in unified fashion. Currently, however, this support does not include representations of operator-sum decompositions of channels or instruments. For example, qt.to_kraus
returns a list of Qobj
instances, which then has no further metadata nor any way of enforcing that each consituent Qobj
instance agrees in dimensions and other metadata. Similarly, instruments can commonly be represented by a decomposition into a sum of completely positive trace non-increasing channels, but there is no current reflection of this structure in QuTiP. This design document describes a modification to QuTiP to enable first-class support for operator-sum decompositions.
In general, QuTiP differentiates between kinds
by using decompositions of array shapes into lists of subsystems. For example, a single-qubit state vector can represented by a shape of (2, 1)
and dimensions of [[2], [1]]
, representing that a vector is a function from a scalar (dims of [1]
) to a single qubit space. Similarly:
Example | Shape | Dimensions |
---|---|---|
Two-qubit dual vector | (1, 4) |
[[1], [2, 2]] |
Two-qutrit unitary operator | (9, 9) |
[[3, 3], [3, 3]] |
When representing linear functions from matrices to matrices, QuTiP implicitly uses the isomorphism that πΏ(πΏ(π΄, π΅), πΏ(πΆ, π·)) β
πΏ(π΄ β π΅, πΆ β π·). That is, linear functions from linear functions to linear functions can be represented by tensoring the input and output spaces of each constiuent space together.
For example, a density operator is a linear operator from βΒ² to βΒ², such that a function from density operators to density operators is an element of the space πΏ(πΏ(βΒ², βΒ²), πΏ(βΒ², βΒ²)). In QuTiP, we write down dimensions as [output dims, input dims]
, where here the input and output dimensions are themselves matrix-like ([[2], [2]]
), giving a final value for our dimensions of [[[2], [2]], [[2], [2]]]
.
Example | Shape | Dimensions |
---|---|---|
Single-qubit channel (linear function from πΏ(βΒ², βΒ²) β πΏ(βΒ², βΒ²) β πΏ(βΒ² β βΒ², βΒ² β βΒ³)) | (16, 16) |
[[[2], [2]], [[2], [2]]] |
Two-qutit channel (πΏ(πΏ(βΒ³ β βΒ³, βΒ³ β βΒ³), πΏ(βΒ³ β βΒ³, βΒ³ β βΒ³)) β πΏ(βΒ³ β βΒ³ β βΒ³ β βΒ³, βΒ³ β βΒ³ β βΒ³ β βΒ³)) | (81, 81) |
[[[3, 3], [3, 3]], [[3, 3]]] |
Effectively, each such specification of dimensions tells the user and other parts of the QuTiP library how to decompose the shape into tensor products of constiutent spaces, with integers standing for ββΏ with π a nonnegative integer.
Following convention that dimensions specify decompositions of Hilbert spaces into tensor products of ββΏ, we propose to extend the [output dims, input dims]
pattern in QuTiP to allow for explicitly denoting operator-sum decompositions as well. We will consider two possible alternatives for doing so.
Following this proposal, the [output_dims, input_dims]
pattern is extended to [n_terms, output_dims, input_dims]
, with lists of two entries being interpreted as [1, output, input]
. For example, a three-qubit channel with Choi rank of 3 (that is, can be specified by a minimum of three operators in a Kraus decomposition) would be represented by [3, [2, 2, 2], [2, 2, 2]]
as opposed to the full superoperator representation of [[[2, 2, 2], [2, 2, 2]], [[2, 2, 2], [2, 2, 2]]]
.
Similarly, a two-outcome measurement on a single qutrit can be represented by an instrument of dimensions [2, [[3], [3]], [[3], [3]]
.
Following this proposal, the [output_dims, input_dims]
pattern is replaced by a new dataclass qutip.spaces.L
:
TSpace = Union[
# Decomposition of a Hilbert space into indices without any further grouping.
List[int],
# Matrix-like decomposition.
# NB: Python type hints do not allow enforcing that lists have exactly two elements.
List[List[int]],
# Superoperator-like decomposition.
List[List[List[int]]],
# New-style linear function notation.
"L"
]
TDimensions = Union[
TSpace,
"OperatorSum"
]
@dataclass(init=False)
class L:
"""
The space of linear operators from `input` to `output`.
"""
output: TSpace
input: TSpace
def __init__(self, output: Union[TSpace, int], input: Union[TSpace, int, None] = None):
self.output = [output] if isinstance(output, int) else output
self.input = input if input is not None else self.output
def __mul__(self, n_terms: int) -> OperatorSum:
return OperatorSum(n_terms, self)
def __rmul__(self, n_terms: int) -> OperatorSum:
return OperatorSum(n_terms, self)
def T(output: Union[TSpace, int], input: Union[TSpace, int, None]) -> L:
"""
The space of transformations on a given vector space (that is, linear functions from
linear functions to linear functions).
For example, `T(2)` is equivalent to `L(L(2))`, which in turn is equivalent to
`L(L(2, 2), L(2, 2))`.
"""
return L(L(output, input))
@dataclass
class OperatorSum:
"""
Dimensions representing operator-sum decompositions, where operators
are each defined on a given space.
"""
n_terms: int
operator: TSpace
For example:
Example | Existing dimensions | New dimensions | kind |
---|---|---|---|
Rectangular 2Γ3 matrix | [[2], [3]] |
L([2], [3]) , L(2, 3) , or L(input=3, output=2) |
oper |
Single-qubit channel | [[[2], [2]], [[2], [2]]] |
L(L(2)) or T(2) |
super |
Two-qutrit channel | [[[3, 3], [3, 3]], [[3, 3], [3, 3]]] |
L(L([3, 3])) or T([3, 3]) |
super |
Operator-sum decomposition of three-qu5it channel with Choi rank 3 | n/a | 3 * L([5, 5, 5]) |
kraus |
Instrument with five outcomes acting on a two-qu7it space | n/a | 5 * T([7, 7]) |
instr |
- Two new
kind
values,kraus
andinstr
, would be introduced to represent operator-sum decompositions of channels and instruments, respectively. - Existing conversion functions (e.g.:
qt.to_super
,qt.to_choi
, and so forth) would be modified to accept bothkind="super"
andkind="kraus"
. - The
iscp
,istp
,ishp
, andiscptp
properties would be extended to apply tokind="kraus"
andkind="instr"
instances as well. - The existing
qt.kraus_to_*
conversion functions would be modified to acceptkind="kraus"
as well as plain lists. - The existing
qt.*_to_kraus
conversion functions would be modified to returnkind="kraus"
. (NB: How to make this non-breaking?) - The existing
__call__
method would be extended to allowkind="kraus"
. - A new
sample(input_state: Qobj) -> Tuple[int, Qobj]
method would be added forkind="instr"
, returning the measured result and the post-measurement state. - A new
istni
property would be added forkind="super"
andkind="kraus"
to allow checking for the weaker trace non-increasing condition.