Manual
How to read this manual
This manual presents the objects of TMS one after the other, sites, states, operators, then the algorithms and the measurements, with small examples run directly from Julia, and ends with how to check the accuracy of the results. The pages that follow build on it: High Level Interface shows how to write a whole simulation as a list of phases, which writes its results to files and can be stopped and resumed, the way most programs are written; Conserving a quantity tells how the conservation of a quantity is declared, what it forbids and what it saves; Threads and performance is for when a computation gets large; and Extending TMS is for sites, phases, algorithms and representations of one's own. The reference pages give every function in detail.
The examples assume TMS has been imported:
using TensorMixedStatesSites and Systems
The first step in using TMS is the definition of your quantum system. In TMS, a system is composed of a finite number of sites numbered from 1 (1, 2, ..., N). These sites may be all identical or not.
There are eight different predefined types of site: Qubit, Qudit, Fermion, Boson, Spin, Electron, Tj and Qboson.
To use each of these sites and the corresponding predefined operators you need first to import the corresponding module. For example to use qubits, you need to write
using .QubitsNote the "." before the name and the "s" at the end.
To define a site, call the corresponding constructor, for example
s = Qubit()Qubit()Four site constructors need an argument: Qudit(dim) and Boson(dim) for the dimension of the local Hilbert space, Spin(s) for the spin, and Qboson(q, dim) for the deformation parameter and the dimension. For example
using .Bosons, .Spins
s = Boson(4)Boson(4)s = Spin(3/2)Spin(1.5)You can now define a quantum system by declaring the sites it contains:
system1 = System(10, Qubit())gives you a system with 10 qubits. Systems may have sites of different types, in which case System is given an array of sites
using .Fermions
system2 = System([Qubit(), Boson(4), Fermion()])gives you a three site system.
States
A state is either a wave function, its pure representation, written Pure, or a density matrix, its mixed representation, written Mixed, which open systems need. Both are stored as a matrix product state (MPS): a chain of tensors, one per site, joined by links whose size, the bond dimension, measures how much entanglement, or for a density matrix how many correlations, the state can hold. A density matrix is stored as the vector of its entries, so each site has dimension $d^2$ instead of $d$, its norm is the Hilbert-Schmidt norm $\sqrt{\mathrm{tr}\,\rho^\dagger\rho}$ rather than its trace, and it costs more than a wave function of the same system.
To create a state, we call the State constructor
state1 = State{Pure}(system1, "Up")returns a pure up state in a 10 qubit system. Predefined local states are designated by their name. Here "Up" is a predefined state of site Qubit.
Sites need not be all in the same local state, in which case we give State an array of local states
state2 = State{Mixed}(system2, ["+", "2", "Occ"])Here we choose a mixed representation.
States may be added or multiplied by a number (they need to be based on the same system). For example
ghz = (State{Pure}(system1, "Up") + State{Pure}(system1, "Dn")) / sqrt(2)We can transform a pure representation into a mixed representation by
mixedstate = mix(state1)For mixed states there is a local mixed state "FullyMixed" which corresponds to a density matrix proportional to the identity matrix (that is the infinite temperature state).
If you need a local state which is not predefined, it is possible to pass its vector (or matrix for mixed states) directly. For example, we could also define state1 by
state1 = State{Pure}(system1, [1., 0.])Prepared states
States that are not products of local states, but have an exact MPS of small bond dimension, are built by functions of their own, which take the local states they are made of:
myneel = superposition(System(4, Qubit()), [1 => ["Up", "Dn", "Up", "Dn"], 1 => ["Dn", "Up", "Dn", "Up"]])
myghz = ghz_state(System(6, Qubit()), "Up", "Dn") # (|↑↑…↑⟩ + |↓↓…↓⟩)/√2
mydicke = dicke_state(System(6, Qubit()), 2, "Up", "Dn") # two sites down, in every way
mydimers = dimer_state(System(6, Spin(1/2)), [(1, 2), (3, 4), (5, 6)], "1/2", "-1/2")
myaklt = aklt_state(System(6, Spin(1)))
mysector = fully_mixed(System(6, Fermion()), N => 3) # 3 fermions at infinite temperature
measure(mydicke, Z)6-element Vector{Float64}:
0.3333333333333333
0.3333333333333333
0.33333333333333337
0.33333333333333337
0.3333333333333333
0.3333333333333333superposition superposes product states with the coefficients given, here the two Néel states, as mixture mixes them with weights in a mixed representation, w_state is the Dicke state of one site, dimer_state puts a singlet on each pair of sites given, aklt_state is the ground state of the AKLT chain of spins one, and fully_mixed the fully mixed state of a sector, here of 3 fermions. The Slater determinants and the Fermi seas of free fermions are built by slater_state and fermi_sea, see Fermions, and the thermal states by thermalize, see Thermal states. Each is a State, which CreateState takes as its state, see Prepared states for the details.
Conserved quantities
A site can be told that a quantity is conserved, for instance the number of fermions:
mysystem = System(10, Fermion(conserve = N))
mystate = State{Pure}(mysystem, [isodd(i) ? "Occ" : "Emp" for i in 1:10])The tensors are then block sparse, which makes a large computation smaller and faster, and in exchange the state stays in the sector it was built in: a local state such as [1, 1] / √2, which superposes two numbers of particles, is refused, and so is an operator of no definite charge acting on the state, which can still be measured. For a mixed state, a quantity may also be conserved strongly, Fermion(conserve = strong(N)). All of this is described in Conserving a quantity.
Limits
TMS uses Matrix Product States to represent quantum states internally. It is important to control the parameters of this approximation, in particular the maximum bond dimension and the cutoff, the total weight of the singular values a truncation of the tensors may drop. To achieve this, many functions accept a Limits object as keyword argument containing those parameters. It is built thus
lim = Limits(cutoff = 1e-10, maxdim = 50)Limits(cutoff = 1.0e-10, maxdim = 50)Any of the arguments may be omitted: maxdim and mindim then impose nothing, and the default cutoff, 1e-26, only discards what rounding leaves, the singular values below about 1e-13 of the norm, while cutoff = 0 discards nothing. The truncation is a choice to be checked, see Checking the accuracy.
A third parameter, mindim, sets the bond dimension the truncation is not allowed to go below, as in Limits(cutoff = 1e-10, maxdim = 50, mindim = 4). maxdim takes precedence when the two conflict.
To apply the constraints on a state, one uses
newstate = truncate(ghz; limits = lim)Many functions accept such an argument. For example, when adding states instead of
state3 = (state1 + ghz) / 2one can write
state3 = +(state1, ghz; limits = lim) / 2Operators
In TMS, there are two kinds of operators: generic operators and indexed operators. For example,
XXrepresents the $\sigma_x$ Pauli operator for qubits. This is a generic operator, it is not applied to a specific site.
X(3)X(3)represents the $\sigma_x$ Pauli operator applied to the system site number 3. This is an indexed operator.
Note that all predefined operator names start with a capital letter, so it is better to keep your own identifiers lowercase to prevent name collisions with them.
Lowercase is not a safe harbour by itself, though. using TensorMixedStates also brings in more than eighty lowercase names, among them output, measure, trace, dim, matrix, tensor, apply, norm and sample. Assigning to one of them at the top level of your program shadows the function for the rest of the file, and if you happen to have used it before assigning to it, Julia 1.10 and 1.11 refuse the assignment outright with cannot assign a value to imported variable. There is no such issue inside a function, where state = ... is an ordinary local variable. This manual prefixes its own variables with my, as in mystate and myop, which is one way of staying clear. Another is to take from a site module only the names one uses, using .Qubits: Qubit, X, Z, which leaves N free, or to write them qualified after import .Qubits as Q, as Q.X(3).
The operator system is very rich and flexible. For example, if you want to use this Hamiltonian
\[H = \sum_{i=1}^{n-1} \sigma_x(i) \sigma_x(i+1)\]
you will simply write
n = 10
h = sum(X(i)X(i+1) for i in 1:n-1)X(1)*X(2)+X(2)*X(3)+X(3)*X(4)+X(4)*X(5)+X(5)*X(6)+X(6)*X(7)+X(7)*X(8)+X(8)*X(9)+X(9)*X(10)Many operations are defined on generic operators:
- addition, multiplication and power by a number
- tensor product:
X⊗Xis a two site operator (⊗is usually obtained by typing \otimes in your editor, just in case, one can also writetensor(X, X)) dagrepresents the adjoint operator, for exampleCis thecoperator for fermions anddag(C)is $c^\dagger$;C'is the same, and both take placed operators too,h + h'.Dissipatorrepresents a Lindblad dissipator, for exampleDissipator(Sp)is the dissipator whose jump operator,Sp, the $S^+$ operator, flips a qubit toward upGaterepresents an operator to be applied as a gate on a mixed state. It is useful to define noisy gate operators, for example0.9Gate(Id) + 0.1Gate(X)is a noisy gate operator that will apply a $\sigma_x$ gate 10 percent of the time.Projrepresents an operator that projects on the given state, for exampleProj("Up")projects qubits on the up state, or on an eigenspace,Proj(Sz => 0)projecting a spin 1 on the state of zero $S_z$.Levelnumbers the basis states of any site, $\mathrm{diag}(0, 1, \dots)$: its eigenbasis is the basis of the site, andLevel => nthe basis staten, counted from 0.- the functions
expandsqrt: for examplesqrt(Swap) controlledmakes controlled gates, which do nothing when their first site is in its first basis state,Level => 0, as"Up"for a qubit:controlled(X)is theCXof the moduleCircuits, see Gates
For example one can define the Rxy 2-site operator by
Rxy(t) = exp(-im * t * (X⊗X + Y⊗Y) / 4)Rxy (generic function with 1 method)If this is not enough to define your favorite operator you can create new ones with named, from an expression, a matrix or a function of the sites, see Operators and measurements of one's own:
myop = named([1 1 ; 1 -1] / √2, "MyOp")MyOpFinally from generic operators, we define indexed operators by simply applying them to the corresponding sites
Rxy(0.2)(2, 5)exp(-0.05im*X⊗X-0.05im*Y⊗Y)(2,5)myop(3)MyOp(3)Fermions
A fermionic operator is written as it is: C(i) destroys a fermion on site i and dag(C)(j) creates one on site j, the Jordan-Wigner strings being inserted for you wherever the operator is used, in expectation values, in MPOs and in gates. A hopping term is thus simply
hopping = sum(dag(C)(i)C(i+1) + dag(C)(i+1)C(i) for i in 1:n-1)The strings follow the order of the sites: C(j) is the matrix of C on site j with F on every site before it. A product of two fermionic operators placed in increasing order of the sites, A(i) * B(j) with i < j, is thus A * F on site i, F on every site in between and B on site j, and in the other order it takes the sign of the swap. A matrix given for an operator of several sites, see Operators and measurements of one's own, is read in the basis of the sites as it is, with no string.
A function of an even fermionic operator of several sites, as the exponential of a hopping term, exp(-0.1im * (dag(C) ⊗ C + dag(dag(C) ⊗ C)))(2, 5), is applied as a gate with its strings, on sites in any order and apart. A function of an odd operator, as exp(C), mixes the two parities: it is split into its even part and its odd part, which takes the strings.
The ground state of free fermions, the Fermi sea of a quadratic hamiltonian, is built at once by fermi_sea, here with three fermions, and any Slater determinant by slater_state, see Prepared states:
sea = fermi_sea(System(n, Fermion()), -hopping, 3)
measure(sea, N)10-element Vector{Float64}:
0.1714223902218396
0.3817216995052644
0.3351266180805347
0.2687201853988544
0.3430091067935101
0.3430091067935089
0.2687201853988528
0.33512661808053745
0.38172169950526236
0.17142239022183706Algorithms
We can now work with states and operators.
Gates
The gates of quantum circuits are in the module Circuits: H, S, T, Phase, the rotations, Rx(θ) being $e^{-i\theta X/2}$ on a qubit as in OpenQASM and Qiskit and Rxx(θ) $e^{-i\theta X \otimes X/2}$, and the controlled gates CX or CNOT, CY, CZ, and CCX or Toffoli:
using .CircuitsGates are applied with apply. In a product of gates the rightmost acts first: here the Hadamard gate on the first qubit, then the controlled not, which turns two up qubits into a Bell pair
mybell = apply(CX(1, 2) * H(1), State{Pure}(System(2, Qubit()), "Up"))
measure(mybell, [Z(1)Z(2), X(1)X(2)])2-element Vector{Pair{String, Any}}:
"Z(1)*Z(2)" => 1.0
"X(1)*X(2)" => 1.0Applying all the gates in a single call is much more efficient than one by one, and the keyword argument limits constrains the truncations made along the way.
Ground states
The ground state of a hamiltonian is computed by ground_state, from a starting state, here a random state of bond dimension 8, by the method algo, Dmrg. It returns the energy and the ground state, which can then be measured. For the Ising chain in a transverse field
mysystem = System(10, Qubit())
hamiltonian = -sum(Z(i)Z(i + 1) for i in 1:9) - sum(X(i) for i in 1:10)
energy, ground = ground_state(hamiltonian, random_state(Pure, mysystem, 8);
algo = Dmrg(nsweeps = 10), limits = Limits(maxdim = 20))
energy-12.381489999654798measure(ground, X)10-element Vector{Float64}:
0.8512118670042681
0.7324519047454634
0.7021738068131279
0.6900669649442622
0.6853707301414419
0.6853707301414402
0.6900669649442619
0.7021738068131284
0.7324519047454645
0.8512118670042708nsweeps is the number of sweeps, passes of dmrg along the chain and back, Dmrg(tol = …) stops the search earlier once the energy changes less than tol between two sweeps, and limits may give one value per sweep, as in Limits(maxdim = [10, 20, 50]).
Given eigenstates found before, orthogonal_to, the search gives the lowest eigenstate orthogonal to them, an excited state: here the first excited state, and the gap
energy1, excited = ground_state(hamiltonian, random_state(Pure, mysystem, 8);
algo = Dmrg(nsweeps = 20), limits = Limits(maxdim = 20),
orthogonal_to = [ground])
energy1 - energy0.29892037434574625A penalty, weight times the projectors on the states given, is added to the hamiltonian for that. It must exceed the gap between the state searched and each of them, and its default, 1.1 times a bound of the width of the spectrum, always does but slows the convergence, which a weight just above the gap speeds up, see ground_state.
Time evolution
Time evolution is done by evolve, which takes an evolver, the time to evolve for and the state, and the method as algo, Tdvp() by default, or ApproxW. The evolver follows the convention
evolver = -im * hamiltonian + dissipatorsdissipators being accepted on a mixed state only. On a pure state, it integrates $\frac{d}{dt}|\psi\rangle = -iH|\psi\rangle$, and on a mixed state the Lindblad equation
\[\frac{d\rho}{dt} = -i[H, \rho] + \sum_k \left(L_k \rho L_k^\dagger - \tfrac{1}{2}\{L_k^\dagger L_k, \rho\}\right)\]
each term Dissipator(L) giving one jump operator $L_k$, with $\hbar = 1$. A rate $\gamma$ is written γ * Dissipator(L), or equally Dissipator(sqrt(γ) * L), since Dissipator(c * L) is abs2(c) * Dissipator(L). Under $H = \sum_i \sigma_z^i$, qubits starting in "+" precess, with $\langle \sigma_x \rangle = \cos 2t$, which is $\cos 1 \approx 0.5403$ at $t = 0.5$:
myevolved = evolve(-im * sum(Z(i) for i in 1:4), 0.5, State{Pure}(System(4, Qubit()), "+");
nsteps = 10)
measure(myevolved, X)4-element Vector{Float64}:
0.5403023058681404
0.54030230586814
0.5403023058681382
0.5403023058681393nsteps is the number of steps, each of length t / nsteps, and limits constrains the state as before. With no hamiltonian and Dissipator(Sm) on each site, the qubits of a mixed state go from up to down, with $\langle \sigma_z \rangle = 2e^{-t} - 1$, which is $-0.2642$ at $t = 1$:
myrho = State{Mixed}(System(4, Qubit()), "Up")
mydecayed = evolve(sum(Dissipator(Sm)(i) for i in 1:4), 1.0, myrho; nsteps = 10)
measure(mydecayed, Z)4-element Vector{Float64}:
-0.26424111765711483
-0.2642411176571154
-0.26424111765711555
-0.26424111765711494ApproxW approximates the exponential of the evolver over a step by MPOs, built from the approximations WI or WII of Zaletel et al. It is given the order of its approximation, from 1 to 4, and w, 1 or 2 for WI or WII, for which order = 4, w = 2 is usually a good choice:
mydecayed = evolve(sum(Dissipator(Sm)(i) for i in 1:4), 1.0, myrho;
algo = ApproxW(order = 4, w = 2), nsteps = 10)
measure(mydecayed, Z)4-element Vector{Float64}:
-0.2642411176571155
-0.26424111765711683
-0.26424111765711644
-0.2642411176571165An evolver may also depend on time, see Time dependent evolvers.
Noise
Noise acts on a mixed state, either as a channel applied between gates, or as a term of an evolver. Three kinds are ready for sites of any type, each as a channel …_gate(p, …), taking place with probability p, and as a generator …_dissipator(γ, …), of rate γ:
reset_channel(p, state)resets a site tostate, seeReset; withn,reset_channel(p, n, state)resetsnsites together, andreset_channel(p, [s1, s2])each site to its state;depolarizing_channel(p, n)is the reset to"FullyMixed": on a qubit, the channel applying each Pauli operator with probabilityp/3isdepolarizing_channel(4p/3);dephasing_channel(p, A)erases the coherences between the eigenspaces ofA, or in the basis of the site withoutA, seeDephase.
Evolving under a generator for a time t is the channel of probability $1 - e^{-\gamma t}$. Dephasing qubits in "+" at rate 1 gives $\langle \sigma_x \rangle = e^{-t}$, which is $0.6065$ at $t = 0.5$, both ways:
myplus = State{Mixed}(System(2, Qubit()), "+")
mydephased = evolve(sum(dephasing_dissipator(1.0)(i) for i in 1:2), 0.5, myplus; nsteps = 10)
measure(mydephased, X)2-element Vector{Float64}:
0.6065306597126334
0.6065306597126329measure(apply(prod(dephasing_channel(1 - exp(-0.5))(i) for i in 1:2), myplus), X)2-element Vector{Float64}:
0.6065306597126334
0.6065306597126334Sites reset together are not sites reset each on its own: depolarizing_channel(p, 2) depolarizes both sites with probability p, where depolarizing_channel(p) ⊗ depolarizing_channel(p) depolarizes each with probability p. And dephasing_dissipator(γ, A) damps every coherence between two eigenspaces of A at the rate γ, where Dissipator(A) damps it at $(\lambda_a - \lambda_b)^2/2$, faster for distant eigenvalues; they agree on a qubit, Dissipator(Z) being dephasing_dissipator(2, Z).
The reset, and so the depolarization, changes the charges and is refused on sites conserving something strongly, where a dephasing in the eigenbasis of an operator commuting with the charge, as N, is accepted. Any other channel is a sum of gates, Gate(K) being $\rho \mapsto K \rho K^\dagger$, or the evolution under a Lindbladian L of a few sites for a time t, exp(t * L).
On a qubit, "Up" is $|0\rangle$ and "Dn" $|1\rangle$, the excitation N counts: the decay of an excitation, from "Dn" to "Up", is Dissipator(Sp), and Dissipator(Sm), which takes "Up" to "Dn" in the evolution above, excites. The decay is given for sites of any type by amplitude_damping_channel and amplitude_damping_dissipator, of the excitations an operator destroys, Sp by default, and the relaxation of times $T_1$ and $T_2$ of a qubit, its decay and its dephasing, by thermal_relaxation_channel and thermal_relaxation_dissipator of the module Qubits.
Steady states
steady_state looks for the state $\rho$ with $L\rho = 0$, $L$ being the evolver, starting from a mixed state, by running dmrg on $L^\dagger L$. It returns two things: the residual $\|L\rho\|^2$, for $\rho$ of unit Hilbert-Schmidt norm, which is zero at a true steady state and tells how well the search converged, and the steady state itself, normalized to trace one. A Lindbladian may have several steady states, see Checking the accuracy. Qubits jumping from up to down at rate 1 and from down to up at rate 0.5 settle at $\langle \sigma_z \rangle = -1/3$:
value, mysteady = steady_state(sum(Dissipator(Sm)(i) + 0.5Dissipator(Sp)(i) for i in 1:4),
myrho; algo = Dmrg(nsweeps = 10))
measure(mysteady, Z)4-element Vector{Float64}:
-0.33333333333333437
-0.3333333333333349
-0.3333333333333338
-0.3333333333333334Thermal states
thermalize takes a mixed state $\rho$ to $e^{-\beta H/2} \rho \, e^{-\beta H/2}$, normalized, by an evolution in imaginary time, by Tdvp unless its algo says otherwise. From "FullyMixed", the state at infinite temperature, it gives the thermal state $e^{-\beta H}/Z$. It returns the logarithm of the trace the state would have without being normalized, here $\log Z - 4 \log 2$, and the state. Qubits in the field $H = -\sum_i \sigma_z^i$ have $\langle \sigma_z \rangle = \tanh \beta$, which is $0.4621$ at $\beta = 0.5$, and $\log Z - 4 \log 2 = 4 \log \cosh \beta \approx 0.4805$:
mylog, mythermal = thermalize(-sum(Z(i) for i in 1:4), 0.5,
State{Mixed}(System(4, Qubit()), "FullyMixed"); nsteps = 10)
mylog0.48045802783310904measure(mythermal, Z)4-element Vector{Float64}:
0.46211715726000985
0.46211715726000985
0.4621171572600098
0.46211715726000835For more details, see the Algorithms page of the reference or the inline help.
Measurements
Once we have created a state, we may want to measure it. Take for example a three qubit state
mystate = State{Pure}(System(3, Qubit()), ["+", "Up", "+"])result = measure(mystate, X(1)X(3))1.0will give $\langle \psi | \sigma_x^1 \sigma_x^3 | \psi \rangle$
result = measure(mystate, X)3-element Vector{Float64}:
1.0
0.0
1.0will give the array of the $\langle \psi | \sigma_x^i | \psi \rangle$
result = measure(mystate, (X, Y))3×3 Matrix{ComplexF64}:
0.0+0.0im 0.0+0.0im 0.0+0.0im
0.0+0.0im 0.0+1.0im 0.0+0.0im
0.0-4.26642e-17im 0.0+0.0im 0.0+0.0imwill give the matrix of the $\langle \psi | \sigma_x^i \sigma_y^j | \psi \rangle$, complex since its diagonal is $\langle \sigma_x \sigma_y \rangle = i \langle \sigma_z \rangle$
A fermionic operator is measured as any other, its Jordan-Wigner strings laid for it, in a correlation as (dag(C), C) too. A single one, as C(3), has a value only on a state that superposes parities, and vanishes on any other.
We can also measure properties of the state as a whole, with measurements such as Trace, Purity, EntanglementEntropy(cut) or Fidelity(ref): the Measurements page has the table of them all.
A few sites can be looked at together: ReducedDensityMatrix(positions) measures their density matrix, VonNeumannEntropy(positions) their entropy and LogNegativity(a, b) the entanglement between two parts of them, which on a mixed state the entropy no longer tells. Both are $\log 2$ for one qubit of a Bell pair:
mybell = apply(CX(1, 2) * H(1), State{Pure}(System(3, Qubit()), "Up"))
measure(mybell, [VonNeumannEntropy([1]), LogNegativity([1], [2])])2-element Vector{Pair{String, Any}}:
"VonNeumannEntropy(1)" => 0.6931471805599453
"LogNegativity(1;2)" => 0.6931471805599453We can also ask for several measurements at the same time
results = measure(mystate, [X, X(2)Z(3), (X, Y), Trace, MemoryUsage])5-element Vector{Pair{String, Any}}:
"X" => [1.0, 0.0, 1.0]
"X(2)*Z(3)" => 0.0
"(X, Y)" => ComplexF64[0.0 + 0.0im 0.0 + 0.0im 0.0 + 0.0im; 0.0 + 0.0im 0.0 + 1.0im 0.0 + 0.0im; 0.0 - 4.266421588589643e-17im 0.0 + 0.0im 0.0 + 0.0im]
"Trace" => 0.9999999999999997 + 0.0im
"MemoryUsage" => 5488A measurement can also be drawn, as in an experiment: probabilities(state, op) gives the results of measuring an operator placed on sites with their probabilities, sample(state, op) draws one, and collapse(state, op) draws one and gives it with its probability and the state projected onto it, (result, probability, state). Any Hermitian operator of a few sites can be measured, and on any number of sites a string of Pauli operators or the number of particles of a region. The parity of two qubits in "+" is ±1 with probability 1/2, and either result leaves an entangled pair:
mytwo = State{Pure}(System(2, Qubit()), "+")
probabilities(mytwo, Z(1) * Z(2))2-element Vector{Pair{Float64, Float64}}:
-1.0 => 0.5
1.0 => 0.5myparity, myprob, mypair = collapse(mytwo, Z(1) * Z(2))
myparity, measure(mypair, X(1)X(2))(-1.0, 1.0000000000000002)Given the result, collapse(state, op => λ) returns the same, the state projected onto that result, which post-selects it:
_, myprob, mypair = collapse(mytwo, Z(1) * Z(2) => -1)
myprob, measure(mypair, [Z(1)Z(2), X(1)X(2)])(0.5, Pair{String, Any}["Z(1)*Z(2)" => -1.0000000000000002, "X(1)*X(2)" => 1.0000000000000002])For more details see the reference or the inline help.
Accuracy
A result is trusted once it no longer moves when the truncation, the time step and the sweeps are improved: how to choose them and how to check what they leave is the subject of Choosing and checking the parameters.