Linear Algebra¶

trueq.math.
dekron
(A, dim=2, force=False)¶ This takes a matrix which can be written as
A = np.kron(a, b)
, and attempts to decompose it into(a, b)
such thata
andb
are unitary.This can also be used to find the closest unitary to a target matrix
U
in terms of the Frobenius norm:dekron(U, dim=1, force=True)[1] # This forces a decomposition into (a, b), where a.shape = (1, 1) # this means that b is the same shape as the input U
See
trueq.math.pitsianis_decomp()
for a more general method. Parameters
A (
numpy.array
) – A matrix to decompose into(a, b)
dim (
int
) – Dimension of the first submatrix a,dim=2
meansa.shape = (2, 2)
force (
bool
) – If this isTrue
, this forces a decomposition to the nearest possible(a, b)
as defined by the Frobenous norm, where(a, b)
are both unitary. IfFalse
then this will raise aDecompError
if an exact decomposition is not found.
 Returns
(a, b)  the matrices that attempt to make
A = np.kron(a, b)
 Return type
tuple
 Raises
DecompError – if it cannot find a valid decomposition if
force=False

trueq.math.
split_gate
(gate, labels=None, max_size=6)¶ Exhaustively attempts to factor the given
trueq.Gate
as a kronecker product of smaller gates. The output is atrueq.Cycle
containing these factors and which subsystems they act on; this cycle implements a unitary equal to the given gate. If atrueq.Cycle
is provided as input, then this procedure is performed on each of its gates, and single new factored cycle is returned.Some worked examples:
# Generate a 3 qubit gate object that is the product of seperate single # qubit gate operations. This is using the & shorthand for kronecker product. gate = tq.Gate.x & tq.Gate.y & tq.Gate.z # Gate({'XYZ': (1+0j)}) split_gate(gate=gate, labels=(0, 1, 2)) # Cycle((0,): Gate.x, (1,): Gate.y, (2,): Gate.z)
Another example involving 2 qubit operation between qubits (0, 2):
# Here we have a XX 90 Operation on qubits 0, 2, and a T gate on qubit 1 gate = tq.Gate.from_generators('XIX', 90) @ tq.Gate.from_generators('IZI', 45) split_gate(gate) # Cycle((0, 2): Gate(II, XX), (1,): Gate.t)
If a gate cannot be split, the returned
Cycle
will just contain the original gate with labels.A
tq.Cycle
may also be provided instead of gate, if this is done,labels
must beNone
, and the provided cycle will be altered in place instead of a new cycle being returned.Note
This is an exponentially difficult operation as the number of qudits increases. Best and Worst case tested performance on a typical desktop computer, note that these are approximate times for single gate operations, and cycle split time should be the sum of all single gate split time present in them:
# of Qubits
Best
Worst
2 Qubits
0.5 ms
0.5 ms
3 Qubits
0.9 ms
1.2 ms
4 Qubits
1.4 ms
10 ms
5 Qubits
5.3 ms
250 ms
6 Qubits
0.03 s
22 sec
It is recommended to not attempt larger than 6 qubits or qudit equivalent.
 Parameters
gate (
trueq.Gate
trueq.Cycle
) – The gate or cycle to be split into pieces if possible.labels (
tuple
list
) – The given labels associated with provided gate, if this isNone
, labels are made fromrange(gate.n_sys)
.max_size (
int
) – Spliting will only be attempted if the number of subsystems is less than this value. If the number of subsystems exceed this, the original gate is returned unsplit.
 Returns
A
trueq.Cycle
which contains the factored gates and associated labels. Return type

trueq.math.
set_cache_filename
(filename)¶ Set the filename of the shelf file, clears cache, and if the file exists loads file contents into local_cache.

trueq.math.
decompose_su4
(given_gate, target_gate, max_depth=3, rounding=6, tol=1e06)¶ Decompose a target 2 qubit
trueq.Gate
, into a layers of given 2 qubittrueq.Gate
and \(SU(2)\otimes SU(2)\) operations.decompose_su4(tq.Gate.cx, tq.Gate.swap)
This returns a
trueq.Circuit
using only 1 qubit unitaries and the provided given_gate to reconstruct the target_gate up to a global phase. Parameters
target_gate (
trueq.Gate
) – TheGate
to decompose into.given_gate (
trueq.Gate
) – TheGate
to use for the decomposition.

class
trueq.math.
QubitMode
¶ Enumerates possible qubit gate decomposition modes. A decomposition mode of ZXZ, for example, results in decompositions of the form
[("Z", angle1), ("X", angle2), ("Z", angle3)]
, where entries appear in chronological order, and nagles are specified in degreesSome decoposition modes contain 5 rotation axes, such as ZXZXZ. These modes rely on identities of the form \(R_X(\theta)=R_Z(\pi/2)R_X(\pi/2)R_Z(\pi\theta)R_X(pi/2)R_Z(pi/2)\). Therefore, these modes guarantee that the second and fourth rotation are always +90 degrees. This is useful when virtual gates are used for one rotation axis. If virtual Z gates are used, and the mode is ZXZXZ, then gates will be decomposed in the form
[("Z", angle1), ("X", 90), ("Z", angle2), ("X", 90), ("Z", angle3)]
and only one pulse shape is required to create any single qubit unitary.
property
frame_change
¶ A frame change matrix used when decomposing into this mode.
 Type
