A Yao.jl backend to efficiently simulate fermionic linear optics (FLO) circuits based on Classical simulation of noninteracting-fermion quantum circuits and Disorder-assisted error correction in Majorana chains. FLO circuits are a class of quantum circuits that are closely related to non-interacting fermions and can be efficiently simulated on classical computers, similar to the way Clifford circuits can be efficiently classically simulated, as is done in YaoClifford.jl.

The goal of FLOYao.jl is that if you have code written in Yao.jl that only uses FLO gates and other primitives that are efficiently simulatable in polynomial time and space, that you can simply replace your AbstractArrayReg with a MajoranaReg and run exactly the same simulation, with the same code but exponentially faster.

A brief introduction to fermionic linear optics circuits is found in the Documentation and a more in-depth introduction in e.g. the two papers linked above.

MajoranaReg{T} <: AbstractRegister{2}

A register holding the "state" of a Majorana operators when propagating through a FLO circuit as a 2n×2n matrix.


The MajoranaReg constructor will not initialize the state matrix. It is recommended to use FLOYao.zero_state or FLOYao.product_state to produce your initial state.

bitstring_probability(reg::MajoranaReg, bit_string::BitStr)

The probability to measure a given bit_string when measuring reg


The covariance matrix

\[ M_{pq} = \frac{i}{2} ⟨Ω|U^†[γ_p,γ_q]U|Ω⟩\]

of a FLO state $U|Ω⟩$.

fast_add!(A::AbstractMatrix, B::SparseMatrixCSC)

Fast implementation of A .+= B for sparse B.

fast_overlap(y::AbstractVecOrMat, A::SparseMatrixCSC, x::AbstractVecOrMat)

