TensorPlay
Reference guides
latest (dev)
Copy
View Markdown

Latest development documentation · Updated 2026-10-15

Quantum simulation

tensorplay.quantum simulates quantum circuits on the tensor hardware. A state is a tensor of amplitudes, gates are matrices applied to the qubits they act on, and measurement turns amplitudes into a distribution you can evaluate exactly or sample.

import tensorplay as tp
from tensorplay import quantum as tq

bell = tq.Circuit(2)
bell.h(0)
bell.cx(0, 1)

state = bell()
tq.probs(state)             # [[0.5, 0., 0., 0.5]] — |00> and |11>
tq.expectation(state, "ZZ") # [1.] — the pair is perfectly correlated
tq.measure(state, shots=1000)

Layout

A state is shaped [batch, 2, 2, ...] — one dimension of size 2 per qubit, after the batch. Qubit 0 leads, so qubit 0 is the most significant bit of the amplitude index: applying X to wire 0 of |00> gives |10>, which is index 2.

Layout

Shape

Meaning

State

[batch, 2, 2, ...]

one amplitude per qubit dimension

Flat state

[batch, 2**n]

the same amplitudes, qubit dimensions folded

Density matrix

[batch, 2**n, 2**n]

row index the ket, column index the bra

Complex dtypes are tensorplay.complex64 and tensorplay.complex128; a state keeps whichever one it was created with. A batch dimension of 1 is always present, so a single state is a valid batch of one.

Qubit 0 is the most significant bit throughout — in marginal_probs, in measure with a wires list, and in the argument index of a Pauli word.

Two-qubit and wider gates read their arguments in the order given: the first wire named is the first index bit of the gate matrix, which is what makes the control the control. apply_cnot(state, 1, 0) drives wire 0 from wire 1, and apply_mcx(state, [0, 2, 1]) uses wires 0 and 2 as controls and flips wire 1.

States

tq.zeros_state(3)            # |000>
tq.plus_state(2)             # (|00> + |01> + |10> + |11>) / 2
tq.basis_state(5, 3)         # |101> on 3 qubits
tq.state_from_bases([0, 3], 2)  # (|00> + |11>) / sqrt(2)
tq.product_state([a, b])     # the tensor product of per-qubit vectors
tq.random_state(3)           # uniform on the sphere of the register
tq.encode_state([x, y, z])   # amplitude-encode a classical vector

normalize rescales to unit norm, norm reports it, and inner is the conjugate overlap between two states. to_density and density_to_state cross between the ket and density views, density_mixture decomposes a density matrix into its eigenstates and their probabilities, and partial_trace drops qubits from a density matrix.

global_phase answers the question that comes up whenever two operators are computed two different ways: do they differ by a phase and nothing else? It reads that one factor off the whole overlap, so no single entry decides it, and raises if the two are not the same operator up to a phase.

phase = tq.global_phase(fused_gate, dense_product)   # 1.0 if they are the same

Gates

apply_gate is the primitive: a matrix applied to a list of wires. Everything else — the named gates, controlled gates, circuits — is a layer on top of it.

state = tq.zeros_state(2)
state = tq.apply_h(state, 0)          # Hadamard on wire 0
state = tq.apply_cnot(state, 0, 1)    # control wire 0, target wire 1
state = tq.apply_gate(state, tq.rot_y_matrix(0.3), [1])

Parameter-free gates: apply_x, apply_y, apply_z, apply_i, apply_h, apply_s, apply_sdg, apply_sx, apply_t, apply_tdg. Parameterized gates take the parameter as the last argument: apply_rx, apply_ry, apply_rz, apply_p, apply_u2, apply_u3, apply_rot (any axis), apply_multirz (one angle on a list of wires), apply_global_phase (scales the whole state). Two-qubit gates: apply_cx/apply_cnot, apply_cy, apply_cz, apply_swap, apply_iswap, apply_dcx (the same controlled X with the target written first), apply_ecr (echoed cross resonance), the controlled square roots apply_cs, apply_csdg and apply_csx, and the controlled parameter gates apply_cp, apply_crx, apply_cry, apply_crz. Wider gates: apply_toffoli/apply_ccx (three wires), apply_ccz (two controls and a phase), apply_rccx (the relative-phase Toffoli), apply_rc3x (its four-wire version), apply_cswap (control plus two targets), and apply_mcx, which takes a control list and flips the remaining wire.

