Code functions

QuantumCode functions for verifying, evaluating, transforming and contracting.

Basic

Functions for code properties or verification.

TensorNetworkCodes.verifyFunction
verify(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.
source

Evaluation

Functions to evaluate operators or syndromes.

TensorNetworkCodes.find_distance_logicalsFunction
find_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])
source
TensorNetworkCodes.find_pure_errorFunction
find_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
source
TensorNetworkCodes.find_pure_errorsFunction
find_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
source
TensorNetworkCodes.find_syndromeFunction
find_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
source

Transformation

Functions to gauge, permute and purify codes.

TensorNetworkCodes.gaugeFunction
gauge(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}[]
source
TensorNetworkCodes.permuteFunction
permute(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]
source
TensorNetworkCodes.purifyFunction
purify(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)
source

Contraction

Functions to contract codes using the primitives of combining codes and fusing physical qubits.

TensorNetworkCodes.combineFunction
combine(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
source
TensorNetworkCodes.contractFunction
contract(
    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.

See also: combine, fusion.

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
source
TensorNetworkCodes.contract_by_coordsFunction
contract_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.

See also: combine, fusion.

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
source
TensorNetworkCodes.fusionFunction
fusion(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)
source