Weight Reduction
Weight reduction was first introduced for CSS codes in [1], [2] and for classical codes in [3]. Here, we follow the finite-size analysis of [4]. The arguments of the functions below are aligned with the terminology introduced in that paper.
Classical Codes
Weight reduction applied to classical codes acts on parity-check matrices. To weight reduce a generator matrix instead, apply weight reduction to the dual code.
julia> C = ReedMullerCode(1, 3)
[8, 4, 4]_2 Reed-Muller code RM(1, 3)
Generator matrix: 4 × 8
1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1
0 0 1 1 0 0 1 1
0 0 0 0 1 1 1 1
julia> parity_check_matrix(C)
[1 1 1 1 1 1 1 1]
[0 1 0 1 0 1 0 1]
[0 0 1 1 0 0 1 1]
[0 0 0 0 1 1 1 1]
julia> C_wtred = weight_reduction(C)
[27, 4, 9]_2 linear code
Generator matrix: 4 × 27
1 1 1 1 0 0 0 0 0 0 0 1 1 1 0 0 0 1 1 1 0 1 1 1 0 0 0
1 1 0 0 1 1 0 0 0 0 0 1 1 0 1 0 0 0 0 0 1 0 0 0 1 0 0
0 1 0 1 0 1 0 1 1 1 1 1 1 0 0 0 1 0 1 0 1 1 1 0 0 1 0
1 1 0 0 0 0 1 1 1 1 1 1 0 0 1 1 0 0 0 1 0 0 1 0 0 0 1
julia> parity_check_matrix(C_wtred)
[0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0]
[1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0]
[0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0]
[0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]
[0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0]
[0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0]
[0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1]
[0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]
[0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
As described in [4], this function applies independent row and column permutations by default. These may be independently turned off using the optional arguments permute_rows
and permute_columns
, respectively.
julia> C_wtred = weight_reduction(C, permute_rows = false, permute_columns = false);
julia> parity_check_matrix(C_wtred)
[1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0]
[0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0]
[0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]
[0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0]
[0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0]
[0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1]
[0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]
[0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
Use the optional arguments rows = false
or columns = false
to reduce only the columns or rows, respectively. Provide a vector of row or column indices to the optional arguments row_indices
and column_indices
to only reduce specific rows or columns, respectively. If the optional arguments row_target
or column_target
are set, then all rows and columns with weights greater than these values are weight reduced. Compressed weight reduction is available by setting compressed = true
. Finally, the optional argument seed
sets Random.seed!(seed)
, which allows for reproducible permutations.
Weight reduction may also be applied to matrices directly without having to construct a code object. This may be used to reduce a generator matrix, if desired.
julia> H1 = matrix(GF(2), 2, 6, [1 1 1 1 0 0; 0 0 1 1 1 1]);
julia> H2 = H1[:, [1, 5, 3, 6, 4, 2 ]];
julia> weight_reduction(H1, permute_rows = false, permute_columns = false)
[1 0 0 0 0 0 1 0 0 0 0 0]
[0 1 0 0 0 0 1 1 0 0 0 0]
[0 0 1 0 0 0 0 1 1 0 0 0]
[0 0 0 1 0 0 0 0 1 0 0 0]
[0 0 1 0 0 0 0 0 0 1 0 0]
[0 0 0 1 0 0 0 0 0 1 1 0]
[0 0 0 0 1 0 0 0 0 0 1 1]
[0 0 0 0 0 1 0 0 0 0 0 1]
julia> weight_reduction(H2, permute_rows = false, permute_columns = false)
[1 0 0 0 0 0 1 0 0 0 0 0]
[0 0 1 0 0 0 1 1 0 0 0 0]
[0 0 0 0 1 0 0 1 1 0 0 0]
[0 0 0 0 0 1 0 0 1 0 0 0]
[0 1 0 0 0 0 0 0 0 1 0 0]
[0 0 1 0 0 0 0 0 0 1 1 0]
[0 0 0 1 0 0 0 0 0 0 1 1]
[0 0 0 0 1 0 0 0 0 0 0 1]
The easiest way to see the effect of the permutation H2
of H1
is to create code objects for the matrices. Since we have already applied the desired permutation, we will turn further permutations off. Since these codes are small, the LinearCode
constructor will automatically compute their minimum distance. (This is Example 10 of [4].)
julia> C1 = LinearCode(H1, true);
julia> weight_reduction(C1, permute_rows = false, permute_columns = false)
[12, 4, 3]_2 linear code
Generator matrix: 4 × 12
1 1 0 0 0 0 1 0 0 0 0 0
0 0 1 1 0 0 0 0 1 1 0 0
0 1 0 1 1 0 0 1 1 0 1 0
0 0 0 0 1 1 0 0 0 0 0 1
julia> C2 = LinearCode(H2, true);
julia> C2_wtred = weight_reduction(C2, permute_rows = false, permute_columns = false)
[12, 4, 4]_2 linear code
Generator matrix: 4 × 12
1 0 0 0 0 1 1 1 1 0 0 0
1 1 1 0 0 0 1 0 0 1 0 0
1 0 1 1 0 0 1 0 0 0 1 0
1 0 0 1 1 0 1 1 0 0 0 1
We can check the parameters with a function like
function check_weights(C)
w = maximum(count(!iszero, parity_check_matrix(C)[i, :]) for i in 1:nrows(parity_check_matrix(C)))
q = maximum(count(!iszero, parity_check_matrix(C)[:, i]) for i in 1:ncols(parity_check_matrix(C)))
@show (w, q)
return nothing
end
julia> check_weights(C2_wtred)
(w, q) = (3, 2)
Quantum Codes
Quantum weight reduction consists of four steps: copying, gauging, thickening and choosing heights, and coning. In addition to running the entire process on a pair of stabilizer matrices or code object, each step may be run individually.
Coning
Example 1 of [4]
julia> F = GF(2);
julia> H_X = matrix(F, 4, 6, [
1 1 1 0 0 0;
1 1 0 0 1 1;
1 0 1 1 1 0;
1 0 0 0 0 1]);
julia> H_Z = matrix(F, 1, 6, [1 0 1 0 0 1]);
julia> tilde_H_X, tilde_H_Z = copying(H_X, H_Z);
julia> tilde_H_X
[1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0]
[0 0 1 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0]
[1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1]
julia> tilde_H_Z
[1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1]
All of the examples in this section will also work using a code object.
julia> S = CSSCode(H_X, H_Z);
julia> S_copy = copying(S)
[[24, 1]]_2 CSS stabilizer code
X-stabilizer matrix: 22 × 24
chi(0) 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0
chi(0) 0 0 1 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0
chi(0) 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0
chi(0) 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
Z-stabilizer matrix: 1 × 24
chi(0) 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1
We can check the parameters with a function like
function check_weights(S)
w_X = maximum(count(!iszero, X_stabilizers(S)[i, :]) for i in 1:nrows(X_stabilizers(S)))
q_X = maximum(count(!iszero, X_stabilizers(S)[:, i]) for i in 1:ncols(X_stabilizers(S)))
w_Z = maximum(count(!iszero, Z_stabilizers(S)[i, :]) for i in 1:nrows(Z_stabilizers(S)))
q_Z = maximum(count(!iszero, Z_stabilizers(S)[:, i]) for i in 1:ncols(Z_stabilizers(S)))
@show (w_X, q_X, w_Z, q_Z)
return nothing
end
julia> check_weights(S_copy)
(w_X, q_X, w_Z, q_Z) = (4, 3, 12, 1)
Gauging
Example 2 of [4]
julia> S = Q15RM();
julia> H_X = X_stabilizers(S)[[2, 1], :];
julia> H_Z = Z_stabilizers(S)[[4, 3, 2, 1], :];
julia> tilde_H_X, tilde_H_Z = gauging(H_X, H_Z);
julia> tilde_H_X
[0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 0 0 0 0]
[1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0]
[0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0]
[0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0]
[0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0]
[0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1]
[0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 1]
julia> tilde_H_Z
[0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 1 0 0 0 0 1 0]
[0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 1 0 0 0 0 1 0 0 0]
[0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 1 0 1 0 1 1 0 0 1]
[1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 0 0 1 0 1 0 1 0]
Thickening And Choosing Heights
For thickening and choosing heights, one must specify the thickening parameter l
and heights
. This is Example 3 of [4].
julia> F = GF(2);
julia> l = 3; heights = [1, 2];
julia> H_X = matrix(F, 1, 4, [1 1 1 1]);
julia> H_Z = matrix(F, 2, 4, [1 1 0 0; 1 0 1 0]);
julia> tilde_H_X, tilde_H_Z = thickening_and_choose_heights(H_X, H_Z, l, heights);
julia> tilde_H_X
[1 0 0 1 0 0 1 0 0 1 0 0 1 0]
[0 1 0 0 1 0 0 1 0 0 1 0 1 1]
[0 0 1 0 0 1 0 0 1 0 0 1 0 1]
julia> tilde_H_Z
[1 0 0 1 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 1 0 0 0 0 0 0]
[1 1 0 0 0 0 0 0 0 0 0 0 1 0]
[0 1 1 0 0 0 0 0 0 0 0 0 0 1]
[0 0 0 1 1 0 0 0 0 0 0 0 1 0]
[0 0 0 0 1 1 0 0 0 0 0 0 0 1]
[0 0 0 0 0 0 1 1 0 0 0 0 1 0]
[0 0 0 0 0 0 0 1 1 0 0 0 0 1]
[0 0 0 0 0 0 0 0 0 1 1 0 1 0]
[0 0 0 0 0 0 0 0 0 0 1 1 0 1]
Coning
This implementation uses the Decongestion Lemma [5] to find a cycle basis (see [4]). This iteratively reduces the size of the graph, and any time the graph has no cycles of length one or two, a new edge is picked at random. Different cycle bases lead to different cellulations, which leads to different stabilizers. In this way, randomness is introduced into an any prodecure which uses coning as a subroutine. As with the classical case above, an optional seed
argument is provided to control this.
At the moment, coning is only supported for reasonable codes [2] in the case the X
stabilizers have maximum weight two overlap with the support of the Z
stabilizer being reduced. In other words, it is designed to be input the output of thickening and choosing heights and not a random code. We may extend this later, if desired.
julia> H_X = matrix(GF(2), 11, 10, [
1 1 0 0 0 0 0 0 0 0;
0 1 1 0 0 0 0 0 0 0;
0 0 1 1 0 0 0 0 0 0;
0 0 0 1 1 0 0 0 0 0;
0 0 0 0 1 1 0 0 0 0;
0 0 0 0 0 1 1 0 0 0;
1 0 0 0 0 0 1 0 0 0;
0 0 0 1 0 0 0 1 0 0;
0 0 0 0 0 0 0 1 1 0;
0 0 0 0 0 0 0 0 1 1;
0 0 0 0 0 0 0 1 0 1
]);
julia> H_Z = matrix(GF(2), 1, 10, [1 1 1 1 1 1 1 1 1 1 ]);
To cone, we must specify which $Z$ stabilizers to reduce.
julia> tilde_H_X, tilde_H_Z = coning(H_X, H_Z, [1]);
julia> tilde_H_X
[0 0 1 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0]
[1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0]
[1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0]
[0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0]
[0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0]
[0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0]
[0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0]
[0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0]
[0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 1]
[0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 1]
julia> tilde_H_Z
[1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]
[1 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0]
[0 1 1 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0]
[0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0]
[0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0]
[0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0]
[0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0]
[0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 0 0 1 0 0]
[0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1 0]
[0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1]
Improved Copying
The copying variants introduced in [4] are available via an optional argument to copying
.
julia> S = Q15RM();
julia> copying(S)
[[60, 1]]_2 CSS stabilizer code
julia> copying(S, method = :reduced)
[[32, 1]]_2 CSS stabilizer code
julia> copying(S, method = :target, target_q_X = 3)
[[16, 1]]_2 CSS stabilizer code
X-stabilizer matrix: 5 × 16
chi(0) 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
chi(0) 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0
chi(0) 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 1
chi(0) 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 1
chi(0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
Z-stabilizer matrix: 10 × 16
chi(0) 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1
chi(0) 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 1
chi(0) 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 1
chi(0) 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
chi(0) 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 1
chi(0) 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 1
chi(0) 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 1
chi(0) 0 0 0 0 0 0 0 0 0 1 1 0 0 1 1 1
chi(0) 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1
chi(0) 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 1
All Together
The functions weight_reduction
and quantum_weight_reduction
provide a wrapper for automatically running the entire quantum weight reduction process in order. The arguments provided to each step individually are be passed into these functions with the exception that copying's optional argument method
is now copying_type
and target_q_X
is now copying_target
. The optional parameter target_q_X
now triggers a second round of thickening and choosing heights in coning. The first, manadatory round of thickening and choosing heights is controlled via l1
and heights
. The second, optional round is controlled via l2
and target_q_X
, where the second set of heights are determined by the target.
Note the randomness of the output induced by coning.
julia> S = Q15RM();
julia> l = 3; heights = [2, 1, 2, 1, 2, 3, 1, 3, 3, 1];
julia> quantum_weight_reduction(S, l, heights)
[[722, 1]]_2 CSS stabilizer code
julia> quantum_weight_reduction(S, l, heights)
[[721, 1]]_2 CSS stabilizer code
julia> quantum_weight_reduction(S, l, heights, copying_type = :reduced)
[[510, 1]]_2 CSS stabilizer code
julia> quantum_weight_reduction(S, l, heights, copying_type = :target, copying_target = 3)
[[315, 1]]_2 CSS stabilizer code
Copying And Gauging As Coning
It was shown in [4] that copying and gauging can be thought of as mapping cones.
julia> F = GF(2);
julia> H_X = matrix(F, 4, 6, [
1 1 1 0 0 0;
1 1 0 0 1 1;
1 0 1 1 1 0;
1 0 0 0 0 1]);
julia> H_Z = matrix(F, 1, 6, [1 0 1 0 0 1]);
julia> tilde_H_X, tilde_H_Z = copying(H_X, H_Z);
julia> tilde_H_X_cone, tilde_H_Z_cone = copying_as_coning(H_X, H_Z);
julia> tilde_H_X == tilde_H_X_cone
true
julia> tilde_H_Z == tilde_H_Z_cone
true
julia> tilde_H_X, tilde_H_Z = copying(H_X, H_Z, method = :reduced);
julia> tilde_H_X_cone, tilde_H_Z_cone = copying_as_coning(H_X, H_Z, method = :reduced);
julia> tilde_H_X == tilde_H_X_cone
true
julia> tilde_H_Z == tilde_H_Z_cone
true
julia> tilde_H_X, tilde_H_Z = copying(H_X, H_Z, method = :target, target_q_X = 3);
julia> tilde_H_X_cone, tilde_H_Z_cone = copying_as_coning(H_X, H_Z, method = :target, target_q_X = 3);
julia> tilde_H_X == tilde_H_X_cone
true
julia> tilde_H_Z == tilde_H_Z_cone
true
While perhaps more elegant, solving a solution of equations is more time consuming.
julia> using BenchmarkTools
julia> @btime copying($H_X, $H_Z);
6.675 μs (223 allocations: 17.71 KiB)
julia> @btime copying_as_coning($H_X, $H_Z);
83.250 μs (1131 allocations: 132.41 KiB)
The results are similar for gauging, although now the mapping cone is slightly faster (on this example).
julia> S = Q15RM();
julia> H_X = X_stabilizers(S)[[2, 1], :];
julia> H_Z = Z_stabilizers(S)[[4, 3, 2, 1], :];
julia> tilde_H_X, tilde_H_Z = gauging(H_X, H_Z);
julia> tilde_H_X_cone, tilde_H_Z_cone = gauging_as_coning(H_X, H_Z);
julia> tilde_H_X == tilde_H_X_cone
true
julia> tilde_H_Z == tilde_H_Z_cone
true
julia> @btime gauging($H_X, $H_Z);
45.167 μs (1582 allocations: 128.55 KiB)
julia> @btime gauging_as_coning($H_X, $H_Z);
32.084 μs (722 allocations: 66.96 KiB)
Classical Versus Quantum Weight Reduction
Consider the code from the first row of Table 1 in [4].
julia> C = best_known_linear_code(6, 3)
[6, 3, 3]_2 linear code
Generator matrix: 3 × 6
1 0 1 0 1 0
0 1 1 0 0 1
0 0 1 1 1 1
julia> S = HypergraphProductCode(C)
[[45, 9, 3]]_2 subsystem code
julia> quantum_weight_reduction(S, num_Z_stabs(S), collect(1:l), seed = 5849772946347113199, copying_type = :target, copying_target = 3)
[[2892, 9]]_2 CSS stabilizer code
Weight reducing the classical codes before passing to the hypergraph product gives.
julia> C_wtred = weight_reduction(C)
[9, 3, 4]_2 linear code
Generator matrix: 3 × 9
1 0 1 0 1 0 1 0 0
0 1 1 0 0 1 0 1 0
0 1 0 1 1 0 0 0 1
julia> HypergraphProductCode(C_wtred)
[[117, 9, 4]]_2 subsystem code
julia> C_wtred_com = weight_reduction(C, compressed = true)
[7, 3, 3]_2 linear code
Generator matrix: 3 × 7
1 1 1 1 0 0 0
0 1 1 0 0 1 0
1 0 1 0 1 0 1
julia> HypergraphProductCode(C_wtred_com)
[[65, 9, 3]]_2 subsystem code
Exploring The Cycle Structure
Classical weight reduction should not change the cycle structure of the code. We can test this. Recall that a parity-check matrix defines a Tanner graph, and the girth, $g$, of the graph is defined to the length of the shortest cycle. Short cycles are defined to be cycles with length up to $2g - 2$. The total number of short cycles are not preserved by weight reduction since the girth may not increase as much as the length of a cycle, pushing it beyond the $2g - 2$ limit. Elementary cycles are cycles which do not pass through the same vertex twice. The total number of elementary cycles is invariant under classical weight reduction.
We will supress the plots output from the functions below.
julia> C = best_known_linear_code(6, 3)
[6, 3, 3]_2 linear code
Generator matrix: 3 × 6
1 0 1 0 1 0
0 1 1 0 0 1
0 0 1 1 1 1
julia> L = LDPCCode(C)
[6, 3, 3]_2 irregular 4-limited LDPC code with density 0.5555555555555556.
Variable degree polynomial:
3//10*x^2 + 2//5*x + 3//10
Check degree polynomial:
2//5*x^3 + 3//5*x^2
Parity-check matrix: 3 × 6
1 1 1 1 0 0
0 1 1 0 1 0
1 0 1 0 0 1
julia> girth(L)
4
julia> count_short_cycles(L)
(Plot{Plots.GRBackend() n=1}, Dict(4 => 4, 6 => 2))
julia> count_elementary_cycles(L)
(Plot{Plots.GRBackend() n=1}, Dict(4 => 4, 6 => 2))
julia> C_wtred = weight_reduction(C, permute_rows = false, permute_columns = false)
[9, 3, 4]_2 linear code
Generator matrix: 3 × 9
1 1 0 0 1 1 1 0 0
0 1 1 0 0 1 0 1 0
0 0 1 1 1 1 0 0 1
julia> L_wtred = LDPCCode(C_wtred)
[9, 3, 4]_2 irregular 3-limited LDPC code with density 0.2962962962962963.
Variable degree polynomial:
3//16*x^2 + 5//8*x + 3//16
Check degree polynomial:
3//4*x^2 + 1//4*x
Parity-check matrix: 6 × 9
1 0 0 0 0 0 1 0 0
0 1 0 0 0 0 1 1 0
0 0 1 0 0 0 0 1 1
0 0 0 1 0 0 0 0 1
0 1 1 0 1 0 0 0 0
1 0 1 0 0 1 0 0 0
julia> girth(L_wtred)
6
julia> count_short_cycles(L_wtred)
(Plot{Plots.GRBackend() n=1}, Dict(6 => 2, 10 => 0, 8 => 4))
julia> count_elementary_cycles(L_wtred)
(Plot{Plots.GRBackend() n=1}, Dict(6 => 2, 8 => 4))
We see that the girth increased, as well as the cycle lengths, but the total number of elementary cycles is still six. The function count_short_cycles
preallocates a dictionary with entries from $g$ to $2g - 2$, which in this case in ten. Since there are no length ten cycles, this entry still exists but with value zero.
The hypergraph product does not preserve cycle structure, and the maximum girth of the Tanner graph is now capped at eight. Ignoring the $X$-$Z$ correlations, let's consider the $X$ stabilizers of the following codes.
julia> S = HypergraphProductCode(C)
[[45, 9, 3]]_2 subsystem code
julia> S_wtred = HypergraphProductCode(C_wtred)
[[117, 9, 4]]_2 subsystem code
julia> L_X = LDPCCode(X_stabilizers(S))
[45, 27]_2 irregular 7-limited LDPC code with density 0.1111111111111111.
Variable degree polynomial:
2//15*x^3 + 2//5*x^2 + 4//15*x + 1//5
Check degree polynomial:
7//90*x^6 + 4//15*x^5 + 7//18*x^4 + 4//15*x^3
julia> girth(L_X)
4
julia> L_X_wtred = LDPCCode(X_stabilizers(S_wtred))
[117, 63]_2 irregular 6-limited LDPC code with density 0.03798670465337132.
Variable degree polynomial:
33//80*x^2 + 19//40*x + 9//80
Check degree polynomial:
1//10*x^5 + 11//24*x^4 + 11//30*x^3 + 3//40*x^2
julia> girth(L_X_wtred)
6
julia> _, D = count_elementary_cycles(L_X)
(Plot{Plots.GRBackend() n=1}, Dict(4 => 36, 6 => 92, 10 => 176, 12 => 104, 8 => 280, 14 => 68))
julia> _, D_wtred = count_elementary_cycles(L_X_wtred); print(ans[2])
Dict(16 => 356, 20 => 108, 12 => 544, 24 => 18, 28 => 2, 8 => 352, 22 => 196, 6 => 30, 14 => 1326, 10 => 960, 18 => 748, 26 => 22)
julia> sum(values(D))
756
julia> sum(values(D_wtred))
4662
Even though the weight-reduced code produced more cycles after the hypergraph product, the number of shorter cycles has decreased.
julia> count_short_cycles(L_X)
(Plot{Plots.GRBackend() n=1}, Dict(4 => 36, 6 => 92))
julia> count_short_cycles(L_X_wtred)
(Plot{Plots.GRBackend() n=1}, Dict(6 => 30, 10 => 960, 8 => 352))
The cycle structure is not preserved by quantum weight reduction [4].
julia> S_qwtred = weight_reduction(S, 4, rand(1:4, nrows(S.Z_stabs)))
[[2170, 9]]_2 CSS stabilizer code
julia> L_X_qwtred = LDPCCode(X_stabilizers(S_qwtred))
[2170, 1197]_2 irregular 9-limited LDPC code with density 0.002024658584234584.
Variable degree polynomial:
7//1444*x^6 + 7//722*x^5 + 15//1444*x^4 + 34//1083*x^3 + 65//361*x^2 + 1451//2166*x + 203//2166
Check degree polynomial:
9//722*x^8 + 2//57*x^7 + 259//4332*x^6 + 85//722*x^5 + 445//2166*x^4 + 484//1083*x^3 + 177//1444*x^2
julia> girth(L_X_qwtred)
4
julia> count_short_cycles(L_X_qwtred)
(Plot{Plots.GRBackend() n=1}, Dict(4 => 170, 6 => 300))
julia> _, D_qwtred = count_elementary_cycles(L_X_qwtred); print(D_qwtred)
Dict(78 => 28, 56 => 2894, 16 => 13428, 20 => 24654, 58 => 15616, 52 => 7728, 60 => 1062, 12 => 6768, 24 => 38458, 28 => 48938, 8 => 2990, 30 => 140804, 72 => 16, 22 => 86594, 32 => 52376, 6 => 300, 36 => 49460, 44 => 29098, 68 => 88, 14 => 27266, 74 => 260, 64 => 400, 46 => 94434, 66 => 3418, 76 => 4, 40 => 41326, 48 => 16998, 34 => 147230, 50 => 60238, 4 => 170, 54 => 32394, 70 => 1216, 10 => 8890, 18 => 53496, 26 => 118236, 38 => 142138, 42 => 125086, 62 => 7388)
Lifted Products
Classical weight reduction also applies to other types of inputs, although with the current function, the row and column indices must be specified explicitly either as a vector or a range.
julia> F = GF(2);
julia> S, x = PolynomialRing(F, "x");
julia> l = 63;
julia> R = ResidueRing(S, x^l - 1);
julia> A = matrix(R, 7, 7,
[x^27, 0, 0, 1, x^18, x^27, 1,
1, x^27, 0, 0, 1, x^18, x^27,
x^27, 1, x^27, 0, 0, 1, x^18,
x^18, x^27, 1, x^27, 0, 0, 1,
1, x^18, x^27, 1, x^27, 0, 0,
0, 1, x^18, x^27, 1, x^27, 0,
0, 0, 1, x^18, x^27, 1, x^27])
[x^27 0 0 1 x^18 x^27 1]
[ 1 x^27 0 0 1 x^18 x^27]
[x^27 1 x^27 0 0 1 x^18]
[x^18 x^27 1 x^27 0 0 1]
[ 1 x^18 x^27 1 x^27 0 0]
[ 0 1 x^18 x^27 1 x^27 0]
[ 0 0 1 x^18 x^27 1 x^27]
julia> b = R(1 + x + x^6)
x^6 + x + 1
julia> LiftedProductCode(A, b)
┌ Warning: Commutativity of A and b required but not yet enforced.
└ @ CodingTheory ~/Documents/GitHub/CodingTheory/src/Quantum/product_codes.jl:354
[[882, 48]]_2 CSS stabilizer code
julia> A_wtred = weight_reduction(A, row_indices = 1:4, column_indices = 1:4, permute_rows = false, permute_columns = false);
julia> LiftedProductCode(A_wtred, b)
┌ Warning: Commutativity of A and b required but not yet enforced.
└ @ CodingTheory ~/Documents/GitHub/CodingTheory/src/Quantum/product_codes.jl:354
[[4914, 48]]_2 CSS stabilizer code