The chemistry pair gates mix two wires while leaving the register’s excitation count alone, which is what a particle-number-preserving ansatz needs: apply_xx_plus_yy, apply_xx_minus_yy and apply_single_excitation, each taking the pair of wires and an angle.

random_unitary draws a gate uniformly from all the unitaries on a register, which is what a test or a baseline average wants rather than a matrix of random entries: the distribution is uniform over the operators themselves, phase and all.

Two-body rotations take a pair of wires and an angle the same way: apply_rxx, apply_ryy, apply_rzz and apply_rzx. Each is exp(-i * angle * P / 2) for a two-wire Pauli product P — ZZ is diagonal, so it only adds a phase, which is the gate an Ising Hamiltonian is written with. The first three treat the pair alike; apply_rzx puts Z on the wire named first.

state = tq.apply_rzz(state, [0, 1], 0.4)   # a phase on each basis state
state = tq.apply_rxx(state, [0, 1], 0.4)   # mixes |01> and |10>

The matching matrix builders are available on their own — h_matrix, rot_x_matrix, swap_matrix, mcx_matrix and the rest — so a gate can be built, inspected, or composed without applying it. controlled_matrix wraps any single-qubit unitary as a control, pauli_matrix builds a Pauli word, and dagger is the conjugate transpose.

Control readings

A control decides whether the gate fires by what it reads, and all ones is only the default. control_values says what each control has to hold, in wire order:

tq.apply_mcx(tq.zeros_state(3), [0, 1, 2])                      # fires on |11.>
tq.apply_mcx(tq.zeros_state(3), [0, 1, 2], control_values=[0, 0])  # fires when both are 0

apply_controlled(state, mat, wires, control_values=...) is the same idea for an arbitrary operator, with the controls leading the wire list.

Batched parameters

A gate matrix may carry one entry per batch item, which gives every state in the batch its own parameter:

theta = tp.tensor([0.0, 3.14159])          # per batch item
states = tq.zeros_state(1, batch=2)
tq.apply_ry(states, 0, theta)               # first qubit stays 0, second flips

Circuits

A Circuit is an ordered gate list over a fixed register. It holds no state: calling it starts from |0...0>, and apply threads a state you provide through the same gates.

qc = tq.Circuit(3)
qc.h(0)
qc.rx(1, 0.4)
qc.cx(0, 1)
qc.barrier()             # a marker for transpilation; changes nothing
qc.cx(1, 2)

state = qc()             # from |000>
state = qc(some_state)   # from a state you choose
state = qc(batch=64)     # 64 copies, all following the same gates

add takes any gate name as a string, so gates without a method are still reachable; gates() returns the recorded list, and len(qc) counts it. The methods cover the named gates — h, x, y, z, s, sdg, t, tdg, sx, rx, ry, rz, p, u3, cx, cz, cp, crx, cry, crz, swap, iswap, cswap, ccx and mcx.

unitary() multiplies the gates into the dense register-wide matrix, and inverse() returns the circuit that undoes this one — reversed order, with each gate replaced by its adjoint. qft_circuit(n) builds the Fourier transform as a gate sequence rather than a dense matrix.

qc.unitary().shape       # (8, 8) on 3 qubits
qc.inverse()(qc())       # back to |000>

Ansatz templates

Layer templates lay out a trainable circuit instead of hand by hand. nlocal_circuit is the general form: reps blocks of “rotate every wire, then entangle”, a closing rotation layer, and one trainable angle per rotation gate.

tq.real_amplitudes(3, reps=2)          # RY rotations and CNOT pairs
tq.efficient_su2(3, reps=2)            # RY and RZ rotations, CNOT pairs
tq.nlocal_circuit(3, ("ry", "p"), reps=1, entanglement="circular")

The entanglement pattern is one of "linear", "reverse_linear", "circular" or "full"; entanglement_pairs(n, pattern) returns the pairs directly. Angles arrive as module parameters in (-pi, pi), so the template can go straight to an optimizer:

qc = tq.real_amplitudes(3, reps=2)
loss = tq.expectation(qc.state(), "ZI")
loss.backward()
len(list(qc.parameters()))            # 9: three wires, two blocks, one closing layer

Measurement

probs is the squared amplitude of every basis entry — the exact measurement distribution.