Fast implementation of tr(y' * A * x) for sparse A.


Get the indices of majorana operators from a kronecker product of pauli operators


Converts a 2n×2n MajoranaReg reg into a 2^n ArrayReg.


This implementation is not very clever and should mainly be used for debugging purposes with small numbers of qubits. It pipes a random state $|ψ⟩$ through the projector $U|Ω⟩⟨Ω|U^†$ which may give inaccurate results if $⟨ψ|U|ψ⟩$ is very small.

majorana_expect(block::AbstractMatrix, locs, reg::MajoranaReg)

Calculate ⟨reg|block|reg⟩ where block is a hamiltonian written as the coefficients in a sum of squares of Majorana operators and reg a MajoranaReg.


Majorana operator in a system with n on site Majorana site i


Converts an $2n×2n$ Majorana hamiltonian H into the full $2^n×2^n$ hamiltonian in the qubit basis.


Put reg into the all ones state

one_state([T=Float64,] n)

Create a Majorana register on n qubits in the all one state $|1 ⋯ 1⟩$ with storage type T.

paulibasis2majoranasquares(P::AbstractVector, locs=1:log4(length(P)))

Convert an operator written in the Pauli basis as a $4^n$-element vector to the corresponding $2n×2n$ matrix of coefficients of products of two Majorana operators.

Throws a NonFLOException if P contains terms that are not corresponding to the product of two Majorana operators.


Converts an operator to in the pauli basis to a matrix in the computational basis. Inverse to qubit2paulibasis.

product_state!(reg::MajoranaReg, bit_str::BitStr)

Put reg into the product state described by bit_str

product_state([T=Float64,] bit_str::DitStr{2})
product_state([T=Float64,] bit_configs::AbstractVector)
product_state([T=Float64,] nbits::Int, val::Int)

Create an MajoranaReg of a product state.

The state can be specified as a bit string, as an array of Integers or Booleans or with nbits and val.

qubit2majoranaevolution(U::AbstractMatrix, locs)

Turns a $n$ qubit unitary U on the $n$ qubits in locs into the corresponding $SO(2n×2n)$ matrix for the evolution of the Majorana operators.


Converts a $2^n×2^n$ matrix A in the standard qubit basis into a $4^n$ vector representing the same operator in the Pauli basis.

The ordering is as follows: Let $σ^0 = I, σ^1 = X, σ^2 = Y$ and $σ^3 = Z$.


Creating the dense pauli tensor product via kron and inner product via dot is much slower than neccessary, since the pauli tensor product matrix is very sparse. Much faster would be to compute the inner product directly.

rand_state([Float64,] n)

Create a Haar random MajoranaReg on n qubits.

random_unit_vector([Float64, ] n, N=n)

Generate a uniformly random vector on the n-sphere embedded in ℝ^N and return it and the sign of its first entry.

reducedmat(::Type{T}, k::KronBlock)

Matrix representation of k with only the qubits k acts on non-trivially in the tensor product. (As opposed to Yao.mat(k) which gives the full 2^n×2^n matrix)

sample!(covmat, locs=1:size(covmat,1)÷2, ids=sortperm(locs), rng=GLOBAL_RNG)

Take a computational basis state sample from a FLO state with given covarianvematrix


  • covmat: The 2n×2n covariance matrix $M_{ij} = i ⟨ψ|γ_i γ_j|ψ⟩ - i δ_{ij}$. This matrix will be overwritten by the function!
  • locs=1:size(covmat,1)÷2: The list of qubits to measure
  • ids=sortperm(locs): The sorting permutation for locs. You don't need to pass this, but precomputing it can make this faster
  • rng=Random.GLOBAL_RNG: The random number generator to use for sampling.


This function works inplace on the covmat argument to avoid allocations. If you need multiple samples from the same covmat, make sure to pass a copy in.


Updates the covariance matrix M, given that the ith qubit was measured in the state ni with probability pi.


After this procedure only the entries M[p,q] with 2i-1 < p < q will be correct. This is sufficient to calculate measurement probabilities for j > i.

yaoham2majoranasquares(::Type{T}=Float64, yaoham::AbstracBlock{2})

Convert a hamiltonian written as a YaoBlock into the corresponding $2n×2n$ majorana hamiltonian.


Put reg into the computational zero state

zero_state([T=Float64,] n)

Create a Majorana register on n qubits in the vacuum state $|Ω⟩$ with storage type T.


Create a Majorana register in the zero state with the same element type and number of qubits as reg.

expect(op::AbstractBlock, reg::MajoranaReg)
expect(op::AbstractBlock, (reg => circuit)::Pair{<:MajoranaReg,<:AbstractBlock})

Get the expectation value of an operator, the second parameter can be a register reg or a pair of input register and circuit reg => circuit.

expect'(op::AbstractBlock, reg => circuit::) -> Pair{<:MajoranaReg,<:AbstractVector}
expect'(op::AbstractBlock, reg::MajoranaReg) -> MajoranaReg

Obtain the gradient with respect to registers and circuit parameters. For pair input, the second return value is a pair of gψ => gparams, with the gradient of input state and gparams the gradients of circuit parameters. For register input, the return value is a register.

fidelity(reg1::MajoranaReg, reg2::MajoranaReg)

The fidelity $|⟨ψ|φ⟩|$ between the FLO states $|ψ⟩$ and $|φ⟩$ defined by reg1 and reg2.


This definition differs from the standard definition $|⟨φ|ψ⟩|²$ by a square root, but is consistent with the one used in Yao.jl


Computing gradients of the fidelity via fidelity'(pair_or_reg1, pair_or_reg2) is not yet supported for MajoranaRegs.

measure!([postprocess::Union{NoPostProcess, ResetTo},] reg::MajoranaReg; rng=Random.GLOBAL_RNG)

Measure a Majorana register in the computational basis and return the resulting BitStr.


  • postprocess: Is the post processing method
    • NoPostProcess() (the default) will collapse the state to the measurement outcome state.
    • ResetTo(bit_str) will reset the state to the product state specified by bit_str
  • reg::MajoranaReg: The quantum register
  • rng::Random.GLOBAL_RNG: The RNG to use
measure(reg::MajoranaReg[, locs]; nshots=1, rng=Random.GLOBAL_RNG) -> Vector{BitStr}

Measure a MajoranaReg and return the results as a vector of BitStrings. This is a cheating version of measure! that does not need to recompute the reg for each new sample and also does not alter it.


  • reg::MajoranaReg: The state register
  • locs: The list of qubits to measure. Defaults to all qubits.
  • nshots=1: The number of samples to take
  • rng=Random.GLOBAL_RNG: The random number generator to use