numpy.ndarray

decompose
(gate)¶ Decomposes the given gate into this mode, where the output format is a list of tuples of the form
(pauli, angle)
.>>> QubitMode.ZXZ.decompose(Gate([[0,1j],[1j,0]])) [("Z", 90), ("X", 180), ("Z", 90)]
 Return type
list

validate_factories
(factories)¶ Decides whether the given dictionary mapping single qubit Pauli strings to
GateFactory
s is sufficient to decompose this mode. It is checked that the factories are simple qubit rotations about the correct axes, and that factories that need to be able to rotate arbitrarily are able to do so. Here, “correct” means that the dictionary key is the same as the rotation axis of the factory.In addition to these checks, the input is updated to contain a key of the form, ex “X90” if the mode is ZXZXZ, whose value is a
trueq.NativeGate
that performs an X90. This is because the given factories dictionary may have a fixed X90 factory, or a parameterized Xtheta factory, and we want to avoid performing the logic to figure this out during runtime. Parameters
factories (
dict
) – dictionary mapping single qubit Pauli strings toGateFactory
s. Return type
bool
 Raises
ValueError – If the factories are not sufficient to make arbitrary qubit gates in this mode.

property

trueq.math.
pitsianis_decomp
(A, dim=2, r=None)¶ This decomposes a matrix into a sum of kronecker product matrices.
\[A = \sum_i^{i=r} B_i \otimes C_i\]Where the dimensions of the respective matrices are:
A.shape # (dim * n, dim * n) B.shape # (r, dim, dim) C.shape # (r, n, n)
The approximation for
A
is then:np.sum([np.kron(x, y) for x, y in zip(B, C)], axis=0)
 Parameters
A (
ndarray
like) – A square array with dimension of (dim * n, dim * n) to be decomposed.dim (
int
) – The dimension of the B matrix as defined above.r (
int
orNone
) – The number of terms to approximate to, if None provided, an exact decomposition is returned, and will have maximum of (dim * n) arrays.
 Returns
list
of arrays as described above.

trueq.math.
kak
¶ Decompose an arbitrary 2 qubit unitary using the KAK decomposition.
Decomposition is of the form:
np.kron(a0, a1) @ U @ np.kron(b0, b1)
where U is a unitary with hamiltonian only containing II, XX, YY, ZZ termsThis returns
(a0, a1), (b0, b1)
, and the generators for aGate
UThe original unitary can be reconstructed by:
A, B, k = kak(cnot) gen = {p: v for p, v in zip(['XX', 'YY', 'ZZ'], k])} np.kron(*A) @ tq.Gate.from_generators(gen).mat @ np.kron(*B)
This is implemented from: https://arxiv.org/pdf/quantph/0507171.pdf
 Parameters
X (
trueq.Gate
) – A Gate object Returns
(A, B, k)
, see above for a complete description

trueq.math.
is_decomposable
(U)¶ Tests if a given 4x4 matrix is decomposable into (2x2) kron (2x2) matrices.
This test is based on the fact a decomposable SU(4) matrix becomes an SO(4) matrix in the magic basis.
 Parameters
U – Unitary of shape 4x4
 Returns
if U can be decomposed into np.kron(a, b), where a, b are 2x2
 Return type
bool

trueq.math.
simultaneous_svd
(A, B)¶ Simultaneous SVD of matrices A and B.
A and B must have the same shape, and must be such that \(A^\dagger B\) and \(A B^\dagger\) are hermitian.
 Parameters
A (
numpy.ndarray
) – A matrix.B (
numpy.ndarray
) – Another matrix.
 Returns
(U, V, DA, DB)
such that \(D_A = U^\dagger A V\) and \(D_B = U^\dagger B V\).

class
trueq.math.
Frame
(frame)¶ A frame (that is, a spanning set) of a vector space.
# Make a frame with elements "U", "D" where U=[1, 0] and D=[0, 1] b = Frame({"U": [1, 0], "D": [0, 1]})
 Parameters
frame (
dict
) – a dictionary of frame elements.

shape
(n_sys)¶ The shape of the vector space spanned by tensor products of Frame operators.
 Parameters
n_sys (
int
) – the number of tensor factors. Return type
tuple