tq.probs(state)                        # [batch, 2**n]
tq.marginal_probs(state, [0, 2])       # the listed qubits, in the order given
tq.expectation(state, "XIZ")           # <psi|P|psi>, real, per batch item

expectation applies the Pauli word one factor at a time, so its cost grows with the state rather than with its square. apply_pauli_word gives P|psi> itself.

measure samples the distribution, where the state already lives:

counts = tq.measure(state, shots=4096)          # [batch, 2**n]
counts = tq.measure(state, shots=1024, wires=[0])  # [batch, 2]

Sampling walks the cumulative distribution with one uniform per shot, so a sampled batch never leaves the device. sample_indices returns the raw draws and counts_from_indices tallies them if you want the two steps separately.

Reading values back out of shots

A Pauli word that touches an X or a Y wire cannot be read off a plain histogram until the wires are turned: apply_measurement_basis puts the state into the frame where measuring in Z reports each factor’s eigenvalue. The histogram then averages those signs, which is expectation_from_counts.

readable = tq.apply_measurement_basis(state, "XZ")
counts = tq.measure(readable, shots=4096, wires=[0, 2])
tq.expectation_from_counts(counts, "XZ", wires=[0, 2])   # [batch]

Words that agree on every wire where both of them act can share one such rotation: each is a sub-word of the group’s product, so a single histogram reads all of them. commuting_groups finds the groups, joint_expectation runs them and returns one estimate per word — fewer settings means fewer shots for the same noise.

tq.commuting_groups(["XZ", "XI", "ZZ"])   # [['XZ', 'XI'], ['ZZ']]
tq.joint_expectation(state, ["XZ", "XI", "ZZ"], shots=4096)   # [batch, 3]

A readout that lies

A device reports a bit that was prepared wrong now and then, and the two directions usually differ. A readout matrix records that: row p is what gets reported for a prepared p, so it is a distribution over the reported bit.

noisy = tq.readout_error_matrix(0.02)               # 2% in both directions
worse = tq.readout_error_matrix(0.02, 0.05)         # 2% of 1s, 5% of 0s

measure takes one and decides every shot’s reading on its own, which is how a real readout behaves:

counts = tq.measure(state, shots=4096, readout=noisy)

Give one matrix per wire as [n, 2, 2] when the wires were calibrated apart. apply_readout_error applies the same map to a histogram or an exact distribution, so an offline histogram can be corrected — or biased, which is what it is usually wanted for.

Reading a state back out of a histogram

A histogram in the computational basis is the diagonal of a density matrix and nothing else. density_from_counts hands that diagonal back, which is the state the measurements cannot rule out and no more: every off-diagonal term is a phase, and a basis measurement never sees a phase.

rho = tq.density_from_counts(counts)                # populations, no phases
state = tq.state_from_counts(counts)                # the square roots of them

state_from_counts names the member of that family a histogram leaves free whose amplitudes are real and non-negative, which is the one overlapping the measured state the most. Comparing it with a theory compares populations; the phases come from histograms taken in rotated bases, which is what joint_expectation reads.

An outcome that never came out of a finite number of shots stays out. Pass pseudocount= to lift every outcome off the floor before normalizing, which makes the density matrix full rank for a tomography fit — and makes the answer wrong by that much, which is what it says on the tin.

Noise

Channels act on density matrices, given as Kraus operators. Each one is a set of local matrices K_r, and applying it means rho -> sum_r K_r rho K_r^dagger.

rho = tq.to_density(state)
rho = tq.apply_bit_flip(rho, 0.01, wire=0)
rho = tq.apply_phase_flip(rho, 0.02, wire=1)
rho = tq.apply_amplitude_damping(rho, 0.05, wire=2)
rho = tq.apply_depolarizing(rho, 0.01, wire=0)

bit_flip_kraus, phase_flip_kraus, amplitude_damping_kraus and depolarizing_kraus return the operator sets, and apply_channel takes any set you build yourself, on any list of wires. Every builder takes n_qubits=: one wire by default, and more wires as the kind of error the name says. bit_flip and phase_flip are then correlated — every named wire flips together or none does — depolarizing draws a uniform non-identity Pauli word over the whole register, and amplitude_damping damps every wire independently:

