Math¶
Decompose a target two-qubit |
|
Finds two unitary matrices whose tensor product is closest to the specified matrix. |
|
Enumerates possible qubit gate decomposition modes. |
|
Exhaustively attempts to reduce the given |
|
A frame (that is, a spanning set) of a vector space. |
|
Returns a new unitary where extra dimensions have been added to every subsystem and the given unitary has been embedded into the lowest energy levels. |
|
Returns the integer solution to |
|
Returns the integer logarithm of |
|
Returns the Kronecker product of all supplied arrays (two or more). |
|
Calculates the process infidelity between two unitary matrices. |
|
Reshuffles the subsystems of a multi-partite matrix. |
|
Decompose an arbitrary two-qubit unitary matrix using the KAK decomposition. |
|
Finds a gate inside of a |
|
Returns a random CPTP superoperator matrix drawn from the BCSZ distribution ([16]). |
|
Returns a random density matrix drawn from the trace-normalized Ginibre ensemble of the given rank. |
|
Returns a Haar-random unitary matrix of size |
|
A fixed unitary rotation. |
|
Parent class for |
|
A representation of a rotation of the form \(exp^{-i \cdot G \cdot \text{param}}\) for some Hermitian matrix \(G\). |
|
Represents a quantum superoperator. |
|
Represents an operator (e.g., unitary) or superoperator (e.g., CPTP channel) on a multipartite qubit system. |
|
Represents a pure or mixed state on a multipartite qubit system. |
|
Represents a multipartite tensor on an arbitrary number of subsystems. |
|
Returns the superoperator of the operation \(X\mapsto AXB^T\) in the subsystem-wise row stacking basis. |
Decomposition tools¶
-
trueq.math.decomposition.
decompose_su4
(given_gate, target_gate, max_depth=3, tol=1e-08)¶ Decompose a target two-qubit
Gate
into alternating cycles of local operations and the given two-qubitGate
.import trueq as tq circuit = tq.math.decompose_su4(tq.Gate.cx, tq.Gate.swap) circuit.draw()
- Parameters
- Returns
A circuit that implements the target gate up to a global phase.
- Return type
-
trueq.math.decomposition.
dekron
(A, dim=2, force=False)¶ Finds two unitary matrices whose tensor product is closest to the specified matrix.
This can also be used to find the closest unitary to a matrix in terms of the Frobenius norm.
import numpy as np import trueq as tq U = np.diag([1, 1, -1, -1]) print(tq.math.dekron(U, dim=2, force=True)) print(tq.math.dekron(U, dim=1, force=True))
(array([[ 1.+0.j, 0.+0.j], [ 0.+0.j, -1.+0.j]]), array([[1.+0.j, 0.+0.j], [0.+0.j, 1.+0.j]])) (array([[-1.+0.j]]), array([[-1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], [ 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j], [ 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j], [ 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j]]))
- Parameters
A (
numpy.array
) – A matrix to decompose into a tensor product of two unitary matrices.dim (
int
) – Dimension of the first square submatrixa
.force (
bool
) – Whether to force a decomposition to the nearest tensor factors (True
) or raise an error if no exact decomposition is found (False
).
- Returns
The closest unitary tensor factors of the input matrix.
- Return type
tuple
- Raises
DecompError – if
force=False
and no valid decomposition is found.
-
class
trueq.math.decomposition.
QubitMode
(value)¶ 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.-
decompose
(gate)¶ Decomposes the given gate into this mode, where the output format is a list of tuples of the form
(pauli, angle)
.import trueq as tq tq.math.QubitMode.ZXZ.decompose(tq.Gate.y)
[('Z', 0.0), ('X', 180.0), ('Z', 180.0)]
- Returns
A list of axes and angles to generate the gate from this mode.
- Return type
list
-
-
trueq.math.decomposition.
split_gate
(gate, labels=None, max_size=6)¶ Exhaustively attempts to reduce the given
Gate
orCycle
into a cycle containing gates that act on as few systems as possible.import trueq as tq # 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 print(tq.math.split_gate(gate=gate, labels=(0, 1, 2))) # Make a 90 degree rotation about XX on qubits (0, 2) and a T gate on qubit 1 gate = tq.Gate.from_generators('XIX', 90) @ tq.Gate.from_generators('IZI', 45) print(tq.math.split_gate(gate, labels=(0, 1, 2)))
Cycle((0,): Gate.x, (1,): Gate.y, (2,): Gate.z) Cycle((0, 2): Gate(XX), (1,): Gate.t)
Note
The cost of this function scales exponentially with the number of qubits. Below are best- and worst-case runtimes on a typical desktop computer for a single gate. Setting max_size to be greater than 6 for qubits could result in extremely long delays.
# 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
- Parameters
gate (
Gate
|Cycle
) – The gate or cycle to be split into pieces if possible.labels (
tuple
|list
) – The given labels associated with the provided gate. Must be omitted if the input is a cycle.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
Cycle
containing the factored gates and associated labels.- Return type
Frame¶
-
class
trueq.math.frame.
Frame
(frame)¶ A frame (that is, a spanning set) of a vector space.
import trueq as tq # Make a frame with elements "U", "D" where U=[1, 0] and D=[0, 1] b = tq.math.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 the frame operators.
- Parameters
n_sys (
int
) – the number of tensor factors.- Returns
The shape of the vector space spanned by tensor products of the frame operators.
- Return type
tuple
-
labels
(n_sys)¶ Returns an iterator of basis element labels for multiple systems. The order is the same as
elements
.import trueq as tq b = tq.math.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.- Returns
An iterator over basis element labels for multiple systems.
- Return type
map
-
get_element
(label)¶ Returns the array corresponding to the given label or the kronecker product of the arrays for an iterable of labels.
import trueq as tq b = tq.math.Frame({"U": [1, 0], "D": [0, 1]}) print(b.get_element("D")) print(b.get_element("DU"))
[0.+0.j 1.+0.j] [0.+0.j 0.+0.j 1.+0.j 0.+0.j]
- Parameters
label (
Iterable
) – Which element to fetch.- Returns
The array for a given basis element.
- Return type
numpy.ndarray
-
elements
(n_sys=1)¶ An iterator of frame elements for multiple systems. The order is the same as
labels
.import trueq as tq b = tq.math.Frame({"U": [1, 0], "D": [0, 1]}) list(b.elements(2))
[array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]), array([0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]), array([0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j]), array([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j])]
- Parameters
n_sys (
int
) – a positive integer specifying the number of systems under consideration.- Returns
An iterator of the frame elements for multiple systems.
- Return type
map
-
n_sys
(arr)¶ Determine how many systems an array acts on.
- Parameters
arr (
array-like
) – An array with shape equal to theshape
of this basis.- Returns
The number of systems an array acts on.
- Return type
int
-
to_coeffs
(arr)¶ Expands the given array in this frame, returning a 1D vector in the same order as
labels
. This function is the inverse offrom_coeffs()
.- Parameters
arr (
array-like
) – An array with shape consistent with theshape
of this frame for some positive integer.- Returns
A vector of expansion coefficients.
- 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 (
array-like
) – A 1D array of coefficients.- Returns
The matrix specified by a vector of expansion coefficients.
- Return type
numpy.ndarray
-
to_expansion
(arr)¶ Expands the given array in this basis, returning a dictionary mapping
labels
to coefficients.Note
Only non-zero 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 memberlabels
mapped to \(0\) is returned.- Parameters
arr (
array-like
) – An array with shape consistent with theshape
of this basis for some positive integer.- Returns
A sparse representation of
arr
with respect to this frame.- 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.- Returns
A dense matrix from a sparse set of expansion coefficients.
- Return type
numpy.ndarray
-
plot_arr
(arr, ax=None)¶ Plots the given array as the coefficients in this basis/frame.
- Parameters
arr (
array-like
) – An array with shape consistent with theshape
of this basis for some positive integer or a list thereof.ax (
matplotlib.Axis
) – An existing axis to plot on. If not given, a new figure will be created.
-
plot_superoperator
(arr, ax=None)¶ Plots the superoperator that acts by conjugation by an array or a list of arrays on a state expanded in this frame.
- Parameters
arr (
array-like
) – An array with shape consistent with theshape
of this basis for some positive integer or a list thereof.ax (
matplotlib.Axis
) – An existing axis to plot on. If not given, a new figure will be created.
-
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 (
array-like
) – An array with shape consistent with theshape
of this basis for some positive integer or a list thereof.- Returns
A matrix representation of a superoperator with respect to this frame.
- 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 (
array-like
) – An array with shape consistent with theshape
of this basis for some positive integer or a list thereof.- Returns
A sparse representation of a superoperator with respect to this frame.
- Return type
dict
-
frame.
pauli_basis
= <trueq.math.frame.Frame object>¶
General tools¶
-
trueq.math.general.
embed_unitary
(u, n_sys, extra_dims)¶ Returns a new unitary where extra dimensions have been added to every subsystem and the given unitary has been embedded into the lowest energy levels. The returned unitary acts trivially on the extra levels.
import trueq as tq # inject the CNOT gate into two qutrits cnot3 = tq.math.embed_unitary(tq.Gate.cnot.mat, 2, 1) tq.visualization.plot_mat(cnot3, xlabels=3, ylabels=3)
- Parameters
u (
numpy.ndarray
-like) – The unitary matrix to embed.n_sys (
int
) – The number of subsystems the unitary acts on.extra_dims – The number of extra dimensions to add to each subsystem.
- Returns
An embedding of
u
into a larger space.- Return type
int
-
trueq.math.general.
int_base
(x, n)¶ Returns the integer solution to
base ** n == x
. ReturnsNone
when no exact solution exists to numerical precision.print(int_base(16, 4)) print(logint(16, 2)) print(logint(16, 3))
- Parameters
x (
int
) – The positive integer to find the base of.n – A positive integer; the power the base should be raised to get
x
.
- Returns
The integer solution to
base ** n == x
.- Return type
int
|None
-
trueq.math.general.
int_log
(x, base)¶ Returns the integer logarithm of
x
in the givenbase
. ReturnsNone
when no exact solution exists to numerical precision.import trueq as tq print(tq.math.int_log(16, 2)) print(tq.math.int_log(16, 4)) print(tq.math.int_log(16, 3))
4 2 None
- Parameters
x (
int
) – The positive integer to compute the logarithm of.base (
int
) – The base of the logarithm.
- Returns
The integer logarithm in a specified base.
- Return type
int
|None
-
trueq.math.general.
kron
(*arrs)¶ Returns the Kronecker product of all supplied arrays (two or more). Note that this function does not work with numpy.complex128 data type.
- Parameters
arrs (
tuple
) – A tuple of numpy.ndarrays.- Returns
The Kronecker product of the specified arrays.
- Return type
numpy.ndarray
-
trueq.math.general.
proc_infidelity
(A, B)¶ Calculates the process infidelity between two unitary matrices.
- Parameters
A (
numpy.ndarray
) – A unitary matrix of shape(d, d)
.B (
numpy.ndarray
) – A unitary matrix of shape(d, d)
.
- Returns
The process infidelity between the two unitary matrices.
- Return type
float
-
trueq.math.general.
reshuffle
(mat, perm, dim=2)¶ Reshuffles the subsystems of a multi-partite matrix.
import numpy as np import trueq as tq # Cnot on qubits (0, 1) mat = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) # We can reverse the direction of the CNOT matrix so that the control qubit # becomes 1 instead of 0: tq.visualization.plot_mat(tq.math.reshuffle(mat, [1, 0]), xlabels=2, ylabels=2)
- Parameters
mat (
numpy.ndarray
) – Anumpy.ndarray
object to be reshuffled, which must have an(n, n)
shape wheren = dim ** len(perm)
.perm (
Iterable
) – A list of indices to permute which 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.
- Returns
The reshuffled matrix.
- Return type
numpy.ndarray
KAK tools¶
-
trueq.math.kak_utils.
kak
(X)¶ Decompose an arbitrary two-qubit unitary matrix using the KAK decomposition.
The decomposition is of the form
np.kron(a0, a1) @ U @ np.kron(b0, b1)
whereU
is a unitary matrix with hamiltonian only containing"II"
,"XX"
,"YY"
, and"ZZ"
terms.This returns
(a0, a1), (b0, b1)
, and the generators for aGate
"U"
.The original unitary can be reconstructed as follows.
import trueq as tq A, B, k = tq.math.kak(tq.Gate.cnot) gen = {p: v for p, v in zip(['XX', 'YY', 'ZZ'], k)} tq.Gate(np.kron(*A) @ tq.Gate.from_generators(gen).mat @ np.kron(*B))
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".- Name:
-
- Gate.cx
- Aliases:
-
- Gate.cx
- Gate.cnot
- Likeness:
-
- CNOT
- Generators:
-
- 'IX': 90.0
- 'ZI': 90.0
- 'ZX': -90.0
- Matrix:
-
-
-0.71 -0.71j -0.71 -0.71j -0.71 -0.71j -0.71 -0.71j
-
- Parameters
X (
Gate
) – A two-qubit gate.- Returns
A decomposition of the input.
- Return type
tuple
-
trueq.math.kak_utils.
find_kak_equivalent
(conf, gate, param_scaling=180, tol=0.01)¶ Finds a gate inside of a
Config
that is equivalent to the target gate up to single-qubit operations.- Parameters
conf (
Config
) – AConfig
object containing multi-qubit gates.gate (
Gate
) – The target gate to find inside the config.param_scaling (
float
) – Upper bound on the parameter range that is searched to match parameter values for parametrized gates.tol (
float
) – Tolerance to decide if convergence was successful, success is defined as the L1 distance between target KAK and found KAK is less than the tol.
- Returns
a list of
Gate
s that are equivalent to the target.- Return type
list
Random Generators¶
-
trueq.math.random.
random_bcsz
(dim, rank=None)¶ Returns a random CPTP superoperator matrix drawn from the BCSZ distribution ([16]). The superoperator is in the global row stacking basis.
- Parameters
dim (
int
) – The dimension of the Hilbert space matrix.rank (
int
) – A positive integer specifying the output Choi rank. IfNone
, then the valuedim ** 2
is used.
- Returns
A matrix of shape
(dim**2, dim**2)
.- Return type
numpy.ndarray
-
trueq.math.random.
random_density
(dim, rank=1)¶ Returns a random density matrix drawn from the trace-normalized Ginibre ensemble of the given rank. By default, the rank is one so that this function returns random pure states.
- Parameters
dim (
int
) – The dimension of the density matrix.rank (
int
) – A positive integer specifying the rank of the output density matrix.
- Returns
A random density matrix of a specified matrix rank.
- Return type
numpy.ndarray
-
trueq.math.random.
random_unitary
(dim)¶ Returns a Haar-random unitary matrix of size
(dim, dim)
.- Parameters
dim (
int
) – The size of the unitary matrix.- Returns
A Haar-random unitary matrix.
- Return type
numpy.ndarray
Rotations¶
-
trueq.math.rotation.
FixedRotation
(mat)¶ A fixed unitary rotation.
import trueq as tq import numpy as np from trueq.math import FixedRotation g = FixedRotation(tq.Gate.x.mat) assert g.pauli_angle == ("X", 180)
- Parameters
mat (
numpy.array
) – A Unitary matrix.
-
trueq.math.rotation.
Layer
()¶ Parent class for
Rotation
andFixedRotation
.
-
trueq.math.rotation.
Rotation
(gen, param_name)¶ A representation of a rotation of the form \(exp^{-i \cdot G \cdot \text{param}}\) for some Hermitian matrix \(G\).
import trueq as tq import numpy as np from trueq.math import Rotation g = Rotation(tq.Gate.x.mat / 2, "theta") assert tq.Gate(g(np.pi / 2)) == tq.Gate.cliff5 pauli, const = g.pauli_const assert pauli == "X" assert abs(const - 180 / np.pi) < 1e-12
- Parameters
gen (
numpy.array
) – A Hermitian matrix to generate this rotation.param_name (
str
) – The name associated with theparam
, e.g.theta
,phi
,t
, etc.
Superoperators¶
-
class
trueq.math.superop.
Superop
(mat)¶ Represents a quantum superoperator. A superoperator is a linear operation that maps density matrices to density matrices. The main utility of this class is converting between the various superoperator representations such as Kraus operators, Choi matrices, and Pauli transfer matrices.
Generally, instances of this class should be created by calling one of the static methods starting with
from_
. For example:import numpy as np import trueq as tq superop = tq.math.Superop.from_kraus([np.eye(2)])
Note
This class is in support of future features, and is not currently connected to other parts of the library. However, it is still fully functional as a stand-alone utility, and can be used, for instance, to generate inputs to the simulator method
add_kraus
.- Parameters
mat (
numpy.ndarray
-like) – A superoperator matrix in the row-stacking basis.
-
property
dim
¶ The Hilbert space dimension that this superoperator acts on. For example, this is equal to 4 if this superoperator acts on \(4 \times 4\) density matrices.
- Type
int
-
property
choi_rank
¶ The rank of the Choi matrix of this superoperator, that is, the minimal number of Kraus operators required to describe this superoperator.
- Type
int
-
property
is_tp
¶ Whether this superoperator is trace preserving (TP), i.e. whether the trace of output density matrices is always equal to the trace of input density matrices.
- Type
bool
-
property
is_unital
¶ Whether this superoperator is unital, i.e. whether it maps the completely mixed state to the completely mixed state.
- Type
bool
-
property
is_cp
¶ Whether this superoperator is completely positive (CP), i.e. whether this superoperator always outputs positive states when given positive states (even when this superoperator is tensored with an ancilla space).
- Type
bool
-
property
is_cptp
¶ Whether this superoperator is completely positive and trace-preserving (CPTP). See also
is_cp
andis_tp
.- Type
bool
-
property
avg_gate_fidelity
¶ The average gate fidelity of this superoperator, defined by
\[\overline{F} = \int d\psi \langle\psi|\mathcal{E}(|\psi\rangle\langle\psi|)|\psi\rangle\]for the superoperator \(\mathcal{E}\). This is related to the process fidelity \(F_E\) (see
fidelity
) via \(\overline{F}=(d^2F_E+d)/(d(d+1))\) where \(d\) is the dimension of the underlying Hilbert space.import trueq.math as tqm s = tqm.Superop(tqm.random_bcsz(3)) u = tqm.Superop.from_unitary(tqm.random_unitary(3)) # find the average gate fidelity between these superoperators (s @ u.adj).avg_gate_fidelity
0.38594364414695476
- Type
float
-
property
avg_gate_infidelity
¶ The average gate infidelity of this superoperator, often denoted as \(r\), equal to one minus
avg_gate_fidelity
; see the definition there.import trueq.math as tqm s = tqm.Superop(tqm.random_bcsz(3)) u = tqm.Superop.from_unitary(tqm.random_unitary(3)) # find the average gate infidelity between these superoperators (s @ u.adj).avg_gate_infidelity
0.6810401955012966
- Type
float
-
property
coherent_infidelity
¶ The coherent infidelity of this superoperator, defined by
\[e_U = e_F - e_S\]for the superoperator \(\mathcal{E}\), where \(e_F\) is the process
infidelity
and \(e_S\) is thestochastic_infidelity
. This quantity ranges between 0 and the processinfidelity
of this superoperator, where a larger value indicates more coherent (i.e. unitary or calibration) noise. This quantity is reported by the XRB protocol when SRB results are also present to estimate \(e_F\).- Type
float
-
property
dnorm
¶ The diamond norm of this superoperator.
import trueq as tq # ideal gate s1 = tq.math.Superop.from_unitary(tq.Gate.cnot.mat) # gate simulated with depolarizing sim = tq.Simulator().add_depolarizing(0.01) s2 = sim.operator(tq.Cycle({(0,1): tq.Gate.cnot})).mat() s2 = tq.math.Superop.from_rowstack(s2) # compute the diamond norm of the difference (see the note below for why # this is commented out) # (s1 - s2).dnorm
Note
This is computed via Watrous’ semidefinite program, see [17].
Note
This function requires
cvxpy
andcvxopt
to be properly installed. In particular, their dependencyscs
must also be installed, which also requiresblas
andlapack
libraries to be installed. It may take some effort to get this all set up in your environment, hence the example above hasdnorm
commented out so that the documentation will build.- Type
float
-
property
fidelity
¶ The process fidelity of this superoperator, which can be defined as
\[F_E = \operatorname{Tr}(\mathcal{E})/d^2\]for the superoperator \(\mathcal{E}\) where \(d\) is the dimension of the underlying Hilbert space. This quantity is related to the average gate fidelity \(\overline{F}\) (see
avg_gate_fidelity
) via \(\overline{F}=(d^2F_E+d)/(d(d+1))\).import trueq.math as tqm s = tqm.Superop(tqm.random_bcsz(3)) u = tqm.Superop.from_unitary(tqm.random_unitary(3)) # find the process fidelity between these superoperators (s @ u.adj).fidelity
0.14112671554144154
- Type
float
-
property
infidelity
¶ The process infidelity of this superoperator, defined as \(e_F=1-F_E\) where \(F_E\) is the process
fidelity
. This quantity is estimated by protocols like SRB and CB.import trueq.math as tqm s = tqm.Superop(tqm.random_bcsz(3)) u = tqm.Superop.from_unitary(tqm.random_unitary(3)) # find the process infidelity between these superoperators (s @ u.adj).infidelity
0.9196890061959228
- Type
float
-
norm
(p=2)¶ Returns the Schatten p-norm of this superoperator, equal to
\[\|\mathcal{E}\|_p = ( \operatorname{Tr}[(\mathcal{E}^\dagger\mathcal{E})^{p/2}])^{1/p} )\]for the superoperator \(\mathcal{E}\). These norms are unitarily invariant, so the basis (row-stacking, column-stacking, normalized Weyl, etc.) is irrelevant. Certain values of \(p\) have special names:
\(p=1\) is the trace norm.
\(p=2\) is the Frobenius norm and is the fastest to compute.
\(p=\infty\) is the spectral (or operator) norm.
- Parameters
p (
float
) – Any real number greater than or equal to one.- Return type
float
- Raises
ValueError – If \(p<1\).
-
property
stochastic_infidelity
¶ The stochastic infidelity of this superoperator, defined by
\[e_S = 1 - \sqrt{\operatorname{Tr}(\mathcal{E}\mathcal{E}^\dagger)} / d\]for the superoperator \(\mathcal{E}\), where \(d\) is the dimension of Hilbert space, see [8]. This quantity ranges between 0 and the process
infidelity
of this superoperator, where a larger value indicates more stochastic noise. The stochastic infidelity is reported by the XRB protocol. For unital TP maps, the stochastic fidelity is related to theunitarity
by the formula\[e_S = 1 -\sqrt{u(1-1/d^2)+1/d^2}\]- Type
float
-
property
unitary_fraction
¶ The unitary fraction of this superoperator, defined as
\[f_u = 1 - (1-1/d^2)\frac{1 - \sqrt{u}}{e_F}\]where \(e_F\) is the process
infidelity
and \(d\) is the dimension of Hilbert space. A unitary fraction equal to 0 represents the least possible amount of unitarity a superoperator can have, given itsfidelity
, and a value of 1 represents a unitary superoperator.- Type
float
-
property
unitarity
¶ The unitarity of this superoperator, defined by
\[u = \frac{d}{d-1}\int d\psi \operatorname{Tr}[ \mathcal{E}'(|\psi\rangle\langle\psi|) ]\]for the superoperator \(\mathcal{E}\), where
\[\mathcal{E}'(A) = \mathcal{E}(A) - \operatorname{Tr}[\mathcal{E}(A)]\frac{I}{\sqrt{d}}\]is the traceless projection of \(\mathcal{E}\), and where \(d\) is the dimension of the underlying Hilbert space [7]. In other words, the unitarity is the purity of output states averaged over all pure input states, normalized by possible trace-decreasing behaviour. This quantity is estimated by the XRB protocol.
- Type
float
-
static
from_kraus
(*ops)¶ Instantiates a new
Superop
from a given set of Kraus operators. A superoperator in the Kraus representation is a set of \(m\) operators \({K_0,\ldots,K_{m-1}}\) that act on a given density matrix by the formula \(\rho\mapsto\sum_{i=0}^{m-1}K_i \rho K^\dagger_i\). A superoperator admits a Kraus representation if and only if it is completely positive (seeis_cp
), but it is only trace preserving (seeis_tp
) if \(\sum_{k=0}^{m-1}K^\dagger_i K_i = \mathbb{I}\).import numpy as np import trueq as tq # instantiate a depolarizing channel p = 0.05 superop = tq.math.Superop.from_kraus( np.sqrt(1-p) * tq.Gate.id.mat, np.sqrt(p / 3) * tq.Gate.x.mat, np.sqrt(p / 3) * tq.Gate.y.mat, np.sqrt(p / 3) * tq.Gate.z.mat )
- Parameters
ops (
Iterable
) – An iterable of Kraus operators.- Returns
The superoperator corresponding to the given set of Kraus operators.
- Return type
-
property
kraus
¶ The Kraus representation of this superoperator; see
from_kraus()
for details about the representation. Note that Kraus representations are not unique, and this decomposition is only possible if the superoperator is CP (seeis_cp
). The format is a 3-D arrayops
of shape(n_ops, dim, dim)
whereops[i,:,:]
is the \(i^{th}\) Kraus operator.- Type
numpy.ndarray
-
plot_kraus
(n_columns=4, abs_max=None, axes=None)¶ Plots a Kraus representation of this superoperator; see
from_kraus()
for details about the representation. Note that Kraus representations are not unique.- Parameters
n_columns (
int
) – If new axes are made, how many columns of subplots to use.abs_max (
None
|float
) – The value to scale absolute values of the matrix by; the value at which plotted colors become fully opaque. By default, this is the largest absolute magnitude of the input matrix.axes (
Iterable
|None
) – An existing list of axes to plot on. They are created otherwise.
-
static
from_unitary
(u)¶ Instantiates a new
Superop
from a given unitary matrix. A unitary matrix \(U\) acts on a given matrix by the formula \(\rho\mapsto U\rho U^\dagger\).import trueq as tq # instantiate a CNOT as a superoperator superop = tq.math.Superop.from_unitary(tq.Gate.cnot.mat)
- Parameters
u (
numpy.ndarray
) – A unitary matrix.- Returns
The superoperator that conjugates input states by the given unitary matrix.
- Return type
-
static
from_function
(fcn, dim)¶ Instantiates a new
Superop
from a python function.import trueq as tq def depolarizing(rho, p=0.05): d = rho.shape[0] return (1 - p) * rho + p * np.eye(d) * np.trace(rho) / d # instantiate a qubit depolarizing channel superop = tq.math.Superop.from_function(depolarizing, dim=2) # instantiate a qutrit depolarizing channel superop = tq.math.Superop.from_function(depolarizing, dim=3)
Note
The function is assumed to be a linear function of a single density matrix. Passing a nonlinear function will result in a linear approximation that reproduces the action of the function on a basis of the operator space.
- Parameters
fcn (
function
) – A function that takes a density matrix and returns a density matrix of the same shape.dim (
int
) – The dimension of Hilbert space.
- Returns
The superoperator that maps a density matrix to the state returned by the given function.
- Return type
-
from_qobj
()¶ Instantiates a new
Superop
from a QuTiP operator object.import qutip as qt import trueq as tq # make some QuTiP operator cx = qt.sigmax() # convert to a Superop and plot s = tq.math.Superop.from_qobj(cx) s.plot_ptm()
- Parameters
qobj (
qutip.Qobj
) – A QuTiP superoperator object.- Returns
The superoperator for a given qobj.
- Raises
RuntimeError
if QuTiP is not imported.- Return type
-
property
qobj
¶ This
Superop
as a QuTiP superoperator object.import qutip as qt import trueq as tq # construct a Superop from a simulation sim = tq.Simulator().add_stochastic_pauli(px=0.04).add_overrotation(0, 0.03) s = tq.math.Superop(sim.operator(tq.Cycle({(0,1): tq.Gate.cx})).mat()) # convert to a Qobj and plot o = s.qobj qt.visualization.matrix_histogram_complex(o)
(<Figure size 432x288 with 2 Axes>, <mpl_toolkits.mplot3d.axes3d.Axes3D at 0x7fbaae2ab5f8>)
QuTiP is an optional dependency and must be installed for this function to work.
- Raises
RuntimeError – If QuTiP is not imported.
- Type
qutip.Qobj
-
static
from_ptm
(ptm)¶ Instantiates a new
Superop
from a Pauli transfer matrix (PTM). A PTM represents how density matrices are transformed when expanded in the Pauli basis. In particular, suppose for \(n\) qubits, the Pauli matrices are denoted by \(\{P_i\}_{i=0}^{4^n-1}\), and sorted lexicographically. For example, for \(n=2\), then we have the ordering\[\{P_i\}_{i=0}^{15} =\{II, IX, IY, IZ, XI, XX, XY, XZ, YI, YX, YY, YZ, ZI, ZX, ZY, ZZ\}.\]Given any density matrix \(\rho\), we can expand it in the Pauli basis as \(\rho=\sum_{i=4^n-1} c_i P_i\). A PTM \(A\) performs the transformation \(c\mapsto A c\) where \(c\) is the vector \(c=(c_0,c_1,...,c_{4^n-1})\).
import numpy as np import trueq as tq # instantiate a qubit depolarizing channel superop = tq.math.Superop.from_ptm(np.diag([0.97, 0.01, 0.01, 0.01]))
- Parameters
ptm (
numpy.ndarray
) – A PTM. This must have a dimension that is a power of 4.- Returns
The superoperator for a given PTM.
- Return type
-
property
ptm
¶ The Pauli transfer matrix representation of this superoperator; see
from_ptm()
for details on this representation. Note that thedim
of this superoperator must be a power of 4 for this to be valid.- Type
numpy.ndarray
-
plot_ptm
(abs_max=None, ax=None)¶ Plots the Pauli transfer matrix representation of this superoperator; see
from_ptm()
for details on this representation.- Parameters
abs_max (
None
|float
) – The value to scale absolute values of the matrix by; the value at which plotted colors become fully opaque. By default, this is the largest absolute magnitude of the input matrix.ax (
matplotlib.Axis
|None
) – An existing axis to plot on. One is created otherwise.
-
static
from_choi
(choi)¶ Instantiates a new
Superop
from a Choi matrix. If a channel \(\Lambda\) transforms a given density matrix as \(\rho\mapsto \Lambda(\rho)\), then the Choi matrix (in the row convention) is equal to \(\Lambda_r=\sum_{i,j=0}^{d-1}\Lambda(E_{i,j})\otimes E_{i,j}\) where \(d\) is the dimension, and \(E_{i,j}\) is the \(d\times d\) matrix of zeros with a 1 in the \((i,j)^\text{th}\) entry.The Choi matrix is positive semidefinite if and only if the superoperator it represents is completely positive (see
is_cp
). Therefore, there is a correspondence between completely positive superoperators and density matrices (which must also be positive semidefinite) that is often called the Choi-Jamiolkowski isomorphism.import numpy as np import trueq as tq # instantiate a random CP qubit channel superop = tq.math.Superop.from_choi(2 * tq.math.random_density(4))
- Parameters
choi (
numpy.ndarray
) – A Choi matrix.- Returns
The superoperator for a given Choi matrix.
- Return type
-
property
choi
¶ The Choi matrix representation of this superoperator; see
from_choi()
for details on this representation.- Type
numpy.ndarray
-
plot_choi
(abs_max=None, ax=None)¶ Plots the Choi representation of this superoperator; see
from_choi()
for details on this representation.- Parameters
abs_max (
None
|float
) – The value to scale absolute values of the matrix by; the value at which plotted colors become fully opaque. By default, this is the largest absolute magnitude of the input matrix.ax (
matplotlib.Axis
|None
) – An existing axis to plot on. If omitted, a new one is created.
-
static
from_rowstack
(mat)¶ Instantiates a new
Superop
from a Liouville superoperator matrix in the row-stacking basis. Note that since this is the native basis of theSuperop
class, this method is equivalent to the constructor.This is the best representation for superopertors for simulation if density matrices are C-ordered (i.e. row-major, so that entire rows are contiguous in memory). This is because if our row-stacking superoperator matrix is \(M\) and our density matrix \(\rho\), then if we take the rows of \(\rho\) and stack them together into one column vector, denoted \(\operatorname{vec}(\rho)\), then the output density matrix is given by regular matrix multiplication, \(\operatorname{vec}^{-1}(M\operatorname{vec}(\rho))\), where \(\operatorname{vec}^{-1}\) is the unstacking operator.
import numpy as np import trueq as tq # instantiate the identity channel on a qutrit superop = tq.math.Superop.from_rowstack(np.eye(9))
- Parameters
mat (
numpy.ndarray
) – A superoperator in the row-stacking basis.- Return type
-
property
rowstack
¶ The matrix representation of this superoperator in the row-stacking basis; see
from_rowstack()
for details on this representation.- Type
numpy.ndarray
-
plot_rowstack
(abs_max=None, ax=None)¶ Plots the matrix representation of this superoperator in the row-stacking basis; see
from_rowstack()
for details on this representation.- Parameters
abs_max (
None
|float
) – The value to scale absolute values of the matrix by; the value at which plotted colors become fully opaque. By default, this is the largest absolute magnitude of the input matrix.ax (
matplotlib.Axis
|None
) – An existing axis to plot on. If omitted, a new one is created.
-
static
from_rowstack_subsys
(mat, dim=2)¶ Instantiates a new
Superop
from a Liouville superoperator matrix in the row-stacking subsystem-wise basis. If a unitary \(U=U_1\otimes U_2\) acts on some bipartite Hilbert space, thenrowstack
(which is also the native representation used by this class) is equal to\[U\otimes \overline{U} =U_1 \otimes U_2 \otimes \overline{U}_1 \otimes \overline{U}_2\]which we call the global rowstacking convention. It is sometimes more useful to use the subsystem-wise rowstacking convention where the wires of each subsytem are grouped together, corresponding to the unitary
\[U\otimes \overline{U} =U_1 \otimes \overline{U}_1 \otimes U_2 \otimes \overline{U}_2\]Indeed, this is the convention used by
OperatorTensor
and hence also by theSimulator
. This idea generalizes to n-partite superoperators.import numpy as np import trueq as tq # instantiate the identity channel on two qubits superop = tq.math.Superop.from_rowstack_subsys(np.eye(16))
- Parameters
dim (
int
) – The dimension of each Hilbert space subsystem, e.g. 2 for qubits.mat (
numpy.ndarray
) – A superoperator in the row-stacking subsystem-wise basis.
- Return type
-
rowstack_subsys
(dim=2)¶ Returns the matrix representation of this superoperator in the row-stacking basis; see
from_rowstack_subsys()
for details on this representation.- Parameters
dim (
int
) – The dimension of each Hilbert space subsystem, e.g. 2 for qubits.- Returns
The superoperator matrix.
- Return type
numpy.ndarray
-
plot_rowstack_subsys
(dim=2, abs_max=None, ax=None)¶ Plots the matrix representation of this superoperator in the row-stacking subsystem-wise basis; see
from_rowstack_subsys()
for details on this representation.- Parameters
dim (
int
) – The dimension of each Hilbert space subsystem, e.g. 2 for qubits.abs_max (
None
|float
) – The value to scale absolute values of the matrix by; the value at which plotted colors become fully opaque. By default, this is the largest absolute magnitude of the input matrix.ax (
matplotlib.Axis
|None
) – An existing axis to plot on. If omitted, a new one is created.
-
static
from_colstack
(mat)¶ Instantiates a new
Superop
from a superoperator in the column-stacking basis.This is the best representation for superopertors for simulation if density matrices are Fortran-ordered (i.e. column-major, so that entire columns are contiguous in memory). This is because if our column-stacking superoperator matrix is \(M\) and our density matrix \(\rho\), then if we take the columns of \(\rho\) and stack them together into one column vector, denoted \(\operatorname{vec}(\rho)\), then the output density matrix is given by regular matrix multiplication, \(\operatorname{vec}^{-1}(M\operatorname{vec}(\rho))\), where \(\operatorname{vec}^{-1}\) is the unstacking operator.
import numpy as np import trueq as tq # instantiate the identity channel on a qutrit superop = tq.math.Superop.from_colstack(np.eye(9))
- Parameters
mat (
numpy.ndarray
) – A superoperator in the column-stacking basis.- Return type
-
property
colstack
¶ The matrix representation of this superoperator in the column-stacking basis; see
from_colstack()
for details on this representation.- Type
numpy.ndarray
-
plot_colstack
(abs_max=None, ax=None)¶ Plots the matrix representation of this superoperator in the col-stacking basis; see
from_colstack()
for details on this representation.- Parameters
abs_max (
None
|float
) – The value to scale absolute values of the matrix by; the value at which plotted colors become fully opaque. By default, this is the largest absolute magnitude of the input matrix.ax (
matplotlib.Axis
|None
) – An existing axis to plot on. If omitted, a new one is created.
-
apply
(state)¶ Applies this superoperator to a given density matrix or pure state. The output is always a density matrix of shape
(dim, dim)
.import numpy as np import trueq as tq # make a superoperator acting on a qutrit s = tq.math.Superop(np.random.randn(9, 9)) # apply to a pure state s.apply([1, 0, 0])
array([[-1.40193583+0.j, 1.09478461+0.j, -2.37332745+0.j], [ 0.13943257+0.j, -1.93958783+0.j, -0.47011066+0.j], [-0.73372309+0.j, -0.01911321+0.j, 1.23727107+0.j]])
- Parameters
state (
numpy.ndarray
-like) – An input density matrix or pure state of the correct shape.- Returns
The image of state under this superoperator.
- Return type
numpy.ndarray
Tensors¶
-
class
trueq.math.tensor.
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 (e.g., 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 qubit system. Each subsystem has a single output wire of dimension \(d\) and no input wires (i.e., a column vector). SeeStateTensor
.(d,), ()
: Probability distributions over Cartesian products of dits. Each subsystem has a single output wire of dimension \(d\) and no input wires (i.e., a column vector).(d,d), ()
: Vectorized mixed states on a multipartite qubit 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 row-stacking convention). SeeStateTensor
.(d,), (d,)
: Unitaries acting on a multipartite qubit system. Each subsystem has a single output wire of dimension \(d\) and a single input wire of dimension \(d\) (i.e., a square matrix). SeeOperatorTensor
.(d,d), (d,d)
: Superoperators acting on a multipartite qubit 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 left-multiply square matrices onto specific subsystems (see
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) # display the matrix corresponding to the circuit {(0, 2): cnot, (1,): h} tq.visualization.plot_mat(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]] }
{(0,): [[0, 1], [1, 0]], (2, 3): [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]}
In the above example, the label
(0,)
stores an X gate, and the labels(2, 3)
store 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
|None
) – 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, e.g.,
((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 array 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.- Returns
This instance or a copy.
- Return type
-
adj
(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.- Returns
This instance or a copy.
- Return type
-
transpose
(output_perm, input_perm, copy=True)¶ Permutes wire dimensions of this tensor, possibly switching input wires with output wires.
from trueq.math import Tensor # turn a column vector into a row vector t = Tensor((2,)) print(t.shape, t.transpose([], [0]).shape) # vectorize a matrix with row-stacking t = Tensor((2,), (2,)) print(t.shape, t.transpose([0, 1], []).shape) # vectorize a matrix with column-stacking t = Tensor((2,), (3,)) print(t.shape, t.transpose([1, 0], []).shape) # shuffle some wires t = Tensor((2, 3), (5,)) print(t.shape, t.transpose([2, 1], [0]).shape)
((2,), ()) ((), (2,)) ((2,), (2,)) ((2, 2), ()) ((2,), (3,)) ((3, 2), ()) ((2, 3), (5,)) ((5, 3), (2,))
- Parameters
output_perm (
Iterable
) – A list of indices referencing positions in the joint tupleshape = output_shape + input_shape
. Thereforeoutput_perm + input_perm
must be a permutation ofrange(len(shape))
.input_perm (
Iterable
) – A list of indices referencing positions in the joint tupleoutput_shape + input_shape
.copy (
bool
) – Whether to mutate the given tensor, or to create a copy and leave the original intact.
- Returns
This instance or a copy.
- 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 subsystem-wise, 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"composite-group"
flattens each group of subsystems, giving a shape(8, 64, 125)
in this example, and an order of"composite-flat"
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"
,"composite-group"
, or"composite-flat"
. Or, one of"s"
,"c"
,"cg"
, or"cf"
for short.- Return type
dict
-
mat
(order='subsystem', labels=None)¶ Returns the full matrix representation of this tensor, with subsystems ordered by sorted labels.
The argument of this method changes the order of indexes of the arrays. For simulation performance reasons, the internal storage arranges them subsystem-wise, 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"composite-group"
flattens each group of subsystems, giving a shape(8, 64, 125)
in this example, and an order of"composite-flat"
flattens all inputs and all outputs separately, giving a shape(512, 125)
in this example.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
) – Which order to arrange the wires, either"subsystem"
,"composite"
,"composite-group"
, or"composite-flat"
. Or, one of “s”, ``"c"
,"cg"
, or"cf"
for short.labels (
Iterable
) – The desired subsystem label order, which must be a permutation oflabels
. By default, labels are ordered from smallest to biggest.
- Return type
numpy.ndarray
-
add_labels
(labels)¶ Updates the number of subsystems to include labels provided, if they do not already exist. If the labels are already present, this call doesn’t change anything.
- Parameters
labels (
Iterable
) – 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, assume_unital=True, copy=True)¶ Keeps the provided labels of this tensor by marginalizing the remaining labels. Here, marginalization is defined as the sum over all tensor indices of the remaining labels.
import trueq as tq import numpy as np # Make a random probability distribution on length-4 bitstrings S = np.random.rand(16) p = tq.math.Tensor(2, value={(0, 1, 2, 3): S / S.sum()}) # Keep two of the bitstrings and marginalize the rest p.marginalize((1, 2))
Tensor(<[(2,), ()] on labels [(1, 2)]>)
- Parameters
labels (
tuple
) – Which labels to keep. The order does not matter.assume_unital (
bool
) – Assumes that each factor in the tensor has a sum of 1. Otherwise, in the case where entire entries invalue()
need to be removed, sums are 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 re-added so that the total sum of the tensor can be preserved.copy (
bool
) – Whether to make a copy of this tensor, or mutate it in place.
- Returns
This instance.
- Return type
-
update
(other)¶ Updates this tensor with some new values. If values are placed on labels that do not exist, creates and sets them. If values are placed on labels that already exist, they are first removed by calling
marginalize()
.
-
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 of this tensor’s output wire dimensions. Square matrices are enforced to avoid changing the shape of the tensor.
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))
(0, 3)
- Parameters
labels (
Iterable
) – Which subsystems to apply the matrix to.matrix (
numpy.ndarray
-like) – A square matrix.
- Returns
The labels to which the matrix was applied, including labels that needed to be merged together.
- Return type
tuple
-
apply_to_all
(mat, copy=False)¶ Applies a matrix to every subsystem of this tensor.
- Parameters
mat (
numpy.ndarray
) – A matrix to apply to this system, which must be square ifcopy == False
.copy (
bool
) – Whether to make a copy of this tensor, or mutate it in place.
- Returns
This instance, or the copied instance.
- Return type
-
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 input-output 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) print(product.shape) print(product.labels)
((5,), (4,)) (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})
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
-
to_results
(labels=None, clip=True)¶ Constructs a new
Results
object whose ditstring values are equal to the corresponding matrix entries of this tensor.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})
- 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).clip (
bool
) – Whether or not to clip the values to the interval \([0, 1]\).
- Return type
-
class
trueq.math.tensor.
StateTensor
(dim=2, spawn=None, value=None)¶ Represents a pure or mixed state on a multipartite qubit 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 qubit 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
is_mixed
. You can get the matrix representation withmat()
.import trueq as tq # construct a circuit that makes a 3-qubit cat state circuit = tq.Circuit() circuit.append({(0,): tq.Gate.h}) circuit.append({(0, 1): tq.Gate.cnot}) circuit.append({(1, 2): tq.Gate.cnot}) # use a simulator to fetch the state of this circuit as a StateTensor state = tq.Simulator().state(circuit) # print the 1D pure state vector print(state.mat()) # get the density matrix representation state.upgrade() tq.visualization.plot_mat(state.mat())
[0.70710678+0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0. +0.j 0.70710678+0.j]
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 a few main distinctions from its parent class
Tensor
:The datatype is always
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 outer products 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()
.Certain functions like
marginalize
have been special-cased to quantum states.
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 complex numerical arrays.
- Parameters
dim (
int
) – The Hilbert space dimension of a single subsystem.spawn (
numpy.ndarray
-like) – The default state to set a subsystem to when its label is refered to, but it does not exist yet. This spawn can be pure or mixed. See also the constructor ofTensor
.value (
dict
) – The initial value of this state, which can either be pure or mixed. See also the constructor ofTensor
.
-
property
is_mixed
¶ Whether this state currently is a mixed state (as opposed to a pure state).
- Type
bool
-
property
dim
¶ The Hilbert space dimension of the subsystems 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='composite-group')¶ 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='composite-group', labels=None)¶ 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()
.labels (
Iterable
) – The desired subsystem label order, which must be a permutation oflabels
. By default, labels are ordered from smallest to biggest.
- 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 2-qubit unitary t.apply_matrix((0, 1), tq.Gate.cnot.mat) # apply a 1% bitflip superoperator to qubit 1. This upgrades to mixed state s = 0.99 * np.eye(4) + 0.01 * np.kron(tq.Gate.x.mat, tq.Gate.x.mat) t.apply_matrix((1,), s) # display the final mixed state tq.visualization.plot_mat(t.mat())
The vectorization convention of superoperators is important. Mixed states are stored in a subsystem-wise row-stacked vectorization convention, and therefore superoperators need to use this convention too. See
to_rowstack_super()
.- Parameters
labels (
Iterable
) – Which subsystems to apply the matrix to.matrix (
numpy.ndarray
-like) – A square matrix.
-
marginalize
(labels, assume_unital=False, copy=False)¶ Keeps the provided labels of this tensor by taking the partial trace of the remaining labels. 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 traced-out 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).- Parameters
labels (
tuple
) – Which labels to keep. The order does not matter.assume_unital (
bool
) – Assumes that each factor in the tensor has a trace of 1. Otherwise, in the case where entire entries invalue()
need to be removed, traces are still computed and multiplied onto some other entry so that the overall trace of the tensor is preserved. In the case where all labels in the tensor are traced-out, label(0,)
is re-added so that the total trace of the tensor can be preserved.copy (
bool
) – Whether to make a copy of this tensor, or mutate it in place.
- 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)}) print(psi.probabilities().value()) # 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, 1 - p11]], [[1 - p00, 0], [0, p11]]] povm = tqm.Tensor((2,), (2, 2), spawn=povm) print(psi.probabilities(povm).value())
{(0,): array([0.5, 0.5])}
{(0,): array([0.545, 0.455])}
- 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.
OperatorTensor
(dim=2, spawn=None, value=None, dtype=None)¶ Represents an operator (e.g., unitary) or superoperator (e.g., CPTP channel) on a multipartite qubit 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 qubit 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
is_superop
. You can get the matrix representation withmat()
.import trueq as tq # construct a circuit that makes a 3-qubit cat state circuit = tq.Circuit() circuit.append({(0,): tq.Gate.h}) circuit.append({(0, 1): tq.Gate.cnot}) circuit.append({(1, 2): tq.Gate.cnot}) # use a simulator to fetch the unitary of this circuit as an OperatorTensor op = tq.Simulator().operator(circuit) # display the unitary matrix itself tq.visualization.plot_mat(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 a few distinctions from its parent class
Tensor
:The datatype is
numpy.complex128
by default.The
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()
.Certain functions like
marginalize
have been special-cased to quantum operators.
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 (
numpy.ndarray
-like) – The default operator to set a subsystem to when its label is referred to, but it does not exist yet. This spawn can be operator or superoperator. See also the constructorTensor
.value (
dict
) – The initial value of this state, which can either be an operator or a superoperator. See also the constructor ofTensor
.dtype (
type
) – The datatype of this operator.
-
property
is_superop
¶ Whether this tensor represents a superoperator (as opposed to a unitary).
- Type
bool
-
property
dim
¶ The Hilbert space dimension that subsystems of this state act on.
- Type
int
-
marginalize
(labels, assume_unital=False, copy=False)¶ Keeps the provided labels of this tensor by taking the partial trace of the remaining labels. This will usually
upgrade()
a unitary tensor into a superoperator tensor.The special case of no upgrade happens when this state is pure and all the labels being traced-out 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).- Parameters
labels (
tuple
) – Which labels to keep. The order does not matter.assume_unital (
bool
) – Assumes that each factor in the tensor has a trace of 1. Otherwise, in the case where entire entries invalue()
need to be removed, traces are still computed and multiplied onto some other entry so that the overall trace of the tensor is preserved. In the case where all labels in the tensor are traced-out, label(0,)
is re-added so that the total trace of the tensor can be preserved.copy (
bool
) – Whether to make a copy of this tensor, or mutate it in place.
- Returns
This instance.
- Return type
-
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='composite-flat')¶ 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='composite-flat', labels=None)¶ 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 row-stacking basis.
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()
.labels (
Iterable
) – The desired subsystem label order, which must be a permutation oflabels
. By default, labels are ordered from smallest to biggest.
- 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 to qubits 0 and 1 t.apply_matrix((0, 1), tq.Gate.cnot.mat) # apply a 1% bitflip superoperator. this upgrades to a superoperator s = 0.99 * np.eye(4) + 0.01 * np.kron(tq.Gate.x.mat, tq.Gate.x.mat) t.apply_matrix((1,), s) # show the full superoperator tq.visualization.plot_mat(t.mat())
The vectorization convention of superoperators is important. Mixed states are stored in a subsystem-wise row-stacked vectorization convention, and therefore superoperators need to use this convention too. See
to_rowstack_super()
.- Parameters
labels (
Iterable
) – Which subsystems to apply the matrix to.matrix (
numpy.ndarray
-like) – A square matrix.
-
diagonal
(mat=None, force_real=True)¶ Returns a new 1D
Tensor
whose values are the diagonal elements of this operator conjugated by the given matrix.For example, if the matrix is the change of basis matrix from the vectorized Pauli basis to the row-stacking basis, then the result is proportional to the Pauli transfer matrix diagonal:
import trueq as tq circuit = tq.Circuit([{(0, 1): tq.Gate.cnot}]) s = tq.Simulator().add_depolarizing(0.01).operator(circuit) # the PTM off by a scale of 2 on every subsystem basis = [tq.math.pauli_basis.get_element(p).ravel() for p in "IXYZ"] s.diagonal(np.array(basis).T).mat("f")
array([ 4.00000000e+00, 1.98000000e+00, 1.98000000e+00, 2.22044605e-16, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 3.96000000e+00, 1.96020000e+00, 1.96020000e+00, -1.11022302e-16])
- Parameters
mat (
numpy.ndarray
) – A dimension-compatible square matrix to change the basis by.- Return type
-
trueq.math.tensor.
to_rowstack_super
(A, B, d, n)¶ Returns the superoperator of the operation \(X\mapsto AXB^T\) in the subsystem-wise row stacking basis.
See https://arxiv.org/abs/1111.6950 Section V for details on subsystem-wise vs. composite stacking vectorization bases (though beware that this reference uses the column stacking convention).
- Parameters
A – The left-multiplication operator, a square matrix.
B (
numpy.ndarray
) – The right-multiplication operator, a matrix with the same shape as A.d (
int
) – The subsystem dimension.n (
int
) – The number of subsystems.
- Returns
The superoperator corresponding to conjugating an input by the given matrices.
- Return type
numpy.ndarray