Core
Qecsim
Qecsim
— ModulePackage for simulating quantum error correction using stabilizer codes.
Qecsim.QecsimError
— TypeQecsimError <: Exception
QecsimError(msg)
Construct an exception indicating an internal (core or models) error.
App
Qecsim.App
— ModuleFunctions to run quantum error correction simulations and merge/read/write output data.
Qecsim.App.qec_run_once
— Functionqec_run_once(
code, error_model, decoder, p::Real, rng::AbstractRNG=GLOBAL_RNG
) -> RunResult
Execute a stabilizer code error-decode-recovery (ideal) simulation and return run result.
The parameters code
, error_model
and decoder
should be concrete subtypes or duck-typed implementations of StabilizerCode
, ErrorModel
and Decoder
, respectively.
The simulation algorithm is as follows:
- $S ←$
stabilizers(code)
- $L ←$
logicals(code)
- $e ←$
generate(error_model, code, p, rng)
- $y ← S ⊙ e$
decode_result
$←$decode(decoder, code,
$y$; kwargs...)
- $r ←$
decode_result.recovery
- sanity check: $S ⊙ (r ⊕ e) = 0$
logical_commutations
$← L ⊙ (r ⊕ e)$success
$← L ⊙ (r ⊕ e) = 0$
where $⊕$ denotes element-wise exclusive-or, and $⊙$ is defined in PauliTools.bsp
.
The kwargs
passed to decode
include error_model
, p
and error
; most decoders will ignore these parameters. The decode
method returns a DecodeResult
. If decode_result.success
and/or decode_result.logical_commutations
are specified, they override the values of success
and logical_commutations
, irrespective of whether decode_result.recovery
is specified or not. The value decode_result.custom_values
is passed through in the run result.
See also RunResult
.
Examples
julia> using Qecsim.BasicModels, Qecsim.GenericModels, Random
julia> rng = MersenneTwister(6); # use random seed for reproducible result
julia> qec_run_once(FiveQubitCode(), DepolarizingErrorModel(), NaiveDecoder(), 0.2, rng)
RunResult{Nothing}(false, 2, Bool[1, 0], nothing)
Qecsim.App.RunResult
— TypeRunResult(
success::Bool,
error_weight::Int,
logical_commutations::Union{Nothing,BitVector}
custom_values::Union{Nothing,Vector}
)
Construct a run result as returned by qec_run_once
.
Examples
julia> r = RunResult(false, 2, BitVector([0, 1]), [1.2, 3.1])
RunResult{Vector{Float64}}(false, 2, Bool[0, 1], [1.2, 3.1])
julia> r.success, r.error_weight, r.logical_commutations, r.custom_values
(false, 2, Bool[0, 1], [1.2, 3.1])
Qecsim.App.qec_run
— Functionqec_run(
code, error_model, decoder, p::Real, random_seed=nothing;
max_runs::Union{Integer,Nothing}=nothing,
max_failures::Union{Integer,Nothing}=nothing
) -> Dict
Execute stabilizer code error-decode-recovery (ideal) simulations many times and return aggregated run data, see qec_run_once
for details of a single run.
The parameters code
, error_model
and decoder
should be concrete subtypes or duck-typed implementations of StabilizerCode
, ErrorModel
and Decoder
, respectively.
Simulations are run one or more times as determined by max_runs
and max_failures
. If max_runs
and/or max_failures
are specified, stop after max_runs
runs or max_failures
failures, whichever happens first. If neither is specified, stop after one run.
The returned aggregated data has the following format:
Dict(
:code => "5-qubit" # label(code)
:n_k_d => (5, 1, 3) # nkd(code)
:time_steps => 1 # always 1 for ideal simulations
:error_model => "Depolarizing" # label(error_model)
:decoder => "Naive" # label(decoder)
:error_probability => 0.1 # p
:measurement_error_probability => 0.0 # always 0.0 for ideal simulations
:n_run => 100 # count of runs
:n_success => 92 # count of successful recoveries
:n_fail => 8 # count of failed recoveries
:n_logical_commutations => [5, 6] # count of logical_commutations
:custom_totals => nothing # sum of custom_values
:error_weight_total => 55 # sum of error_weight over n_run runs
:error_weight_pvar => 0.4075 # pvariance of error_weight over n_run runs
:logical_failure_rate => 0.08 # n_fail / n_run
:physical_error_rate => 0.11 # error_weight_total / n_k_d[1] / time_steps / n_run
:wall_time => 0.00253906 # wall-time for run in fractional seconds
)
Examples
julia> using Qecsim.BasicModels, Qecsim.GenericModels
julia> seed = 7;
julia> data = qec_run(FiveQubitCode(), DepolarizingErrorModel(), NaiveDecoder(), 0.1, seed;
max_runs=100);
┌ Info: qec_run: starting
│ code = Qecsim.BasicModels.BasicCode(["XZZXI", "IXZZX", "XIXZZ", "ZXIXZ"], ["XXXXX"], ["ZZZZZ"], (5, 1, 3), "5-qubit")
│ error_model = Qecsim.GenericModels.DepolarizingErrorModel()
│ decoder = Qecsim.GenericModels.NaiveDecoder(10)
│ p = 0.1
│ random_seed = 7
│ max_runs = 100
└ max_failures = nothing
[ Info: qec_run: rng=MersenneTwister(7)
[ Info: qec_run: complete: data=Dict{Symbol, Any}(:error_weight_pvar => 0.4075000000000001, :time_steps => 1, :n_logical_commutations => [5, 6], :error_weight_total => 55, :wall_time => 0.002539058, :n_k_d => (5, 1, 3), :error_model => "Depolarizing", :physical_error_rate => 0.11, :measurement_error_probability => 0.0, :error_probability => 0.1, :n_success => 92, :logical_failure_rate => 0.08, :custom_totals => nothing, :code => "5-qubit", :decoder => "Naive", :n_fail => 8, :n_run => 100)
julia> data
Dict{Symbol, Any} with 17 entries:
:error_weight_pvar => 0.4075
:time_steps => 1
:n_logical_commutations => [5, 6]
:error_weight_total => 55
:wall_time => 0.00253906
:n_k_d => (5, 1, 3)
:error_model => "Depolarizing"
:physical_error_rate => 0.11
:measurement_error_probability => 0.0
:error_probability => 0.1
:n_success => 92
:logical_failure_rate => 0.08
:custom_totals => nothing
:code => "5-qubit"
:decoder => "Naive"
:n_fail => 8
:n_run => 100
Qecsim.App.qec_merge
— Functionqec_merge(data...) -> Vector{Dict}
Merge simulation run data.
Run data is expected in the format specified by qec_run
. Merged data is grouped by: (:code, :n_k_d, :error_model, :decoder, :error_probability, :time_steps, :measurement_error_probability)
. The scalar values: :n_run
, :n_success
, :n_fail
, :error_weight_total
and :wall_time
are summed. The vector value: :n_logical_commutations
is summed element-wise. The vector value: :custom_totals
is summed element-wise for scalar elements and concatenated along dimension 1 (vcat
) for array elements. The values: :logical_failure_rate
and :physical_error_rate
are recalculated. The value :error_weight_pvar
is not currently recalculated and is therefore omitted.
Examples
julia> using Qecsim.BasicModels, Qecsim.GenericModels, Logging
julia> code, error_model, decoder = FiveQubitCode(), BitFlipErrorModel(), NaiveDecoder();
julia> data = Dict[];
julia> with_logger(NullLogger()) do # disable logging for brevity in doctest
push!(data, qec_run(code, error_model, decoder, 0.08, 19; max_runs=100))
push!(data, qec_run(code, error_model, decoder, 0.08, 23; max_runs=100))
end;
julia> qec_merge(data...)
1-element Vector{Dict{Symbol, Any}}:
Dict(:measurement_error_probability => 0.0, :error_probability => 0.08, :time_steps => 1, :error_weight_total => 83, :n_logical_commutations => [11, 3], :wall_time => 0.0028351720000000004, :n_k_d => (5, 1, 3), :error_model => "Bit-flip", :n_success => 189, :logical_failure_rate => 0.055…)
Qecsim.App.qec_read
— Functionqec_read(io::IO) -> Vector{Dict{Symbol,Any}}
qec_read(filename::AbstractString) -> Vector{Dict{Symbol,Any}}
Read simulation run data from the given I/O stream or file.
Run data is expected in the format written by qec_write
(i.e. a JSON array of objects using default JSON encoding of the format specified by qec_run
). The read data is converted to the specified return type as follows: dictionary keys are converted to symbols, :n_k_d
entries are converted to tuples, and vector types are inferred from their elements. If this conversion fails, an exception is thrown.
The JSON format is compatible with the format used by the Python package qecsim.
See also qec_write
.
Qecsim.App.qec_write
— Functionqec_write(io::IO, data...)
qec_write(filename::AbstractString, data...)
Write simulation run data to the given I/O stream or file.
Run data is expected in the format specified by qec_run
and written as a JSON array of objects using default JSON encoding. No checking of the format of the given data is performed. The file version of this method will refuse to overwrite an existing file, instead attempting to log the unwritten data and throwing an exception.
The JSON format is compatible with the format used by the Python package qecsim.
See also qec_read
.
Model
Qecsim.Model
— ModuleAbstract types and methods for codes, error models and decoders.
Model.AbstractModel
Qecsim.Model.AbstractModel
— TypeAbstract supertype for models.
Qecsim.Model.label
— Functionlabel(model) -> String
Return a label suitable for use in plots and for grouping results.
This method should be implemented for concrete subtypes or duck-typed implementations of AbstractModel
.
Model.StabilizerCode
Qecsim.Model.StabilizerCode
— TypeStabilizerCode <: AbstractModel
Abstract supertype for stabilizer codes.
Qecsim.Model.logical_xs
— Functionlogical_xs(code) -> BitMatrix
Return the logical X operators in binary symplectic form.
Each row is an operator. The order should match that of logical_zs
.
This method should be implemented for concrete subtypes or duck-typed implementations of StabilizerCode
.
Qecsim.Model.logical_zs
— Functionlogical_zs(code) -> BitMatrix
Return the logical Z operators in binary symplectic form.
Each row is an operator. The order should match that of logical_xs
.
This method should be implemented for concrete subtypes or duck-typed implementations of StabilizerCode
.
Qecsim.Model.logicals
— Functionlogicals(code) -> BitMatrix
Return the logical operators in binary symplectic form.
Each row is an operator. X operators are stacked above Z operators in the order given by logical_xs
and logical_zs
.
Qecsim.Model.nkd
— Functionnkd(code) -> Tuple{Int,Int,Union{Int,Missing}}
Return a descriptor in the format (n, k, d)
, where n
is the number of physical qubits, k
is the number of logical qubits, and d
is the distance of the code (or missing
if unknown).
This method should be implemented for concrete subtypes or duck-typed implementations of StabilizerCode
.
Qecsim.Model.stabilizers
— Functionstabilizers(code) -> BitMatrix
Return the stabilizers in binary symplectic form.
Each row is a stabilizer generator. An overcomplete set of generators can be included to simplify decoding.
This method should be implemented for concrete subtypes or duck-typed implementations of StabilizerCode
.
Qecsim.Model.validate
— Functionvalidate(code)
Perform sanity checks.
If any of the following fail then a QecsimError
is thrown:
- $S ⊙ S^T = 0$
- $S ⊙ L^T = 0$
- $L ⊙ L^T = Λ$
where $S$ and $L$ are the code stabilizers
and logicals
, respectively, and $⊙$ and $Λ$ are defined in PauliTools.bsp
.
Model.ErrorModel
Qecsim.Model.ErrorModel
— TypeErrorModel <: AbstractModel
Abstract supertype for error models.
Qecsim.Model.generate
— Functiongenerate(error_model, code, p::Real, [rng::AbstractRNG=GLOBAL_RNG]) -> BitVector
Generate a new error in binary symplectic form according to the error_model
and code
, where p
is typically the probability of an error on a single qubit.
This method should be implemented for concrete subtypes or duck-typed implementations of ErrorModel
.
Qecsim.Model.probability_distribution
— Functionprobability_distribution(error_model, p::Real) -> NTuple{4,Real}
Return the single-qubit probability distribution amongst Pauli I, X, Y and Z, where p
is the overall probability of an error on a single qubit.
This method is not invoked by any core modules. Since it is often useful for decoders, it is provided as a template and concrete subtypes or duck-typed implementations of ErrorModel
are encouraged to implement it when appropriate, particularly for IID error models.
Model.Decoder
Qecsim.Model.Decoder
— TypeDecoder <: AbstractModel
Abstract supertype for decoders.
Qecsim.Model.decode
— Functiondecode(decoder, code, syndrome::AbstractVector{Bool}; kwargs...) -> DecodeResult
Resolve a recovery operation for the given code
and syndrome
, or evaluate the success of decoding, as encapsulated in the decode result.
The syndrome has length equal to the number of code stabilizers, and element values of 0 or 1 (false or true) indicate whether the corresponding stabilizer does or does not commute with the error, respectively.
Keyword parameters kwargs
may be provided by the client, e.g. App
, with context values such as error_model
, error_probability
and error
. Most implementations will ignore such parameters; however, if they are used, implementations should declare them explicitly and treat them as optional.
See also DecodeResult
.
This method should be implemented for concrete subtypes or duck-typed implementations of Decoder
.
Qecsim.Model.DecodeResult
— TypeDecodeResult(
success::Union{Nothing,Bool},
recovery::Union{Nothing,AbstractVector{Bool}},
logical_commutations::Union{Nothing,AbstractVector{Bool}}
custom_values::Union{Nothing,AbstractVector}
)
DecodeResult(;
success::Union{Nothing,Bool}=nothing,
recovery::Union{Nothing,AbstractVector{Bool}}=nothing,
logical_commutations::Union{Nothing,AbstractVector{Bool}}=nothing
custom_values::Union{Nothing,AbstractVector}=nothing
)
Construct a decoding result as returned by decode
.
Typically decoders will provide a recovery
operation and delegate the evaluation of success
and logical_commutations
to the client, e.g. App
. Optionally, success
and/or logical_commutations
may be provided as overrides. At least one of recovery
or success
must be specified to allow a success value to be resolved. Additionally custom_values
may be specified. If logical_commutations
and/or custom_values
are provided then they should be of consistent type and size over identically parameterized simulation runs. For example, App
will sum logical_commutations
across runs and, similarly, if custom_values
are numbers they will be summed across runs, and if they are arrays they will be concatenated using vcat
.
See also decode
.
Examples
julia> DecodeResult(; recovery=BitVector([1, 0, 1, 0, 1, 1])) # typical use-case
DecodeResult{Nothing}(nothing, Bool[1, 0, 1, 0, 1, 1], nothing, nothing)
julia> DecodeResult(; success=true) # override success
DecodeResult{Nothing}(true, nothing, nothing, nothing)
julia> DecodeResult(; success=false, logical_commutations=BitVector([1, 0])) # override all
DecodeResult{Nothing}(false, nothing, Bool[1, 0], nothing)
julia> DecodeResult(; success=true, custom_values=[2.3, 4.1]) # custom values (numbers)
DecodeResult{Vector{Float64}}(true, nothing, nothing, [2.3, 4.1])
julia> DecodeResult(; success=true, custom_values=[[2.3], [4.1]]) # custom values (arrays)
DecodeResult{Vector{Vector{Float64}}}(true, nothing, nothing, [[2.3], [4.1]])
julia> DecodeResult(true, nothing, nothing, [2.3, 4.1]) # positional parameters
DecodeResult{Vector{Float64}}(true, nothing, nothing, [2.3, 4.1])
julia> DecodeResult(; success=nothing, recovery=nothing) # too few specified parameters
ERROR: QecsimError: at least one of 'success' or 'recovery' must be specified
Stacktrace:
[...]
PauliTools
Qecsim.PauliTools
— ModuleTools for Pauli strings and binary symplectic vectors / matrices.
Qecsim.PauliTools.bsp
— Functionbsp(
A::AbstractVecOrMat{Bool}, B::AbstractVecOrMat{Bool}
) -> Union{Bool,BitVector,BitMatrix}
Return the binary symplectic product of A
with B
, given in binary symplectic form.
The binary symplectic product $⊙$ is defined as $A ⊙ B ≡ A Λ B \bmod 2$ where $Λ = \left[\begin{smallmatrix} 0 & I \\ I & 0 \end{smallmatrix}\right]$.
Examples
julia> a = BitVector([1, 0, 0, 0]); # XI
julia> b = BitVector([0, 0, 1, 0]); # ZI
julia> bsp(a', b)
true
julia> stabilizers = BitMatrix( # 5-qubit stabilizers
[1 0 0 1 0 0 1 1 0 0 # XZZXI
0 1 0 0 1 0 0 1 1 0 # IXZZX
1 0 1 0 0 0 0 0 1 1 # XIXZZ
0 1 0 1 0 1 0 0 0 1]); # ZXIXZ
julia> error = BitVector([0, 0, 1, 1, 0, 0, 1, 0, 1, 0]); # IZXYI
julia> bsp(stabilizers, error)
4-element BitVector:
0
1
1
0
Qecsim.PauliTools.pack
— Functionpack(bsf::AbstractVector{Bool}) -> Tuple{String,Int}
Pack a binary vector into a concise representation, typically for log output. See also unpack
.
Examples
julia> a = BitVector([1, 0, 1, 0, 1, 1]); # XZY
julia> b = pack(a) # (hex_value, length)
("2b", 6)
julia> unpack(b) == a
true
Qecsim.PauliTools.to_bsf
— Functionto_bsf(
pauli::Union{AbstractString,AbstractVector{<:AbstractString}}
) -> Union{BitVector,BitMatrix}
Convert the Pauli string operator(s) to binary symplectic form.
A single Pauli string is converted to a vector. A vector of Pauli strings is converted to a matrix where each row corresponds to a Pauli.
Examples
julia> to_bsf("XIZIY")
10-element BitVector:
1
0
0
0
1
0
0
1
0
1
julia> to_bsf(["XIZIY", "IXZYI"])
2×10 BitMatrix:
1 0 0 0 1 0 0 1 0 1
0 1 0 1 0 0 0 1 1 0
Qecsim.PauliTools.to_pauli
— Functionto_pauli(bsf::AbstractVecOrMat{Bool}) -> Union{String,Vector{String}}
Convert the binary symplectic form to Pauli string operator(s).
A vector is converted to a single Pauli string. A matrix is converted row-by-row to a collection of Pauli strings.
Examples
julia> to_pauli(BitVector([1, 0, 0, 0, 1, 0, 0, 1, 0, 1]))
"XIZIY"
julia> to_pauli(BitMatrix([1 0 0 0 1 0 0 1 0 1; 0 1 0 1 0 0 0 1 1 0]))
2-element Vector{String}:
"XIZIY"
"IXZYI"
Qecsim.PauliTools.unpack
— Functionunpack(packed_bsf::Tuple{String,Int}) -> BitVector
Unpack a binary vector from a concise representation, typically from log output. See also pack
.
Examples
julia> a = ("2b", 6); # (hex_value, length)
julia> b = unpack(a) # XZY
6-element BitVector:
1
0
1
0
1
1
julia> pack(b) == a
true
Qecsim.PauliTools.weight
— Functionweight(bsf::AbstractVecOrMat{Bool}) -> Int
Return the weight of the binary symplectic form.
Examples
julia> weight(BitVector([1, 0, 0, 0, 1, 0, 0, 1, 0, 1]))
3
julia> weight(BitMatrix([1 0 0 0 1 0 0 1 0 1; 1 1 1 1 1 0 0 0 0 0]))
8
weight(pauli::Union{AbstractString,AbstractVector{<:AbstractString}}) -> Int
Return the weight of the Pauli string operator(s).
Examples
julia> weight("XIZIY")
3
julia> weight(["XIZIY", "XXXXX"])
8