kraus = tq.bit_flip_kraus(0.1)              # one wire: X with chance 0.1
both = tq.bit_flip_kraus(0.1, 2)            # two wires: X X together, or nothing
spread = tq.depolarizing_kraus(0.1, 2)       # 15 errors, each with chance 0.1/15
relax = tq.amplitude_damping_kraus(0.05, 2)  # 4 operators, one per way to decay
rho = tq.apply_channel(rho, both, [0, 1])

A chance can be a differentiable tensor, so a noise rate is a learned parameter: pauli_kraus({"XX": p, "ZI": 0.05}) keeps the gradient on p the way the single-wire builders always have.

Because every Kraus operator is local, the cost stays linear in the register size: no channel superoperator is ever built.

pauli_kraus builds the set from a noise model written down the way noise models are: which errors happen with what chance, with the rest of the chance left to nothing happening.

kraus = tq.pauli_kraus({"XX": 0.02, "ZI": 0.01, "IX": 0.005})
rho = tq.apply_channel(rho, kraus, [0, 1])     # 96.5% of nothing happening

Every named word must span the same number of wires, and one named on a single wire of a wider register still arrives as an operator on the whole of it. A set of uniform chances over every word is the depolarizing channel; a named set is the general one, and this is the way to write a two-qubit noise model at all.

A system that does not close

A channel applies all at once. Noise that acts over time is the generator d rho / dt = -i [H, rho] + sum_r (L_r rho L_r^dagger - 1/2 {L_r^dagger L_r, rho}), and Lindbladian is that generator, held as an object and applied as often as an integration needs.

generator = tq.Lindbladian(h2, jump_ops=["XI", ("IZ", 0.05)])
final = generator.evolve(rho, time=2.0)

The generator itself is never built. Written out densely it is 2**n times the size of rho squared – on ten qubits, a matrix of a trillion entries – so each term is applied on its own and the integral is formed by composition alone.

A jump operator is a Pauli word, a (wires, matrix) pair, or a full-register matrix, and a bare Pauli word means a rate of one. A rate can be written next to it, which is the short way to say how fast the error happens:

tq.Lindbladian(h2, [("XI", 0.02), ("ZI", 0.01)])        # Pauli errors at rates
tq.Lindbladian(h2, [([0], 0.05 ** 0.5 * excitation), n_qubits=4])

A word of identities and Zs is a sign pattern rather than a general matrix, so its whole dissipator is one factor per entry of rho and costs a single pass. A stack of jump operators is read one at a time, the same way.

A jump carries its rate in its own size — sqrt(gamma) * L is an error happening at rate gamma, which is the master equation’s own form of the statement — so amplitude_damping_kraus(gamma)[1] is the sqrt(gamma) |0><1| this wants.

A channel is not a generator, and handing one in as if it were is the one way to get a plausible answer that is quietly wrong. A channel is the exponential of a generator: summing dissipators over its Kraus operators applies the channel once and subtracts the state once per operator, which is not the exponential of anything. The population decay comes out right and the coherence decay does not, by a few percent, and nothing looks amiss. So a stack handed in here is read as a list of jumps, and a channel is applied to a density matrix with apply_channel instead — which is where a channel belongs.

Two routes integrate the generator, and both keep the trace:

generator.evolve(rho, 2.0, method="rk4")     # fixed fourth-order steps
generator.evolve(rho, 2.0, method="taylor")  # a truncated series of the span

"rk4" is the one that can follow a Hamiltonian that moves with time, given as a callable of t, and the one that reports intermediate times. "taylor" sums a series of the whole span, which costs several times less per unit of time covered and lands closer to the exact answer — the default wherever it applies, short spans and long ones alike.

No span is out of reach for the series. Its terms climb to about exp(span) while the sum they add to stays the size of the state, so one sum can only carry so far before it cancels itself away in the rounding; past that point the span is covered by several sums of a size one can carry, in a row. Advancing by a then by b is advancing by a + b, each half applied to the matrix the one before left, and the propagator is still never built — a propagator can only be squared as an operator, and this generator is never built to square.

What an experiment wants is the observables along the way:

states, values = generator.trajectory(
    rho, 2.0, n_sample=101, observables=[h2, "IZ"]
)

values is [time, observable, batch], real. Without a sample count, mesolve answers with the final matrix on its own; sesolve is the closed-system counterpart, which advances a state rather than its square and so costs 2**n per step instead of 4**n.

