Code functions
QuantumCode
functions for verifying, evaluating, transforming and contracting.
Basic
Functions for code properties or verification.
TensorNetworkCodes.num_qubits
— Functionnum_qubits(code::QuantumCode) -> Int
Return the number of physical qubits of the code.
TensorNetworkCodes.verify
— Functionverify(code::QuantumCode; log_warn=true) -> Bool
Return true if the code satisfied the properties of a valid code, or false otherwise. If the code is not valid and log_warn
is true then a warning is logged with the specific reason.
The following checks are performed:
- Number of stabilizers, pure errors and logicals are consistent.
- Stabilizers are independent and mutually commute.
- Pure errors anticommute with corresponding stabilizers and commute with other stabilizers.
- Logicals commute with stabilizers.
The following checks are not yet performed:
- Logical commutation relations.
Evaluation
Functions to evaluate operators or syndromes.
TensorNetworkCodes.find_distance_logicals
— Functionfind_distance_logicals(code::Quantum_code; max_distance=5) -> Int, Vector{Vector{Int}}
Return the distance of the code and all minimum-weight logical operators.
This method works by brute force. It searches for operators of increasing weight so it works well for low-distance codes but will be slow for high-distance codes. If during the search max_distance
is exceeded then an ErrorException
is thrown.
Examples
julia> d, ls = find_distance_logicals(five_qubit_code());
julia> d, length(ls), ls[1] # distance, number and example of minimum-weight logicals
(3, 30, [1, 2, 1, 0, 0])
TensorNetworkCodes.find_pure_error
— Functionfind_pure_error(code::QuantumCode, syndrome::AbstractVector{Int}) -> AbstractVector{Int}
Return a pure error which yields the given syndrome with the given code.
The pure error is formed from a product of code.pure_errors
and is not unique nor necessarily the lowest-weight error corresponding to the syndrome.
See also find_syndrome
.
Examples
julia> code = five_qubit_code();
julia> syndrome = find_syndrome(code, [0, 1, 3, 0, 1]) # error = IXZIX
4-element Vector{Int64}:
1
0
0
1
julia> pure_error = find_pure_error(code, syndrome)
5-element Vector{Int64}:
1
1
0
0
0
julia> find_syndrome(code, pure_error) == syndrome
true
TensorNetworkCodes.find_pure_errors
— Functionfind_pure_errors(stabilizers::AbstractVector{<:AbstractVector{Int}})
-> Vector{Vector{Int}}
Return pure errors corresponding to a list of stabilizers, such that each pure error anticommutes with precisely one stabilizer and the order of pure errors respects that of the stabilizers.
This function is efficient but does not give lowest weight pure errors (you cannot have both of these properties). An ErrorException
is thrown if the function cannot succeed; for example if the stabilizers are not linearly independent.
Examples
julia> stabilizers = [[1, 3, 3, 1, 0], [0, 1, 3, 3, 1], [1, 0, 1, 3, 3], [3, 1, 0, 1, 3]];
julia> pure_errors = find_pure_errors(stabilizers)
4-element Vector{Vector{Int64}}:
[0, 1, 0, 0, 0]
[1, 3, 0, 0, 0]
[3, 1, 0, 0, 0]
[1, 0, 0, 0, 0]
julia> [pauli_commutation(s, p) for s in stabilizers, p in pure_errors] # commutations
4×4 Matrix{Int64}:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
TensorNetworkCodes.find_syndrome
— Functionfind_syndrome(code::QuantumCode, error::AbstractVector{Int}) -> AbstractVector{Int}
Return the syndrome yielded by the given error with the given code.
The syndrome is a list of 1 and 0 of the same length as code.stabilizers
, where 1 indicates the error anticommutes with the corresponding stabilizer.
See also find_pure_error
.
Examples
julia> syndrome = find_syndrome(five_qubit_code(), [0, 1, 3, 0, 1]) # error = IXZIX
4-element Vector{Int64}:
1
0
0
1
Transformation
Functions to gauge, permute and purify codes.
TensorNetworkCodes.gauge
— Functiongauge(code::SimpleCode, logical_qubit::Int, logical_pauli::Int) -> SimpleCode
gauge(code::TensorNetworkCode, logical_qubit::Int, logical_pauli::Int)
-> TensorNetworkCode
Given a code with $k$ logicals on $n$ physical qubits, return a new code with $k - 1$ logicals on $n$ physical qubits by adding a logical operator as a stabilizer, where logical_qubit
indexes which logical qubit is gauged and logical_pauli
indicates which logical Pauli is added to the stabilizers.
A ErrorException
is thrown if logical_qubit
indexes a non-existant logical qubit, or if logical_pauli
is not in 1:3
(logical identity does not fix a gauge).
Examples
julia> code = five_qubit_code();
julia> code.stabilizers
4-element Vector{Vector{Int64}}:
[1, 3, 3, 1, 0]
[0, 1, 3, 3, 1]
[1, 0, 1, 3, 3]
[3, 1, 0, 1, 3]
julia> code.logicals
2-element Vector{Vector{Int64}}:
[1, 1, 1, 1, 1]
[3, 3, 3, 3, 3]
julia> new_code = gauge(code, 1, 3); # gauge logical qubit 1 using logical Z
julia> new_code.stabilizers
5-element Vector{Vector{Int64}}:
[1, 3, 3, 1, 0]
[0, 1, 3, 3, 1]
[1, 0, 1, 3, 3]
[3, 1, 0, 1, 3]
[3, 3, 3, 3, 3]
julia> new_code.logicals
Vector{Int64}[]
TensorNetworkCodes.permute
— Functionpermute(code::SimpleCode, permutation) -> SimpleCode
Return a new simple code with the physical qubits permuted relative to the given code, according to the permutation.
The permutation
is expected in the format used for Base.permute!
and it is applied to each stabilizer, logical and pure error of the code. No checking is done to verify that permuation
is a valid.
Examples
julia> code = five_qubit_code();
julia> code.name
"Five qubit code"
julia> code.stabilizers
4-element Vector{Vector{Int64}}:
[1, 3, 3, 1, 0]
[0, 1, 3, 3, 1]
[1, 0, 1, 3, 3]
[3, 1, 0, 1, 3]
julia> new_code = permute(code, [2, 1, 3, 4, 5]);
julia> new_code.name
"Five qubit code [2, 1, 3, 4, 5]"
julia> new_code.stabilizers
4-element Vector{Vector{Int64}}:
[3, 1, 3, 1, 0]
[1, 0, 3, 3, 1]
[0, 1, 1, 3, 3]
[1, 3, 0, 1, 3]
TensorNetworkCodes.purify
— Functionpurify(code::SimpleCode) -> SimpleCode
Given a simple code with $k$ logicals on $n$ physical qubits, return a new simple code with $0$ logicals on $n + k$ physical qubits.
Examples
julia> code = purify(five_qubit_code());
julia> num_qubits(code), length(code.logicals)
(6, 0)
Contraction
Functions to contract codes using the primitives of combining codes and fusing physical qubits.
TensorNetworkCodes.combine
— Functioncombine(code1::SimpleCode, code2::SimpleCode) -> SimpleCode
combine(code1::TensorNetworkCode, code2::TensorNetworkCode) -> TensorNetworkCode
Return a new code that is the tensor product of the given codes. Physically equivalent to preparing two codes on different sets of physical qubits.
Examples
julia> code = combine(five_qubit_code(), steane_code()); # combine 5 and 7 qubit codes
julia> num_qubits(code)
12
TensorNetworkCodes.contract
— Functioncontract(
code1::TensorNetworkCode,
code2::TensorNetworkCode,
qubit_pair::AbstractVector{Int}
) -> TensorNetworkCode
contract(
code1::TensorNetworkCode,
code2::TensorNetworkCode,
qubit_pairs
) -> TensorNetworkCode
Return a new code that results from combining the codes and fusing physical qubits identified by the qubit pairs. The first and second elements of a qubit pair refer to qubit labels from the first and second codes, respectively.
This is equivalent to combine
followed by fusion
, with the qubit pair labels referring to the code qubit labels before combining. The version that takes qubit_pairs
takes iterables of AbstractVector{Int}
. An ErrorException
is thrown if the fusion is not possible.
Examples
julia> code1 = TensorNetworkCode(five_qubit_code());
julia> code2 = TensorNetworkCode(steane_code());
julia> contracted_code = contract(code1, code2, [[1, 2], [2, 7]]);
julia> num_qubits(contracted_code), length(contracted_code.logicals) ÷ 2
(8, 2)
julia> fusion(combine(code1, code2), [[1, 7], [2, 12]]); # equivalent code
TensorNetworkCodes.contract_by_coords
— Functioncontract_by_coords(code1::TensorNetworkCode,code2::TensorNetworkCode)
-> TensorNetworkCode
Return a new code that results from combining the codes and fusing physical qubits with coincident coordinates.
This is equivalent to combine
followed by fusion
, with the fusion qubit pairs being those with coincident coordinates. An ErrorException
is thrown if the fusion is not possible.
Examples
julia> code1 = TensorNetworkCode(five_qubit_code());
julia> code2 = TensorNetworkCode(steane_code());
julia> set_coords!(code1, 1, [5, 5]); set_coords!(code2, 2, [5, 5]); # qubits 1 & 2 coincide
julia> set_coords!(code1, 2, [6, 6]); set_coords!(code2, 7, [6, 6]); # qubits 2 & 7 coincide
julia> contracted_code = contract_by_coords(code1, code2);
julia> num_qubits(contracted_code), length(contracted_code.logicals) ÷ 2
(8, 2)
julia> contract(code1, code2, [[1, 2], [2, 7]]); # equivalent code
TensorNetworkCodes.fusion
— Functionfusion(code::SimpleCode, qubit_pair::AbstractVector{Int}) -> SimpleCode
fusion(code::SimpleCode, qubit_pairs) -> SimpleCode
fusion(code::TensorNetworkCode, qubit_pair::AbstractVector{Int}) -> TensorNetworkCode
fusion(code::TensorNetworkCode, qubit_pairs) -> TensorNetworkCode
Return a new code that results from fusing the physical qubits with labels given by each qubit pair. The new code has two fewer physical qubits, for each qubit pair, but the same number of logical qubits. Physically equivalent to updating stabilizers, logicals and pure errors after measuring $XX$ and $ZZ$ on each pair of qubits.
The versions that take qubit_pairs
take iterables of AbstractVector{Int}
. An ErrorException
is thrown if the fusion is not possible, i.e., if the logical degrees of freedom would not be preserved by the measurements.
Examples
julia> code = steane_code();
julia> num_qubits(code), length(code.logicals) ÷ 2
(7, 1)
julia> fused_code = fusion(code, [1, 2]);
julia> num_qubits(fused_code), length(fused_code.logicals) ÷ 2
(5, 1)