labels
(n_sys)¶ An iterator of basis element labels for the entire system. The order is the same as
elements
.b = Frame({"U": [1, 0], "D": [0, 1]) list(b.labels(2)) ["UU", "UD", "DU", "DD"]
 Parameters
n_sys (
int
) – a positive integer specifying the number of systems under consideration. Return type
map

get_element
¶ Returns the array corresponding to the given label or the kronecker product of the arrays for an iterable of labels.
b = Frame({"U": [1, 0], "D": [0, 1]}) b.get_element("D") array([0, 1]) b = Frame({"U": [1, 0], "D": [0, 1]) b.get_element("DU") array([0, 0, 1, 0])
 Parameters
label (
Iterable
) – Which element to fetch. Return type
numpy.ndarray

elements
(n_sys=1)¶ An iterator of frame elements for multiple systems. The order is the same as
basis_labels
.b = Frame({"U": [1, 0], "D": [0, 1]) list(b.elements(2)) [ array([1, 0, 0, 0]), array([0, 1, 0, 0]), array([0, 0, 1, 0]), array([0, 0, 0, 1]) ]
 Parameters
n_sys (
int
) – a positive integer specifying the number of systems under consideration. Return type
map

n_sys
(arr)¶ Determine how many systems an array acts upon.
 Parameters
arr (
arraylike
) – An array with shape equal to theshape
of this basis. Return type
int

to_coeffs
(arr)¶ Expands the given array in this frame, returning a 1D vector in the same order as
basis_labels
. This function is the inverse offrom_coeffs()
. Parameters
arr (
arraylike
) – An array with shape consistent with theshape
of this frame for some positive integer. Return type
numpy.ndarray

from_coeffs
(coeffs)¶ Takes a 1D array, interprets it as a list of coefficients with respect to this basis, and returns the array resulting from summing up these coefficients times their corresponding basis element. This function in the inverse of
to_coeffs()
. Parameters
coeffs (
arraylike
) – A 1D array of coefficients. Return type
numpy.ndarray

to_expansion
(arr)¶ Expands the given array in this basis, returning a dictionary mapping
basis_labels
to coefficients.Note
Only nonzero coefficients are normally included. However, in the case where
arr
is the zero array, instead of returning an empty dictionary, a dictionary with the first memberbasis_labels
mapped to \(0\) is returned. Parameters
arr (
arraylike
) – An array with shape consistent with theshape
of this basis for some positive integer. Return type
dict

from_expansion
(expansion)¶ Takes a dictionary mapping basis elements to coefficients and returns the array resulting from summing up these coefficients times their corresponding frame element. This function in the inverse of
to_expansion()
. Parameters
expansion (
dict
) – A dictionary mapping basis elements to coefficients. Return type
numpy.ndarray

superoperator
(arr)¶ Returns the superoperator that acts by conjugation by an array or a list of arrays on a state expanded in this frame.
 Parameters
arr (
arraylike
) – An array with shape consistent with theshape
of this basis for some positive integer or a list thereof. Return type
numpy.ndarray

sparse_superoperator
(arr)¶ Returns a sparse representation of the superoperator that acts by conjugation by an array or a list of arrays on a state expanded in this frame.
 Parameters
arr (
arraylike
) – An array with shape consistent with theshape
of this basis for some positive integer or a list thereof. Return type
dict

class
trueq.math.
OperatorTensor
(dim=2, spawn=None, value=None, dtype=None)¶ Represents an operator (e.g. unitary) or superoperator (e.g. CPTP channel) on a multipartite qudit system. This is one of the main output formats of the
Simulator
. It differs from just a pure matrix, such as anumpy.ndarray
, in that it also stores qudit label information, and attempts to store a sparse representation when the operator is not entangling between various subsystems.Depending on the types of noise in your simulator, this operator could be a unitary or a superoperator. This will affect the dimension. You can check which case you have with
OperatorTensor.is_superop
. You can get the matrix representation withOperatorTensor.mat()
.import trueq as tq # construct a circuit that makes a cat state circuit = tq.Circuit() circuit.add_cycle({(0,): tq.Gate.h}) for idx in range(4): circuit.add_cycle({(idx, idx+1): tq.Gate.cnot}) # use a simulator to fetch the unitary of this circuit as an OperatorTensor op = tq.Simulator().operator(circuit) # grab the unitary itself U = op.mat() # get the superoperator matrix representation op.upgrade() S = op.mat()
What follows is information intended for users that want to do a bit more than get the matrix representation of an operator. This class has three main distinctions from its parent class
Tensor
:The datatype is
numpy.complex128
by defaultThe
mat()
andvalue()
methods return a more convenient tensor order and shape by default.The
upgrade()
method, which transforms this operator into a superoperator mixed state by taking appropriate kroneckers and changing the input and output shapes from(d,)
to(d,d)
. Certain methods automatically callupgrade()
, for example, when a superoperator shaped matrix is given toapply_matrix()
.
import trueq as tq import numpy as np # make a unitary channel for a tensor product of qubits psi = tq.math.OperatorTensor(2) # we can manually upgrade it to a superoperator rho = psi.upgrade() # or we can automatically upgrade it to a superoperator by doing something that # requires a superoperator. here, we apply something of superoperator size, # rather than unitary size psi = tq.math.OperatorTensor(2) psi.apply_matrix((0,), np.eye(4)) # we can also create a superoperator on instatiation based on spawn/value shapes rho = tq.math.OperatorTensor(2, spawn=np.eye(4))
Note
This class does not assume that the represented channel or anything applied to it is unitary, CP, TP, unital, etc.: it is just stores and manipulates numerical arrays.
 Parameters
dim (
int
) – The Hilbert space dimension of a single subsystem.spawn (
dict
) – The default operator to set a subsystem to when it’s label is referred to, but it does not exist yet. This spawn can be operator or superoperator. See also the constructorTensor
.value – The initial value of this state, which can each be operator or superoperator. See also the constructor of
Tensor
.dtype (
type
) – The datatype of this operator.

property
is_superop
¶ Whether this tensor represents a superoporator (as opposed to an operator).
 Type
bool

property
dim
¶ The subsystem Hilbert space dimension that this operator acts on.
 Type
int

upgrade
()¶ If this operator represents a unitary, upgrades it to a tensor representing a superoperator. If this tensor already represents a superoperator, does nothing.
 Returns
This instance.
 Return type

value
(order='compositeflat')¶ Returns a dictionary mapping tuples of subsystem labels to operators on those subsystems. Operators acting on
n
subsystems will have shape(d**n,d**n)
or(d**(2*n),d**(2*n))
depending on the current value ofis_superop
. Parameters
order (
str
) – Advanced users seeTensor.value()
. Return type
dict

mat
(order='compositeflat')¶ Returns the matrix form of this operator. The tensor order is the sorted list of all present labels. If
n
subsystems are present, this matrix will have shape(d**n,d**n)
or(d**(2*n),d**(2*n))
depending on the current value ofis_superop
.If this is a superoperator, then the basis used is the composite rowstacking basis. For example, suppose you wanted to apply this superoperator to a density matrix
rho
on 3 qubits that has shape(8,8)
. Then one could use the following to get the output density matrix:# op is this OperatorTensor op.mat().dot(rho.flatten()).reshape(8, 8)
Note
Calling this method will create an array that is exponentially large in the number of subsystems, and causes any distinct subsystems found in
value
to be permanently merged; calling this method mutates the tensor’s internal representation. Parameters
order (
str
) – Advanced users seeTensor.value()
. Return type
numpy.ndarray

apply_matrix
(labels, matrix)¶ Mutates this operator by applying the given square matrix to the given labels.
If acting on \(n\) subsystems, this matrix can be a \(d^n \times d^n\) unitary, or a \(d^{2n}\times d^{2n}\) superoparator. If this operator is currently a unitary and a superoperator is applied,
upgrade()
is called. If this state is currently a superoperator and a unitary is applied, the unitary is made into a superoperator before applying.import trueq as tq import numpy as np t = tq.math.OperatorTensor(2) # apply a unitary t.apply_matrix((0, 1), tq.Gate.cnot.mat) # apply a 1% bitflip superoperator s = 0.99 * np.eye(4) + 0.01 * np.kron(tq.Gate.x, tq.Gate.x) t.apply_matrix((1,), s)
The vectorization convention of superoperators is important. Mixed states are stored in a subsystemwise rowstacked vectorization convention, and therefore superoperators need to use this convention too. See
to_rowstack_super()
. Parameters
labels (
tuple
) – Which subsystems to apply the matrix to.matrix (
numpy.ndarray
like) – A square matrix.

class
trueq.math.
StateTensor
(dim=2, spawn=None, value=None)¶ Represents a pure or mixed state on a multipartite qudit system. This is one of the main output formats of the
Simulator
. It differs from just a pure matrix, such as anumpy.ndarray
, in that it also stores qudit label information, and attempts to store a sparse representation when the state is not entangled between various subsystems.Depending on the types of noise in your simulator, this state could be pure or mixed. This will affect the dimension. You can check which case you have with
StateTensor.is_mixed
. You can get the matrix representation withStateTensor.mat()
.import trueq as tq # construct a circuit that makes a cat state circuit = tq.Circuit() circuit.add_cycle({(0,): tq.Gate.h}) for idx in range(4): circuit.add_cycle({(idx, idx+1): tq.Gate.cnot}) # use a simulator to fetch the state of this circuit as a StateTensor state = tq.Simulator().state(circuit) # grab the 1D pure state vector psi = state.mat() # get the density matrix representation state.upgrade() rho = state.mat()
What follows is information intended for users that want to do a bit more than get the matrix representation of a state. This class has three main distinctions from its parent class
Tensor
:The datatype is
numpy.complex128
The
mat()
andvalue()
methods return a more convenient tensor order and shape by default.The
upgrade()
method, which transforms this state from a pure state into a mixed state by taking appropriate outerproducts and changing the output shape from(d,)
to(d,d)
. Certain methods automatically callupgrade()
, for example, when a superoperator shaped matrix is given toapply_matrix()
.
import trueq as tq import numpy as np # make a pure state on a tensor product of qubits psi = tq.math.StateTensor(2) # we can manually upgrade it to a mixed state rho = psi.upgrade() # or we can automatically upgrade it to a mixed state by doing something that # requires a mixed state. here, we apply something of superoperator size, rather # than unitary size psi = tq.math.StateTensor(2) psi.apply_matrix((0,), np.eye(4)) # we can also create a mixed state on instatiation based on spawn/value shapes rho = tq.math.StateTensor(2, spawn=[[1, 0], [0, 0]])
Note
This class does not assume that the represented state is positive or that it has unit trace, or that applied operators are unitary, CP , TP, unital, etc.: it just stores and manipulates numerical arrays.
 Parameters
dim (
int
) – The Hilbert space dimension of a single subsystem.spawn (
dict
) – The default state to set a subsystem to when it’s label is refered to, but it does not exist yet. This spawn can be pure or mixed. See also the constructorTensor
.value – The initial value of this state, which can each be pure or mixed. See also the constructor of
Tensor
.

property
is_mixed
¶ Whether this state currently is a mixed state (as opposed to a pure state).
 Type
bool

property
dim
¶ The subsystem Hilbert space dimension of this state.
 Type
int

upgrade
()¶ If this state represents a pure state, upgrades it to a mixed state by taking outer products. If this state is already a mixed state, does nothing.
 Returns
This instance.
 Return type

value
(order='compositegroup')¶ Returns a dictionary mapping tuples of subsystem labels to states on those subsystems. If this state is currently pure, then states will be 1D vectors, and if mixed, then states will be 2D density matrices.
 Parameters
order (
str
) – Advanced users seeTensor.value()
. Return type
dict

mat
(order='compositegroup')¶ If this state is currently pure, returns the pure state vector, otherwise returns the density matrix.
Note
Note that this property will create an array that is exponentially large in the number of subsystems, and causes any distinct subsystems found in
value()
to be permanently merged; calling this attribute mutates the tensor’s internal representation. Parameters
order (
str
) – Advanced users seeTensor.value()
. Return type
numpy.ndarray

apply_matrix
(labels, matrix)¶ Mutates this state by applying the given square matrix to the given labels.
If acting on \(n\) subsystems, this matrix can be a \(d^n \times d^n\) unitary, or a \(d^{2n}\times d^{2n}\) superoparator. If this state is currently pure and a superoperator is applied,
upgrade()
is called. If this state is currently mixed and a unitary is applied, the unitary is made into a superoperator before applying.import trueq as tq import numpy as np t = tq.math.StateTensor(2) # apply a unitary t.apply_matrix((0, 1), tq.Gate.cnot.mat) # apply a 1% bitflip superoperator s = 0.99 * np.eye(4) + 0.01 * np.kron(tq.Gate.x, tq.Gate.x) t.apply_matrix((1,), s)
The vectorization convention of superoperators is important. Mixed states are stored in a subsystemwise rowstacked vectorization convention, and therefore superoperators need to use this convention too. See
to_rowstack_super()
. Parameters
labels (
tuple
) – Which subsystems to apply the matrix to.matrix (
numpy.ndarray
like) – A square matrix.

marginalize
(labels)¶ Removes the provided labels of this tensor by taking the partial trace. This will usually
upgrade()
a pure state to a mixed state.The special case of no upgrade happens when this state is pure and all the labels being tracedout are known to be unentangled with other labels (this is known when every label tuple in the keys of
value()
is either a strict subset of the given labels, or does not intersect at all with the given labels).In the case where all labels in the tensor are traced out, label
(0,)
is readded so that the total trace of the state can be preserved. Parameters
labels (
tuple
) – Which labels to remove. The order does not matter. Returns
This instance.
 Return type

probabilities
(povm=None)¶ Computes the probabilities of all possible measurement outcomes. If a Positve Operator Valued Measurement (POVM) is not provided, then these are the probabilities of computational basis measurements.
import trueq.math as tqm import numpy as np # compute probability distribution of a qubit superposition psi = tqm.StateTensor(2, value={(0,): np.array([1, 1j]) / np.sqrt(2)}) psi.probabilities() # Tensor(<[(2,), ()] on labels [(0,)]>) psi.probalities().value() # {(0,): array([0.5, 0.5])} psi.probabilities().sample(100) # Results({"0": 46, "1": 54}) # define a POVM corresponding to readout error that has a 1% chance of # flipping a 0> measurement and a 10% chance of flipping a 1> measurement p00 = 0.99 p11 = 0.9 povm = [[[p00, 0], [0, 1p11]], [[1p00, 0], [0, p11]]] povm = tqm.Tensor((2,), (2, 2), spawn=povm) psi.probabilities(povm).value() # {(0,): array([0.495, 0.005])}
 Parameters
povm (
Tensor
) – ATensor
with shape((k,), (d, d))
whered
is the Hilbert space dimension, and(k,)
is the number of possible outcomes per subsystem. Each slice[i,:,:]
should be a positive matrix, and the sum overi
should be the identity matrix, to be probability preserving. Return type
dict

class
trueq.math.
Tensor
(output_shape, input_shape=None, spawn=None, value=None, dtype=None)¶ Represents a multipartite tensor on an arbitrary number of subsystems. Each subsystem owns a number of ordered wires (or indices), which can each be input or output wires (eg. lower and upper indices in Einstein’s notation). One important restriction is that all subsystems have the same wire description: the same number, dimensions, and order for input and output wires.
Here are some motivating cases for various output and input shapes
output, input
:(d,), ()
: Pure states on a multipartite qudit system. Each subsystem has a single output wire of dimension \(d\) and no input wires (ie. a column vector). SeeStateTensor
.(d,), ()
: Categorical probability distributions on tensor product of dits. Each subsystem has a single output wire of dimension \(d\) and no input wires (ie. a column vector).(d,d), ()
: Vectorized mixed states on a multipartite qudit system. Each subsystem has two output wires each with dimension \(d\) and no input wires. The first output wire represents the output wire of the unvectorized state, and the second output wire the input wire of the unvectorized state (this is the rowstacking convention). SeeStateTensor
.(d,), (d,)
: Unitaries acting on a multipartite qudit system. Each subsystem has a single output wire of dimension \(d\) and a single input wire of dimension \(d\) (ie. a square matrix). SeeOperatorTensor
.(d,d), (d,d)
: Superoperators acting on a multipartite qudit system. Each subsystem has two input wires and two output wires, all of dimension \(d\). The two input wires act on a vectorized density matrix (or contract against another superoperator in composition). SeeOperatorTensor
.
A main feature of this class is the ability to efficiently leftmultiply square matrices onto specific subsystems (
apply_matrix()
), which can be strictly smaller than all of the subsystems present in the tensor. Therefore this class can be used to maintain the state of a simulation, and is general enough to track pure states, mixed states, unitaries, and superoperators.import trueq as tq import numpy as np t = tq.math.Tensor(2, 2, spawn=np.eye(2)) t.apply_matrix((0,2), tq.Gate.cnot.mat) t.apply_matrix((1,), tq.Gate.h.mat) # U will be the matrix corresponding to the circuit {(0, 2): cnot, (1,): h} U = t.mat().reshape(8, 8)
The internal storage format of this class is a dictionary mapping labels to numpy arrays, such as the following:
{ (0,): [[0, 1], [1, 0]], (2, 3): [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] }
In this case the label
(0,)
is storing an X gate, and the labels(2,3)
are storing a CNOT gate. Parameters
output_shape (
tuple
int
) – The output wire dimension, or a tuple of output wire dimensions. Set to an empty tuple for no output wires.input_shape (
tuple
int
) – The input wire dimension, or a tuple of input wire dimensions. Set to an empty tuple (orNone
) for no input wires.spawn (
numpy.ndarray
like) – The default value to assign to new subsystems. The size must match the provided input and output shapes. By default, this parameter is set to the tensor of all zeros, except the first element, which is 1.value (
dict
) – The initial value of the tensor, set as a dictionary mapping tuples of subsystem indexes to tensors of the correct shape.dtype – The datatype of the arrays,
numpy.float64
by default.

property
dtype
¶ The data type of this tensor.
 Type
type

property
labels
¶ Which subsystem labels of this tensor have a value.
 Type
tuple

property
n_sys
¶ The number of subsystem labels with a value
 Type
int

property
shape
¶ The output and input shape of each subsystem, eg.
((2, 2), (2, 2))
. See the argumentsinput_shape
andoutput_shape
of the constructor ofTensor
. Type
tuple

property
total_dim
¶ The total dimension of this tensor; the total subsystem dimension to the power of the number of subsystems.
 Type
int

property
spawn
¶ The arrary that is assigned to new subsystems by default.
 Type
numpy.ndarray

conj
(copy=True)¶ Takes the complex conjugate of each array in this tensor.
 Parameters
copy (
bool
) – Whether to mutate the given tensor, or to create a copy and leave the original intact. Return type

dag
(copy=True)¶ Takes the complex conjugate and transpose of each array in this tensor. Here, transpose refers to swapping the input shape with the output shape.
 Parameters
copy (
bool
) – Whether to mutate the given tensor, or to create a copy and leave the original intact. Return type

transpose
(output_perm, input_perm, copy=True)¶ Permutes wire dimensions of this tensor, possibly switching input wires with output wires.
# turn a column vector into a row vector t = Tensor((2,)) # ((2,), ()) t.transpose([], [0]).shape # ((), (2,)) # vectorize a matrix with rowstacking t = Tensor((2,), (2,)) # ((2,), (2,)) t.transpose([0, 1], []).shape # ((2, 2), ()) # vectorize a matrix with columnstacking t = Tensor((2,), (3,)) # ((2,), (3,)) t.transpose([1, 0], []).shape # ((3, 2), ()) # shuffle some wires t = Tensor((2, 3), (5,)) t.transpose([2, 1], [0]).shape # ((5, 3), (2,))
 Parameters
output_perm (
Iterable
) – A list of indices referencing positions in the joint tupleshape=output_shape+input_shape
. This value along withinput_perm
must uniquely refrence every integer0,1,...,len(shape)
.output_perm – A list of indices referencing positions in the joint tuple
output_shape+input_shape
.copy (
bool
) – Whether to mutate the given tensor, or to create a copy and leave the original intact.
 Return type

value
(order='subsystem')¶ Returns the value of this tensor: a dictionary mapping tuples of subsystem labels to array values.
The argument of this method changes the order of indexes of the arrays. For simulation performance reasons, the internal storage arranges them subsystemwise, so that all input wires for a given subsystem are contiguous, and likewise all output wires for a given subsystem are contiguous, and this is the default order of this method. This can optionally be changed so that indices are riffled over subsystems.
As an example, suppose the input shape is
(2,4)
and the output shape is(5,)
and that there are 3 subsystems in an array. Then the array will have shape(2,4,2,4,2,4,5,5,5)
if the order is"subsystem"
(default), or the array will have shape(2,2,2,4,4,4,5,5,5)
if the order is"composite"
. As a convenience, an order of"compositegroup"
flattens each group of subsystems, giving a shape(8,64,125)
in this example, and an order of"compositeflat"
flattens all inputs and all outputs separately, giving a shape(512,125)
in this example. Parameters
order (
str
) – Which order to arrange the wires, either"subsystem"
,"composite"
,"compositegroup"
, or"compositeflat"
. Or, one of"
”s”, ``"c"
,"cg"
, or cf”`` for short. Return type
dict

mat
(order='subsystem')¶ Returns the full matrix representation of this tensor, with subsystems ordered by sorted labels.
Note that this property will create an array that is exponentially large in the number of subsystems, and causes any distinct subsystems found in
value()
to be permanently merged; calling this attribute mutates the tensor’s internal representation.The argument of this method changes the order of indexes of the arrays. For simulation performance reasons, the internal storage arranges them subsystemwise, so that all input wires for a given subsystem are contiguous, and likewise all output wires for a given subsystem are contiguous, and this is the default order of this method. This can optionally be changed so that indices are riffled over subsystems.
As an example, suppose the input shape is
(2,4)
and the output shape is(5,)
and that there are 3 subsystems in an array. Then the array will have shape(2,4,2,4,2,4,5,5,5)
if the order is"subsystem"
(default), or the array will have shape(2,2,2,4,4,4,5,5,5)
if the order is"composite"
. As a convenience, an order of"compositegroup"
flattens each group of subsystems, giving a shape(8,64,125)
in this example, and an order of"compositeflat"
flattens all inputs and all outputs separately, giving a shape(512,125)
in this example. Parameters
order (
str
) – Which order to arrange the wires, either"subsystem"
,"composite"
,"compositegroup"
, or"compositeflat"
. Or, one of"
”s”, ``"c"
,"cg"
, or cf”`` for short. Return type
numpy.ndarray

add_labels
(labels)¶ Updates the number of subsystems to include labels provided, if they don’t already exist. If the labels are already present, this call doesn’t change anything.
 Parameters
labels (
list
) – A list of labels to be added to this tensor. Returns
This instance.
 Return type

upgrade
()¶ Does nothing. This is to be used by subclasses that require a mutation of shape on a call to marginalize. See, for instance,
StateTensor.upgrade()
andOperatorTensor.upgrade()
. Returns
This instance.
 Return type

marginalize
(labels)¶ Removes the provided labels of this tensor through marginalization. This is an inplace operation that mutates the tensor. Here, marginalization is defined as the sum over all tensor indices of the provied labels.
import trueq as tq import numpy as np # Make a random probability distribution on length4 bitstrings S = np.random.rand(16) p = tq.math.Tensor(2, value={(0, 1, 2, 3): S / S.sum()}) # Marginalize out two of the bitstrings p.marginalize((1, 2))
In the case where
labels
is a superset of some label invalue()
, the entire entry is removed. However, its sum is still computed and multiplied onto some other entry so that the overall sum of the tensor is preserved. In the case where all labels in the tensor are marginalized, label(0,)
is readded so that the total sum of the tensor can be preserved. Parameters
labels (
tuple
) – Which labels to remove. The order does not matter. Returns
This instance.
 Return type

update
(other)¶ Updates this tensor with some new values. If values are placed on labels that don’t exist, creates and sets them. If values are placed on labels that already exist, they are first removed by calling
marginalize()
. Parameters
other (
dict
Tensor
) – A dict likeTensor.value()
, or anotherTensor
. In either case, the size of subsystems in other needs to match this tensor. Returns
This instance.
 Return type

apply_matrix
(labels, matrix)¶ Mutates this tensor by applying the given square matrix to the given labels.
The size of the matrix must be compatible with the output shape of this tensor, just as with regular matrix multiplication, \(AB\), the number of columns of \(A\) must match the number of rows of \(B\). The rule enforced here is that the number of rows of the given matrix, when reshaped into a square, must be equal to the product if this tensors output wire dimensions. Square matrices are enforced because we cannot change the output shape on only some of the subsystems.
import trueq as tq import numpy as np t = tq.math.Tensor((2,3), (2,)) # we need a 6x6 since output shape is (2,3) t.apply_matrix((0,), np.random.randn(6, 6)) # we need a 36x36 since we are applying to 2 systems t.apply_matrix((0, 3), np.random.randn(36, 36))
 Parameters
labels (
tuple
) – Which subsystems to apply the matrix to.matrix (
numpy.ndarray
like) – A square matrix.

dot
(rhs, dtype=None, overwrite_rhs=False)¶ Contracts this tensor with another tensor, returning a new tensor. The only shape restriction is that this tensor’s input shape must match the output tensor’s output shape. The returned tensor has inputoutput shapes
(this.output_shape, rhs.input_shape)
; the inner shapes have been contracted against eachother.import trueq.math as tqm import numpy as np lhs = tqm.Tensor(5, (2, 3), value={(0, 1): np.random.randn(25, 36)}) rhs = tqm.Tensor((2, 3), 4, value={(1, 5): np.random.randn(36, 16)}) product = lhs.dot(rhs) product.shape # ((5,), (4,)) product.labels # (0, 1, 5)
 Parameters
rhs (
Tensor
) – Another tensor with a compatible shape.dtype (
numpy.dtype
) – The data type of the output tensor. If either of the inputs are complex, and the output is real, the real part is taken without warning.overwrite_rhs (
bool
) – Whether to mutaterhs
so that its final value is the product. Otherwise, a copy ofrhs
is made.
 Return type

sample
(n_shots=1, labels=None)¶ Assumes that this tensor represents a categorical probability distribution over ditstrings and samples
n_shots
from it, or a marginalized version of it iflabels
are provided.import trueq.math as tqm probs = tqm.Tensor(2, value={(0,): [0, 1], (1, 2): [0, 0.5, 0, 0.5]}) probs.sample(100) # Results({"101": 52, "111": 48}) probs.sample(100, labels=(0, 2)) # Results({"11": 100})
 Parameters
n_shots (
int
) – The number of samples to draw from the distribution.labels (
iterable
) – A list of labels to sample from.
 Return type
Results

to_results
(labels=None)¶ Assumes that this tensor represents a categorical probability distribution over ditstrings and constructs a new
Results
whose ditstring values are probabilities.import trueq.math as tqm probs = tqm.Tensor(2, value={(1,): [0, 1], (0, 2): [0, 0.5, 0, 0.5]}) probs.to_results() # Results({"011": 0.5, "111": 0.5}) probs.sample((1, 2)) # Results({"11": 1.0})
 Parameters
labels (
iterable
) – A list of labels to slice, in case a subset of all labels present in this tensor is desired. IfNone
, all labels present in the tensor are used (in sorted order). Return type
Results

trueq.math.
int_log
¶ Calculates the power of
base
that givesnum
. Parameters
num (
int
) – a positive integer.base (
int
) – a positive integer bigger than 1.
 Return type
int

trueq.math.
kron
(*arrs)¶ Returns the kronecker product of all supplied arrays. Extends
sp.kron
to accept more than two arguments. Parameters
arrs – A sequence of numpy.arrays.
 Return type
numpy.array

trueq.math.
proc_infidelity
(A, B)¶ Calculates the process infidelity between given unitaries A, B.
 Parameters
A (
numpy.array
) – Unitary of shape(d, d)
B (
numpy.array
) – Unitary of shape(d, d)
 Returns float
\(1  Tr(A^\dagger B / d)^2\)

trueq.math.
reshuffle
(mat, perm, dim=2)¶ Reshuffles the subsystems of a multipartite matrix.
For example, we can reverse the direction of a CNOT matrix applied to qubits (0,1), so that the the control qubit becomes 1 instead of 0:
# Cnot on qubit (0, 1) mat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] reshuffle(mat, [1, 0]) # array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]])
 Parameters
mat (
numpy.ndarray
like) – An arraylike object to be reshuffled, this must have an (n, n) shape, where n = dim ** len(perm).perm (
list
like) – A list of indices to permute, this must contain all integers from 0 to the number of systems in the mat.dim (
int
) – The dimension of the systems, 2 for qubits, 3 for qutrits, etc.

trueq.math.
simultaneous_diag
(*A)¶ Simultanously diagonalize a list of commuting matricies
This creates a unitary matrix U, which diagonalizes all matricies provided:
\[D[k] = U.H @ A[k] @ U\]Where
D_k
is a diagonal matrix.Note
Algorithm from:
Cardoso, JeanFrançois, and Antoine Souloumiac. “Jacobi angles for simultaneous diagonalization.” SIAM journal on matrix analysis and applications 17.1 (1996): 161164.
 Parameters
A – any number of commuting matricies
 Returns
(U, D_k)
where U is the unitary which diagonalizes all matrices AD_k
is a list of diagonal matrices as defined above

trueq.math.
to_rowstack_super
(A, B, d, n)¶ Returns the superoperator of the operation \(X\mapsto AXB^T\) in the subsystemwise row stacking basis.
See https://arxiv.org/abs/1111.6950 Section V for details on subsystemwise vs. composite stacking vectorization bases (though beware that this reference uses the column stacking convention).
 Parameters
A – The leftmultiplication operator, a square matrix.
B (
numpy.ndarray
) – The rightmultiplication operator, a matrix with the same shape as A.d (
int
) – The subsystem dimension.n (
int
) – The number of subsystems.
 Return type
numpy.ndarray