tq.sesolve(state, h2, time=2.0, order=2, n_sample=51, observables=[h2])
tq.mesolve(rho, h2, None, time=2.0, n_sample=51, observables=[h2])   # same answer

Hamiltonians and time evolution

A Hamiltonian is a weighted sum of Pauli words.

h2 = tq.Hamiltonian([
    (-1.0523732, "II"),
    (0.3979374, "IZ"),
    (-0.3979374, "ZI"),
    (-0.1809312, "XX"),
])
tq.energy(state, h2)     # <psi|H|psi>, real, per batch item
h2.matrix()              # the dense matrix, on the wires the words span

Every Pauli word squares to the identity, so exp(-i * t * c * P) is a rotation about that word, cos(t * c) I - i sin(t * c) P, with a closed form. pauli_rotation_matrix builds it, and trotter_evolve applies one rotation per term in sequence:

evolved = tq.trotter_evolve(state, h2, time=1.5, order=4)

The split is exact for commuting terms. Otherwise the order is what decides how carefully the non-commuting pieces are dropped, and each step is order times the work: a first-order product has an error that falls with the step, the palindromic second-order one with the square of it, and the fourth-order one – that scheme composed three times with uneven weights – with the fourth power. For small registers, matrix_exp_evolve gives the exact propagator from the dense matrix instead, and sesolve picks between the routes.

Gradients

Every gate is differentiable in its parameters, so a circuit is differentiable in whatever tensors you hand it — tq.energy included. Registering the parameters as nn.Parameter is all it takes to optimize a circuit: a parameter handed to a gate becomes one of the circuit’s own, named after the gate it belongs to.

import tensorplay.nn as nn

h2 = tq.Hamiltonian([(-1.0523732, "II"), (0.3979374, "IZ"),
                     (-0.3979374, "ZI"), (-0.1809312, "XX")])

qc = tq.Circuit(2)
theta0 = nn.Parameter(tp.tensor(0.2))
theta1 = nn.Parameter(tp.tensor(-0.4))
qc.ry(0, theta0)
qc.ry(1, theta1)
qc.cx(0, 1)

optimizer = tp.optim.SGD(qc.parameters(), lr=0.05)
loss = tq.energy(qc(), h2).sum()
loss.backward()
optimizer.step()

Inside a recorded graph a gate is evaluated as a permutation, a batched matrix multiply, and the inverse permutation — all differentiable primitives, with the gradient carried through them. Outside one, the compiled kernel applies the gate in a single pass over the amplitudes.

The measurement side stays compiled either way. expectation, probs and marginal_probs read the amplitudes in one pass and are linear in |psi|², so inside a graph they hand the backward the arithmetic they would have done anyway: the gradient of a Pauli word is that word applied to the state, and the gradient of a distribution is the state times the weights the forward already computed. A trained circuit therefore pays for one pass forward and one pass per non-identity factor backward, rather than rebuilding a rotated copy of the state and threading a graph through it.

Gradients without a graph

Two situations call for something else. One is a value that is not built out of differentiable primitives at all: an energy estimated from a finite number of measurements has nothing to backpropagate through. The other is a circuit deep enough that backpropagating through every rotation costs more than evaluating the whole thing twice.

sample_energy answers the first — the Hamiltonian measured with as few measurement settings as its terms allow, optionally through a readout matrix so the estimate carries the error a device would add:

value = tq.sample_energy(state, h2, shots=4000)

param_shift answers the second, and answers the first too. For a value that depends on one angle through exp(-i θ G), the derivative is a difference of the value at two displaced angles, and it is exact — independent of how deep the circuit is and of what sits in the middle of it, a sampler included:

def energy(angles):
    return tq.energy(tq.apply_cnot(
        tq.apply_ry(tq.apply_ry(tq.zeros_state(2, batch=angles.shape[0]),
                                0, angles[:, 0]),
                    1, angles[:, 1]), 0, 1), h2)

angles = tp.tensor([[0.2, -0.4]])
gradients = tq.param_shift(energy, angles)          # no graph anywhere
sampled = lambda a: tq.sample_energy(build(a), h2, 2000)
noisy = tq.param_shift(sampled, angles)             # a value with no graph either

Every angle gate here is a rotation about half a Pauli word, whose eigenvalues are ±1/2, so the default displacement of π/2 and weight of one half are the rule for all of them. A generator whose eigenvalues differ by ω instead wants π / (2 ω) and a weight of ω / (2 sin(ω shift)); both are arguments, so the rule follows the gate rather than assuming it.

The rule reaches an angle that enters through one rotation and stops there. A controlled rotation is not one exponential — where the control is unset its generator reads zero — so the value carries half the frequency as well as the whole one, and no single displacement reaches both. Those angles want the differentiated gradient, which covers them.

One run, three ways

The three routes are the same run to a variational search: the same circuit, the same Hamiltonian, the same optimizer, and a different way of getting the direction to walk in.

h2 = tq.Hamiltonian([(-1.0523732, "II"), (0.3979374, "IZ"),
                     (-0.3979374, "ZI"), (-0.1809312, "XX")])
exact = float(tp.linalg.eigvalsh(h2.matrix())[0])        # -1.868555

def circuit(angles):                                     # 3 layers, 12 angles
    state = tq.zeros_state(2)
    for layer in range(3):
        for qubit in range(2):
            state = tq.apply_ry(state, qubit, angles[0, 4 * layer + qubit])
            state = tq.apply_rz(state, qubit, angles[0, 4 * layer + qubit + 1])
        state = tq.apply_cnot(state, 0, 1)
    return state

energy = lambda a: tq.energy(circuit(a), h2)
theta = nn.Parameter(tp.full((1, 12), 0.4))
optimizer = tp.optim.Adam([theta], lr=0.1)
for step in range(80):
    optimizer.zero_grad()
    energy(theta).backward()                             # through the circuit
    # theta.backward(tq.param_shift(energy, theta.detach()))          # by displacing
    # theta.backward(tq.param_shift(
    #     lambda a: tq.sample_energy(circuit(a), h2, 4000),
    #     theta.detach()))                                              # and in shots
    optimizer.step()

Backpropagating lands about two thousandths above the exact ground energy, the displaced rule about three millionths — it is exact, so what is left is the optimizer’s own step size — and four thousand shots per evaluation about a hundred-thousandth, which is where the shot noise puts it.

Precision

A register’s own dtype is what a whole circuit runs at: a double-precision state stays double through every gate, and a single-precision one stays single even when a matrix arrives wider. The amplitude a gate produces is only as precise as the register holding it, so the place to decide is where the state is built. An angle written as a plain number is read in double precision on the way to the matrix, which costs nothing and keeps a double-precision register from being fed a single-precision matrix.

wide = tq.zeros_state(2, dtype=tp.complex128)   # this is what decides
tq.apply_ry(wide, 0, 0.3)                        # double-precision amplitudes
tq.apply_ry(tq.zeros_state(2), 0, 0.3)          # single, whatever the angle says

One qubit as a point in the ball

A single qubit is a point of the unit ball, and reading a simulation off in those coordinates is what makes a comparison readable: the distance between two states is the angle between their vectors, a mixed state is inside rather than on, and a fit lands somewhere you can look at.

vector = tq.sv2bloch(state)          # (x, y, z), real, per batch entry
tq.dens2bloch(density)               # the same reading off a mixed state
tq.bloch2dens(vector)                # back to the matrix it names
tq.bloch2sv(vector)                  # and the state, when there is one

A normalized state is on the surface; how far inside a mixed one sits says how mixed it is, being twice the larger eigenvalue less one. The reading is real whatever the state’s own precision, and the reverse is exact for a mixed vector.

Reference

tensorplay.quantum.Circuit

An ordered gate list over a register of n_qubits qubits.

tensorplay.quantum.qft_circuit

The quantum Fourier transform as a gate sequence.

tensorplay.quantum.real_amplitudes

Y rotations interleaved with CNOT pairs, the variational starting point.

tensorplay.quantum.efficient_su2

Y and Z rotations interleaved with CNOT pairs.

tensorplay.quantum.nlocal_circuit

Repeats a rotation layer and an entanglement layer over a register.

tensorplay.quantum.entanglement_pairs

The ordered (control, target) pairs of an entanglement pattern.

tensorplay.quantum.apply_gate

Apply the unitary mat to the qubits named by wires.

tensorplay.quantum.controlled_matrix

mat applied only when the control qubits read control_values.

tensorplay.quantum.apply_controlled

Apply mat on the last wires, guarded by the leading ones.

tensorplay.quantum.apply_chadamard

Apply Hadamard on target, controlled by control.

tensorplay.quantum.apply_shadamard

Apply the quarter-turn Y rotation on wire.

tensorplay.quantum.apply_sswap

Apply the square root of swap between two qubits.

tensorplay.quantum.rot_matrix

A rotation of phi radians about any axis.

tensorplay.quantum.multirz_matrix

A rotation of angle radians about Z on every one of n_qubits.

tensorplay.quantum.multirz_eigvals

The eigenvalues multirz_matrix() is built from, as a vector.

tensorplay.quantum.rxx_matrix

A rotation of theta radians generated by X on both wires.

tensorplay.quantum.ryy_matrix

A rotation of theta radians generated by Y on both wires.

tensorplay.quantum.rzz_matrix

A rotation of theta radians generated by Z on both wires.

tensorplay.quantum.rzx_matrix

A rotation generated by Z on the leading wire and X on the other.

tensorplay.quantum.globalphase_matrix

The scalar e^{i angle} as a 1 by 1 operator.

tensorplay.quantum.rccx_matrix

The relative-phase Toffoli, on [control a, control b, target].

tensorplay.quantum.dcx_matrix

The two-wire controlled X written the way its matrix spells it.

tensorplay.quantum.ecr_matrix

The echoed cross-resonance gate, the entangling native of one family.

tensorplay.quantum.rc3x_matrix

The four-wire relative-phase gate on [a, b, c, target].

tensorplay.quantum.xx_plus_yy_matrix

The pair mixer that trades one excitation for the other.

tensorplay.quantum.xx_minus_yy_matrix

The pair mixer that does not touch a state of one excitation.

tensorplay.quantum.single_excitation_matrix

A real rotation of the pair that carries exactly one excitation.

tensorplay.quantum.sh_matrix

The shadamard gate: a quarter-turn rotation around Y.

tensorplay.quantum.sswap_matrix

The square root of swap on [first wire, second wire].

tensorplay.quantum.dagger

The conjugate transpose of a gate matrix, batched over leading dims.

tensorplay.quantum.expand_to_register

Embed a gate matrix into a register-wide unitary on n_qubits qubits.

tensorplay.quantum.zeros_state

The all-zero computational basis state: amplitude 1 on the first entry.

tensorplay.quantum.plus_state

The equal superposition over every basis entry of n_qubits.

tensorplay.quantum.basis_state

The computational basis state named by index, shaped [1, 2, 2, ...].

tensorplay.quantum.encode_state

Amplitude-encode classical feature vectors into quantum states.

tensorplay.quantum.global_phase

The single unit factor that turns one state or gate into the other.

tensorplay.quantum.Lindbladian

The generator of a Lindblad master equation, as it acts on matrices.

tensorplay.quantum.lindbladian

The Lindblad generator of a master equation.

tensorplay.quantum.dissipator

The Lindblad dissipator of jump_ops, applied to density.

tensorplay.quantum.mesolve

Solve the master equation over time.

tensorplay.quantum.sesolve

Evolve a closed system, where the generator is the Hamiltonian alone.

tensorplay.quantum.random_state

A state drawn uniformly from the unit sphere of an n_qubits register.

tensorplay.quantum.random_unitary

A unitary drawn from the Haar measure on an n_qubits register.

tensorplay.quantum.state_from_bases

The equal superposition of the listed basis states.

tensorplay.quantum.product_state

The tensor product of single-qubit states, in the given qubit order.

tensorplay.quantum.flatten

Reshape a register-shaped state [batch, 2, 2, ...] into one flat row per state.

tensorplay.quantum.unflatten

Reshape a flat state back into the register layout [batch, 2, 2, ...]; the qubit count is read off the length if unset.

tensorplay.quantum.norm

The Euclidean norm of each state in the batch.

tensorplay.quantum.normalize

Rescale each state in the batch to unit length.

tensorplay.quantum.apply_reset

Project wire onto the zero state and renormalize the register.

tensorplay.quantum.inner

The bra-ket overlap for each pair of states in the batch.

tensorplay.quantum.fidelity

The squared bra-ket overlap magnitude for each pair of states in the batch.

tensorplay.quantum.to_density

The density matrix of each pure state in the batch.

tensorplay.quantum.bloch2dens

The density matrix a Bloch vector names, (I + r.sigma)/2.

tensorplay.quantum.bloch2sv

The state a Bloch vector names, the way sv2bloch reads one.

tensorplay.quantum.dens2bloch

The Bloch vector of a one-qubit density matrix.

tensorplay.quantum.sv2bloch

The Bloch vector (x, y, z) of a one-qubit state.

tensorplay.quantum.density_to_state

The pure state whose density matrix is density, when it has one.

tensorplay.quantum.density_mixture

The eigenstates of each density matrix, as (weight, state) pairs.

tensorplay.quantum.state_from_counts

The state whose squared amplitudes are the histogram's shares.

tensorplay.quantum.partial_trace

Trace density down to the qubits in keep, in the given order.

tensorplay.quantum.probs

The measurement distribution of each state, one probability per basis entry.

tensorplay.quantum.density_probs

The measurement distribution read off a density matrix's diagonal.

tensorplay.quantum.density_from_counts

The density matrix a histogram of basis measurements describes.

tensorplay.quantum.marginal_probs

The distribution of the listed qubits, marginalized over the rest.

tensorplay.quantum.expectation

The expectation value of the Pauli word word for each state, real and per batch entry.

tensorplay.quantum.expectation_from_density

tr(P rho) for the Pauli word word, real and per batch entry.

tensorplay.quantum.apply_pauli_word

Apply the Pauli word word to each state, one factor at a time.

tensorplay.quantum.apply_measurement_basis

Rotates the state so a plain histogram reads the signs of word.

tensorplay.quantum.expectation_from_counts

Estimates <P> for a Pauli word from a histogram of measurements.

tensorplay.quantum.commuting_groups

Groups Pauli words that one basis rotation can measure together.

tensorplay.quantum.joint_expectation

Estimates several Pauli words from as few measurement settings as possible.

tensorplay.quantum.sample_indices

Draw shots basis indices per row by inverse transform sampling.

tensorplay.quantum.counts_from_indices

Tally sampled basis indices into per-basis counts for each state in the batch.

tensorplay.quantum.gen_bitstrings

The computational basis labels of n_qubits, in flat-index order.

tensorplay.quantum.encode_w

Join wire indices into a string key, "1.3.2" for [1, 3, 2].

tensorplay.quantum.decode_w

Split a dotted wire key back into a list of ints.

tensorplay.quantum.measure

Sample shots measurements per state and tally them.

tensorplay.quantum.readout_error_matrix

The two outcomes one wire reports for each bit it was handed.

tensorplay.quantum.apply_readout_error

Reports a histogram the way a noisy readout would have.

tensorplay.quantum.apply_channel

Apply a local channel to the wires of a density matrix.

tensorplay.quantum.apply_bit_flip

Apply bit-flip noise to one wire of a density matrix.

tensorplay.quantum.apply_phase_flip

Apply phase-flip noise to one wire of a density matrix.

tensorplay.quantum.apply_amplitude_damping

Apply amplitude damping to one wire of a density matrix.

tensorplay.quantum.apply_depolarizing

Apply depolarizing noise to one wire of a density matrix.

tensorplay.quantum.bit_flip_kraus

Kraus operators of the bit-flip channel: X flips every named wire together.

tensorplay.quantum.phase_flip_kraus

Kraus operators of the phase-flip channel: Z rotates every named wire together.

tensorplay.quantum.amplitude_damping_kraus

Kraus operators of amplitude damping: each qubit relaxes to 0 at rate gamma.

tensorplay.quantum.depolarizing_kraus

Kraus operators of the depolarizing channel: a random Pauli error on n_qubits.

tensorplay.quantum.pauli_kraus

Kraus operators of a Pauli channel: named errors with their chances.

tensorplay.quantum.Hamiltonian

A weighted sum of Pauli words.

tensorplay.quantum.pauli_string_matrix

The dense matrix of a Pauli word.

tensorplay.quantum.energy

The Hamiltonian expectation value for each state in the batch, real.

tensorplay.quantum.trotter_evolve

Advance the state by exp(-i * time * H) with a Trotter product.

tensorplay.quantum.matrix_exp_evolve

Advance the state by the exact exp(-i * time * H).

tensorplay.quantum.param_shift

The gradient of a value built from one angle per parameter, by shifting.

tensorplay.quantum.sample_energy

<H> estimated from a finite number of measurements.

Where to go next

On this page

Ask DeepWiki