├── tndecoder3d.jl ├── scripts ├── precompress_circuit_tn.jl ├── README.md ├── sweep_bposd.py ├── sweep_circ_matching.jl ├── sweep_matching.jl ├── sweep.jl ├── sweep_circ.jl ├── sweep_depol_bposd.jl └── sweep_depol.jl ├── README.md ├── surface_code.jl ├── tn_generation.jl ├── contract_depol.jl ├── tools.jl ├── LICENSE ├── contraction_network.jl ├── contract_circ.jl └── contract_bitflip.jl /tndecoder3d.jl: -------------------------------------------------------------------------------- 1 | module TNDecoder3D 2 | 3 | using ITensors 4 | using PyCall 5 | @static if ITensors.version() ≥ v"0.7.0" 6 | using ITensorMPS 7 | end 8 | 9 | include("contraction_network.jl") 10 | include("tn_generation.jl") 11 | include("surface_code.jl") 12 | include("contract_bitflip.jl") 13 | include("contract_circ.jl") 14 | include("contract_depol.jl") 15 | include("tools.jl") 16 | 17 | export unrotated_3D_surface_code, find_representatives, permute_TN, contract, get_res, gen_mle_table, mle_table_lookup, gen_tn_sc3d_phenomenological, gen_tn_sc3d_dual, gen_tn_sc3d_circ, dem_from_stim, open_indices, contract_cubictn, gauge_BP!, gen_tn_depol, gen_tn_depol_gauge, gen_tn_general 18 | 19 | end # module TNDecoder3D 20 | -------------------------------------------------------------------------------- /scripts/precompress_circuit_tn.jl: -------------------------------------------------------------------------------- 1 | include("../tndecoder3d.jl") 2 | using .TNDecoder3D 3 | using PyCall 4 | using JLD2 5 | @pyimport stim 6 | 7 | function gen_circ_tn(d::Int, p::Float64, bd::Int) 8 | circuit = stim.Circuit.generated("surface_code:rotated_memory_x", distance=d, rounds=d, after_clifford_depolarization=p, before_measure_flip_probability=p, after_reset_flip_probability=p) 9 | model = circuit.detector_error_model(decompose_errors=false) 10 | dem = dem_from_stim(model) 11 | 12 | tn3d,expval,open_index = gen_tn_sc3d_circ(dem, bd; do_gauging=false, canonicalness_target=1e-4) 13 | 14 | save_object("/scratch/cpivetea/circtn"*string(d)*".jld2", (tn3d,expval,open_index)) 15 | 16 | end 17 | 18 | 19 | gen_circ_tn(3, 0.01, 16) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository contains source code used to generate the data for figure 1 of the paper _Tensor Network Decoding Beyond 2D_ by Christophe Piveteau, Christopher T. Chubb and Joseph M. Renes ([https://arxiv.org/abs/2310.10722](https://arxiv.org/abs/2310.10722)). 2 | 3 | The code is encapsulated in a Julia package which can be loaded by including the file `tndecoder3d.jl`. 4 | You will need a working Julia environment (version 1.8 was used in the paper) with the packages `ITensors` and `PyCall` installed. 5 | If you're using a newer version of ITensors (>=0.7), you will also require the `ITensorMPS` package. 6 | Following Python packages are used through PyCall and must be pre-installed: 7 | 8 | * stim 9 | * pymatching 10 | * ldpc 11 | * panqec 12 | 13 | To see code examples demonstrating how to use the package (specifically for the point sector, loop sector and depolarizing noise on the unrotated 3D surface code as well as circuit-level noise on the rotated 3D surface code), see the scripts in the directory `scripts`. 14 | 15 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | The scripts in this directory evaluate the performance of various decoders by running them on randomly generated error patterns: 2 | 3 | * `sweep.jl`: 3D TN decoder on point or loop sector (specifiec by the `--sector` argument) of 3D surface code 4 | * `sweep_matching.jl`: Matching decoder on point sector of 3D surface code 5 | * `sweep_bposd.py`: BP+OSD on loop sector of 3D surface code 6 | * `sweep_depol.jl`: 3D TN decoder on 3D surface code with depolarizing noise 7 | * `sweep_depol_bposd.jl`: BP+OSD decoder on 3D surface code with depolarizing noise 8 | * `sweep_circ.jl`: 3D TN decoder on rotated surface code with circuit-level noise 9 | * `sweep_circ_matching.jl`: Matching decoder decoder on rotated surface code with circuit-level noise 10 | 11 | The exact parameters accepted by the scripts vary slightly on the task performed. 12 | 13 | 14 | To run the circuit-level TN decoder in `sweep_circ.jl`, you need to first generate the pre-compressed tensor network which is stored on the disk. 15 | You can do this using the function in `precompress_circuit_tn.jl`. 16 | Check out the two files to see where the pre-compressed network is stored, you will most likely have to change the file paths. 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /scripts/sweep_bposd.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from ldpc import bposd_decoder 3 | from bposd.hgp import hgp 4 | import panqec.codes 5 | import argparse 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("-d", help="distance", type=int) 9 | parser.add_argument("-p", help="error rate times 10000", type=int) 10 | parser.add_argument("-o", help="output file to write to", type=str) 11 | args = parser.parse_args() 12 | 13 | d = args.d 14 | p = args.p / 10000. 15 | c = panqec.codes.surface_3d.Planar3DCode(d,d,d) 16 | H = c.Hx 17 | L = c.logicals_x[0][0:c.n] 18 | 19 | if d == 3: 20 | order = 19 21 | else: 22 | order = 60 23 | bpd=bposd_decoder( 24 | H, 25 | error_rate=p, 26 | max_iter=c.n, 27 | bp_method="ms", 28 | ms_scaling_factor=0, #min sum scaling factor. If set to zero the variable scaling factor method is used 29 | osd_method="osd_cs", #the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" 30 | osd_order=order #the osd search depth 31 | ) 32 | 33 | 34 | succ = 0 35 | fail = 0 36 | for _ in range(1000000): 37 | error = (np.random.uniform(size=(c.n)) < p).astype(int) 38 | syndrome=H@error %2 39 | bpd.decode(syndrome) 40 | 41 | #Decoding is successful if the residual error commutes with the logical operators 42 | residual_error=(bpd.osdw_decoding+error) %2 43 | a=((L@residual_error)%2).any() 44 | if a: fail += 1 45 | else: succ += 1 46 | if _ % 100 == 0: 47 | print(_, fail / (fail+succ)) 48 | # write result to disk 49 | if args.o is not None: 50 | with open(args.o, 'w') as fd: 51 | fd.write("{},{}\n".format(succ,fail)) 52 | -------------------------------------------------------------------------------- /scripts/sweep_circ_matching.jl: -------------------------------------------------------------------------------- 1 | using JLD2 2 | using MKL 3 | using ArgParse 4 | using JSON 5 | include("../tndecoder3d.jl") 6 | using .TNDecoder3D 7 | using PyCall 8 | using ITensors 9 | @pyimport stim 10 | @pyimport pymatching 11 | 12 | s = ArgParseSettings() 13 | @add_arg_table s begin 14 | "--distance", "-d" 15 | arg_type = Int 16 | help = "code distance" 17 | nargs = '+' 18 | required = true 19 | "--error_rate", "-p" 20 | arg_type = Int 21 | help = "physical error rate in unis of 1e-4" 22 | nargs = '+' 23 | required = true 24 | "--out_file", "-o" 25 | arg_type = String 26 | help = "file to which output data is written to" 27 | required = true 28 | "--output_write_interval" 29 | arg_type = Int 30 | help = "after how many samples should the output file be updated" 31 | default = 20 32 | "--num_samples", "-n" 33 | arg_type = Int 34 | help = "number of Monte Carlo samples" 35 | default = 1_000_000 36 | end 37 | args = parse_args(s) 38 | 39 | 40 | data = Dict{Tuple{Int,Int}, Tuple{Int,Int}}() # (distance,err_rate) -> (num_succ,num_fail) 41 | for d in args["distance"] for p_int in args["error_rate"] 42 | data[d,p_int] = (0,0) 43 | end end 44 | 45 | for d in args["distance"] 46 | for p_int in args["error_rate"] 47 | p = p_int / 10000. 48 | circuit = stim.Circuit.generated("surface_code:rotated_memory_x", distance=d, rounds=d, after_clifford_depolarization=p, before_measure_flip_probability=p, after_reset_flip_probability=p) 49 | true_model = circuit.detector_error_model(decompose_errors=false) 50 | approximate_model = circuit.detector_error_model(decompose_errors=true) 51 | sampler = true_model.compile_sampler() 52 | matching = pymatching.Matching.from_detector_error_model(approximate_model) 53 | 54 | for i = 1:args["num_samples"] 55 | samples, logicals, _ = sampler.sample(1) 56 | detectors = convert(Vector{Int8}, samples[1,:]) 57 | logical_err = convert(Int8, logicals[1]) 58 | 59 | predicted_observables = matching.decode(detectors) 60 | 61 | if logical_err == predicted_observables[1] 62 | data[d,p_int] = data[d,p_int] .+ (1,0) 63 | else 64 | data[d,p_int] = data[d,p_int] .+ (0,1) 65 | end 66 | 67 | if i % args["output_write_interval"] == 0 68 | json_str = JSON.json(Dict( 69 | "args" => args, 70 | "data" => data 71 | )) 72 | open(args["out_file"], "w") do f 73 | write(f, json_str) 74 | end 75 | end 76 | 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /scripts/sweep_matching.jl: -------------------------------------------------------------------------------- 1 | using MKL 2 | using ArgParse 3 | using JSON 4 | using PyCall 5 | @pyimport pymatching 6 | include("../tndecoder3d.jl") 7 | using .TNDecoder3D 8 | 9 | s = ArgParseSettings() 10 | @add_arg_table s begin 11 | "--distance", "-d" 12 | arg_type = Int 13 | help = "code distance" 14 | nargs = '+' 15 | required = true 16 | "--error_rate", "-p" 17 | arg_type = Int 18 | help = "physical error rate in unis of 1e-4" 19 | nargs = '+' 20 | required = true 21 | "--out_file", "-o" 22 | arg_type = String 23 | help = "file to which output data is written to" 24 | required = true 25 | "--sector" 26 | arg_type = String 27 | help = "sector to consider, may be either 'X' or 'Z'. These correspond to the 23% and 3% sector respectively." 28 | default = "Z" 29 | "--output_write_interval" 30 | arg_type = Int 31 | help = "after how many samples should the output file be updated" 32 | default = 10000 33 | "--num_samples", "-n" 34 | arg_type = Int 35 | help = "number of Monte Carlo samples" 36 | default = 1_000_000 37 | end 38 | args = parse_args(s) 39 | @assert args["sector"] in ["X", "Z"] 40 | 41 | data = Dict{Tuple{Int,Int}, Tuple{Int,Int}}() # (distance,err_rate,bond_dim) -> (num_succ,num_fail) 42 | for d in args["distance"] for p_int in args["error_rate"] 43 | data[d,p_int] = (0,0) 44 | end end 45 | 46 | for d in args["distance"] 47 | sc3d = unrotated_3D_surface_code(d) 48 | if args["sector"] == "Z" 49 | matching_decoder = pymatching.Matching(sc3d.Hz) 50 | else 51 | matching_decoder = pymatching.Matching(sc3d.Hx) 52 | end 53 | for p_int in args["error_rate"] 54 | p = p_int / 10000. 55 | for i = 1:args["num_samples"] 56 | err = convert(Vector{UInt8}, rand(sc3d.n_qubits) .< p) 57 | if args["sector"] == "Z" 58 | syndrome = convert(Vector{UInt8}, (sc3d.Hz * err) .% 2) 59 | err_L = sum(err .& sc3d.Lz) % 2 60 | prediction = matching_decoder.decode(syndrome) 61 | prediction_L = sum(prediction .& sc3d.Lz) % 2 62 | else 63 | syndrome = convert(Vector{UInt8}, (sc3d.Hx * err) .% 2) 64 | err_L = sum(err .& sc3d.Lx) % 2 65 | prediction = matching_decoder.decode(syndrome) 66 | prediction_L = sum(prediction .& sc3d.Lx) % 2 67 | end 68 | if prediction_L == err_L 69 | data[d,p_int] = data[d,p_int] .+ (1,0) 70 | else 71 | data[d,p_int] = data[d,p_int] .+ (0,1) 72 | end 73 | 74 | if i % args["output_write_interval"] == 0 75 | json_str = JSON.json(Dict( 76 | "args" => args, 77 | "data" => data 78 | )) 79 | open(args["out_file"], "w") do f 80 | write(f, json_str) 81 | end 82 | end 83 | 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /surface_code.jl: -------------------------------------------------------------------------------- 1 | function stab_3DSC_Z(d::Int) 2 | ind=Dict{Tuple{Int,Int,Int},Int}() 3 | for z=1:2d-1, y=0:2d-2, x=0:2d-2 4 | if isodd(x)+isodd(y)+isodd(z)==1 5 | ind[x,y,z]=length(ind)+1 6 | end 7 | end 8 | n=d*(3d^2-4d+2) 9 | ns=d*(3d^2-4d+2) 10 | is=1 11 | S=falses(n,ns) 12 | stab_pos = Vector{Tuple{Float64,Float64,Float64}}() 13 | for z=1:2d-1, y=0:2d-2, x=0:2d-2 14 | if isodd(x)+isodd(y)+isodd(z)==2 15 | if iseven(x) 16 | S[ind[x,y-1,z],is]=true 17 | S[ind[x,y+1,z],is]=true 18 | (z>1) && (S[ind[x,y,z-1],is]=true) 19 | (z<2d-1) && (S[ind[x,y,z+1],is]=true) 20 | push!(stab_pos, (x,y,z)) 21 | elseif iseven(y) 22 | if y==0 23 | S[ind[x-1,y,z],is]=true 24 | S[ind[x+1,y,z],is]=true 25 | (z>1) && (S[ind[x,y,z-1],is]=true) 26 | (z<2d-1) && (S[ind[x,y,z+1],is]=true) 27 | push!(stab_pos, (x,y,z)) 28 | else 29 | is-=1 30 | end 31 | else 32 | S[ind[x-1,y,z],is]=true 33 | S[ind[x+1,y,z],is]=true 34 | S[ind[x,y-1,z],is]=true 35 | S[ind[x,y+1,z],is]=true 36 | push!(stab_pos, (x,y,z)) 37 | end 38 | is+=1 39 | end 40 | end 41 | 42 | qubit_pos = Vector{Tuple{Float64,Float64,Float64}}(undef, n) 43 | for (k,v) in ind 44 | qubit_pos[v] = convert(Tuple{Float64,Float64,Float64}, k) 45 | end 46 | 47 | return S[:,1:is-1], stab_pos, qubit_pos 48 | end 49 | 50 | function log_3DSC_Z(d::Int)::BitVector 51 | L=falses(d*(3d^2-4d+2)) 52 | L[1:d*(3d-2):end].=true 53 | return L 54 | end 55 | 56 | function stab_3DSC_X(d::Int) 57 | ind=Dict{Tuple{Int,Int,Int},Int}() 58 | for z=1:2d-1, y=0:2d-2, x=0:2d-2 59 | if isodd(x)+isodd(y)+isodd(z)==1 60 | ind[x,y,z]=length(ind)+1 61 | end 62 | end 63 | n=d*(3d^2-4d+2) 64 | ns=(d-1)*d^2 65 | is=1 66 | S=falses(n,ns) 67 | stab_pos = Vector{Tuple{Float64,Float64,Float64}}(undef, ns) 68 | for z=1:2d-1, y=0:2d-2, x=0:2d-2 69 | if iseven(x)&&iseven(y)&&iseven(z) 70 | (x>0) && (S[ind[x-1,y,z],is]=true) 71 | (x<2d-2) && (S[ind[x+1,y,z],is]=true) 72 | (y>0) && (S[ind[x,y-1,z],is]=true) 73 | (y<2d-2) && (S[ind[x,y+1,z],is]=true) 74 | S[ind[x,y,z-1],is]=true 75 | S[ind[x,y,z+1],is]=true 76 | stab_pos[is] = (x,y,z) 77 | is+=1 78 | end 79 | end 80 | 81 | qubit_pos = Vector{Tuple{Float64,Float64,Float64}}(undef, n) 82 | for (k,v) in ind 83 | qubit_pos[v] = convert(Tuple{Float64,Float64,Float64}, k) 84 | end 85 | 86 | return S, stab_pos, qubit_pos 87 | end 88 | 89 | function log_3DSC_X(d::Int)::BitVector 90 | L=falses(d*(3d^2-4d+2)) 91 | # L[d^2+1:d*(3d-2)].=true 92 | L[1:d^2].=true 93 | return L 94 | end 95 | 96 | 97 | function unrotated_3D_surface_code(d::Int) 98 | # we switch X and Z to match panqec's convention 99 | Hx, pos_stab_x, pos_qubits = stab_3DSC_Z(d) 100 | Hx = convert(Matrix{UInt8}, transpose(Hx)) 101 | Lx = convert(Vector{UInt8}, log_3DSC_Z(d)) 102 | Hz, pos_stab_z, pos_qubits = stab_3DSC_X(d) 103 | Hz = convert(Matrix{UInt8}, transpose(Hz)) 104 | Lz = convert(Vector{UInt8}, log_3DSC_X(d)) 105 | n_qubits = size(Hz, 2) 106 | return CSSCode{3}(n_qubits, Hx, Hz, Lx, Lz, pos_qubits, pos_stab_x, pos_stab_z) 107 | end 108 | 109 | -------------------------------------------------------------------------------- /scripts/sweep.jl: -------------------------------------------------------------------------------- 1 | using MKL 2 | using ArgParse 3 | using JSON 4 | include("../tndecoder3d.jl") 5 | using .TNDecoder3D 6 | 7 | s = ArgParseSettings() 8 | @add_arg_table s begin 9 | "--distance", "-d" 10 | arg_type = Int 11 | help = "code distance" 12 | nargs = '+' 13 | required = true 14 | "--error_rate", "-p" 15 | arg_type = Int 16 | help = "physical error rate in unis of 1e-4" 17 | nargs = '+' 18 | required = true 19 | "--bond_dimension", "-b" 20 | arg_type = Int 21 | help = "3D bond dimensions" 22 | nargs = '+' 23 | required = true 24 | "--out_file", "-o" 25 | arg_type = String 26 | help = "file to which output data is written to" 27 | required = true 28 | "--mps_bond_dimension" 29 | arg_type = Int 30 | help = "2D bond dimensions" 31 | default = 8 32 | "--sector" 33 | arg_type = String 34 | help = "sector to consider, may be either 'X' or 'Z'. These correspond to the 23% and 3% sector respectively." 35 | default = "Z" 36 | "--output_write_interval" 37 | arg_type = Int 38 | help = "after how many samples should the output file be updated" 39 | default = 20 40 | "--num_samples", "-n" 41 | arg_type = Int 42 | help = "number of Monte Carlo samples" 43 | default = 1_000_000 44 | "--svd_eps" 45 | arg_type = Int 46 | help = "cuttof in power of 10 passed to ITensors's svd. Default is 16 (corresponding to 1e-16)" 47 | default = 16 48 | end 49 | args = parse_args(s) 50 | @assert args["sector"] in ["X", "Z"] 51 | 52 | data = Dict{Tuple{Int,Int,Int}, Tuple{Int,Int}}() # (distance,err_rate,bond_dim) -> (num_succ,num_fail) 53 | for d in args["distance"] for p_int in args["error_rate"] for bd in args["bond_dimension"] 54 | data[d,p_int,bd] = (0,0) 55 | end end end 56 | 57 | svd_eps = 10. ^ (-args["svd_eps"]) 58 | 59 | for d in args["distance"] 60 | sc3d = unrotated_3D_surface_code(d) 61 | for p_int in args["error_rate"] 62 | p = p_int / 10000. 63 | for i = 1:args["num_samples"] 64 | err = convert(Vector{UInt8}, rand(sc3d.n_qubits) .< p) 65 | for bd in args["bond_dimension"] 66 | if args["sector"] == "Z" 67 | syndrome = convert(Vector{UInt8}, (sc3d.Hz * err) .% 2) 68 | err_L = sum(err .& sc3d.Lz) % 2 69 | tn0,tn1 = gen_tn_sc3d_phenomenological(sc3d, d, p, syndrome) 70 | GC.gc() 71 | res = contract(tn1, bd, args["mps_bond_dimension"], svd_eps) 72 | GC.gc() 73 | dec_L = (res[1]>0.) ? 0 : 1 74 | else 75 | syndrome = convert(Vector{UInt8}, (sc3d.Hx * err) .% 2) 76 | repr0, repr1 = find_representatives(sc3d.Hx, sc3d.Lx, syndrome) 77 | err_L = sum(err .& sc3d.Lx) % 2 78 | tn0,tn1 = gen_tn_sc3d_dual(sc3d, d, p, repr0, repr1) 79 | GC.gc() 80 | res0 = contract(tn0, bd, args["mps_bond_dimension"], svd_eps) 81 | GC.gc() 82 | res1 = contract(tn1, bd, args["mps_bond_dimension"], svd_eps) 83 | GC.gc() 84 | dec_L = get_res(res0,res1) 85 | end 86 | 87 | if dec_L == err_L 88 | data[d,p_int,bd] = data[d,p_int,bd] .+ (1,0) 89 | else 90 | data[d,p_int,bd] = data[d,p_int,bd] .+ (0,1) 91 | end 92 | end 93 | 94 | if i % args["output_write_interval"] == 0 95 | json_str = JSON.json(Dict( 96 | "args" => args, 97 | "data" => data 98 | )) 99 | open(args["out_file"], "w") do f 100 | write(f, json_str) 101 | end 102 | end 103 | 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /tn_generation.jl: -------------------------------------------------------------------------------- 1 | struct CSSCode{N} 2 | n_qubits::Int 3 | Hx::Matrix{UInt8} 4 | Hz::Matrix{UInt8} 5 | Lx::Vector{UInt8} 6 | Lz::Vector{UInt8} 7 | pos_qubits::Vector{NTuple{N,Float64}} 8 | pos_stab_x::Vector{NTuple{N,Float64}} 9 | pos_stab_z::Vector{NTuple{N,Float64}} 10 | end 11 | 12 | struct StabilizerCode{N} 13 | n_qubits::Int 14 | stabs::Matrix{UInt8} # rows are of size 2n to store x and z part 15 | Lx::Vector{UInt8} # vector of size 2n to store x and z part 16 | Lz::Vector{UInt8} # vector of size 2n to store x and z part 17 | pos_qubits::Vector{NTuple{N,Float64}} 18 | pos_stabs::Vector{NTuple{N,Float64}} 19 | end 20 | 21 | struct DEM{N} 22 | n_bits::Int 23 | H::Matrix{UInt8} 24 | L::Vector{UInt8} 25 | p::Vector{Float64} 26 | pos_checks::Vector{NTuple{N,Float64}} 27 | end 28 | 29 | @enum TensorType TT_DELTA=1 TT_CHECK=2 DEPOL=3 30 | mutable struct BitFlipTensor{N} 31 | adj::Vector{Int} 32 | type::TensorType 33 | val::Tuple{Float64,Float64} 34 | pos::NTuple{N, Float64} 35 | end 36 | const BitFlipTN{N} = Vector{BitFlipTensor{N}} where N 37 | 38 | mutable struct DepolTensor{N} 39 | adj::Vector{Int} 40 | type::TensorType 41 | adj_is_x::Vector{Bool} # true=X and false=Z. Only relevant for depol tensors. 42 | val::Tuple{Float64,Float64} # (1-p) and p/3 for depol tensor 43 | pos::NTuple{N, Float64} 44 | end 45 | const DepolTN{N} = Vector{DepolTensor{N}} where N 46 | 47 | 48 | 49 | function get_arr(tensor::BitFlipTensor)::Array{Float64} 50 | if tensor.type == STT_DELTA 51 | num_legs = length(tensor.adj) 52 | t = zeros(2*ones(Int, num_legs)...) 53 | t[1] = tensor.val[1] 54 | t[end] = tensor.val[2] 55 | return t 56 | elseif tensor.type == STT_CHECK 57 | num_legs = length(tensor.adj) 58 | t = zeros(2*ones(Int, num_legs)...) 59 | for i = 0:(2^num_legs)-1 60 | t[i+1] = (count_ones(i)%2 == 0) ? tensor.val[1] : tensor.val[2] 61 | end 62 | return t 63 | else 64 | error("Invalid tensor type") 65 | end 66 | end 67 | 68 | 69 | 70 | function css_to_stab(css::CSSCode{N})::StabilizerCode{N} where N 71 | return StabilizerCode{N}( 72 | css.n_qubits, 73 | [css.Hx ; zeros(UInt8, size(css.Hx,1), size(css.Hz,2)) ;; zeros(UInt8, size(css.Hz,1), size(css.Hx,2)) ; css.Hz], 74 | [css.Lx; zeros(UInt8, css.n_qubits)], 75 | [zeros(UInt8, css.n_qubits); css.Lz], 76 | css.pos_qubits, 77 | [css.pos_stab_x; css.pos_stab_z] 78 | ) 79 | end 80 | 81 | 82 | function dem_from_stim(stim_model::PyObject) 83 | # possible instructions in a detector error model are: detector, logical_observable, error, shift_detectors, repeat 84 | # the flattened() method removes the last two 85 | model_string = stim_model.flattened().__str__() 86 | 87 | # count number of errors and detectors 88 | num_error = 0 89 | num_detector = 0 90 | for line in split(model_string, "\n") 91 | if split(line, "(")[1] == "error" 92 | num_error += 1 93 | elseif split(line, "(")[1] == "detector" 94 | num_detector += 1 95 | end 96 | end 97 | 98 | # create dem objects 99 | H = zeros(UInt8, num_detector, num_error) 100 | L = zeros(UInt8, num_error) 101 | p = zeros(Float64, num_error) 102 | pos_checks = Vector{NTuple{3,Float64}}(undef, num_detector) 103 | 104 | # populate objects 105 | err_idx = 0 106 | for line in split(model_string, "\n") 107 | instr_name = split(line, "(")[1] 108 | arguments = split( split(line, "(")[2], ")")[1] 109 | arguments = [strip(s) for s in split(arguments, ",")] 110 | targets = strip(split(line, ")")[2]) 111 | targets = [strip(s) for s in split(targets, " ")] 112 | 113 | if instr_name == "error" 114 | err_idx += 1 115 | p[err_idx] = parse(Float64, arguments[1]) 116 | for t in targets 117 | if t == "L0" 118 | L[err_idx] = 1 119 | elseif t[1] == 'D' 120 | det_idx = parse(Int, t[2:end]) + 1 121 | H[det_idx, err_idx] = 1 122 | else 123 | error("Invalid error target: ", t) 124 | end 125 | end 126 | elseif instr_name == "detector" 127 | posx = parse(Float64, arguments[1]) 128 | posy = parse(Float64, arguments[2]) 129 | posz = parse(Float64, arguments[3]) 130 | idx = parse(Int, targets[1][2:end]) + 1 131 | pos_checks[idx] = (posx,posy,posz) 132 | else 133 | error("Unsupported instruction: ", instr_name) 134 | end 135 | end 136 | 137 | return DEM{3}(num_error,H, L, p, pos_checks) 138 | end 139 | 140 | 141 | -------------------------------------------------------------------------------- /scripts/sweep_circ.jl: -------------------------------------------------------------------------------- 1 | using JLD2 2 | using MKL 3 | using ArgParse 4 | using JSON 5 | include("../tndecoder3d.jl") 6 | using .TNDecoder3D 7 | using PyCall 8 | using ITensors 9 | @pyimport stim 10 | 11 | s = ArgParseSettings() 12 | @add_arg_table s begin 13 | "--distance", "-d" 14 | arg_type = Int 15 | help = "code distance" 16 | nargs = '+' 17 | required = true 18 | "--error_rate", "-p" 19 | arg_type = Int 20 | help = "physical error rate in unis of 1e-4" 21 | nargs = '+' 22 | required = true 23 | "--truncation_dimension", "-t" 24 | arg_type = Int 25 | help = "bond dimension of pre-compressed network" 26 | default = 4 27 | "--split_dimension", "-s" 28 | arg_type = Int 29 | help = "bond dimension used during SVD splitting process" 30 | default = 8 31 | "--bond_dimension", "-b" 32 | arg_type = Int 33 | help = "3D bond dimensions" 34 | nargs = '+' 35 | required = true 36 | "--out_file", "-o" 37 | arg_type = String 38 | help = "file to which output data is written to" 39 | required = true 40 | "--mps_bond_dimension" 41 | arg_type = Int 42 | help = "2D bond dimensions" 43 | default = 8 44 | "--output_write_interval" 45 | arg_type = Int 46 | help = "after how many samples should the output file be updated" 47 | default = 20 48 | "--num_samples", "-n" 49 | arg_type = Int 50 | help = "number of Monte Carlo samples" 51 | default = 1_000_000 52 | "--svd_eps" 53 | arg_type = Int 54 | help = "cuttof in power of 10 passed to ITensors's svd. Default is 16 (corresponding to 1e-16)" 55 | default = 16 56 | end 57 | args = parse_args(s) 58 | 59 | svd_eps = 10. ^ (-args["svd_eps"]) 60 | 61 | data = Dict{Tuple{Int,Int,Int}, Tuple{Int,Int}}() # (distance,err_rate,bond_dim) -> (num_succ,num_fail) 62 | for d in args["distance"] for p_int in args["error_rate"] for bd in args["bond_dimension"] 63 | data[d,p_int,bd] = (0,0) 64 | end end end 65 | 66 | for d in args["distance"] 67 | for p_int in args["error_rate"] 68 | p = p_int / 10000. 69 | circuit = stim.Circuit.generated("surface_code:rotated_memory_x", distance=d, rounds=d, after_clifford_depolarization=p, before_measure_flip_probability=p, after_reset_flip_probability=p) 70 | model = circuit.detector_error_model(decompose_errors=false) 71 | dem = dem_from_stim(model) 72 | sampler = model.compile_sampler() 73 | 74 | #tn3d,expval,open_index = gen_tn_sc3d_circ(dem, 6) 75 | tn3d,expval,open_index = load_object("/cpivetea/circtn"*string(d)*".jld2") 76 | 77 | pos_checks = [convert(Tuple{Int,Int,Int}, round.((0.5*pos[1], 0.5*pos[2], pos[3]))) for pos in dem.pos_checks] 78 | xmin,xmax = extrema([pos[1] for pos in pos_checks]) 79 | ymin,ymax = extrema([pos[2] for pos in pos_checks]) 80 | zmin,zmax = extrema([pos[3] for pos in pos_checks]) 81 | 82 | final_truncation_dim = args["truncation_dimension"] 83 | if final_truncation_dim != 0 84 | for x=xmin:xmax for y=ymin:ymax for z=zmin:zmax 85 | for pos2 in TNDecoder3D.neighbors(tn3d,(x,y,z)) if pos2>(x,y,z) 86 | expval += TNDecoder3D.identity_SU!(tn3d, (x,y,z), pos2, final_truncation_dim, svd_eps) 87 | expval += TNDecoder3D.renormalize_tensor!(tn3d, (x,y,z)) 88 | expval += TNDecoder3D.renormalize_tensor!(tn3d, pos2) 89 | expval += TNDecoder3D.renormalize_bond_matrix!(tn3d, (x,y,z), pos2) 90 | end end 91 | end end end 92 | end 93 | tn3d_base = tn3d 94 | 95 | 96 | for i = 1:args["num_samples"] 97 | tn3d = deepcopy(tn3d_base) 98 | samples, logicals, _ = sampler.sample(1) 99 | detectors = convert(Vector{UInt8}, samples[1,:]) 100 | logical_err = convert(UInt8, logicals[1]) 101 | 102 | for i = 1:length(detectors) 103 | pos = pos_checks[i] 104 | if detectors[i] == 0 105 | tn3d[pos] *= ITensor([1., 0.], open_index[pos]) 106 | else 107 | tn3d[pos] *= ITensor([0., 1.], open_index[pos]) 108 | end 109 | end 110 | for x=xmin:xmax for y=ymin:ymax for z=zmin:zmax 111 | if length(open_indices(tn3d, (x,y,z))) == 1 112 | tn3d[(x,y,z)] *= ITensor([1.], open_indices(tn3d,(x,y,z))[1]) 113 | end 114 | end end end 115 | 116 | for bd in args["bond_dimension"] 117 | res = contract_cubictn(deepcopy(tn3d), bd, args["mps_bond_dimension"]; svd_eps=svd_eps , split_dim=args["split_dimension"]) 118 | dec_L = (res[1]>0.) ? 0 : 1 119 | 120 | if dec_L == logical_err 121 | data[d,p_int,bd] = data[d,p_int,bd] .+ (1,0) 122 | else 123 | data[d,p_int,bd] = data[d,p_int,bd] .+ (0,1) 124 | end 125 | end 126 | 127 | if i % args["output_write_interval"] == 0 128 | json_str = JSON.json(Dict( 129 | "args" => args, 130 | "data" => data 131 | )) 132 | open(args["out_file"], "w") do f 133 | write(f, json_str) 134 | end 135 | end 136 | 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /scripts/sweep_depol_bposd.jl: -------------------------------------------------------------------------------- 1 | using MKL 2 | using ArgParse 3 | using JSON 4 | using PyCall 5 | include("../tndecoder3d.jl") 6 | using .TNDecoder3D 7 | @pyimport ldpc 8 | @pyimport panqec.codes as pqc 9 | 10 | s = ArgParseSettings() 11 | @add_arg_table s begin 12 | "--distance", "-d" 13 | arg_type = Int 14 | help = "code distance" 15 | nargs = '+' 16 | required = true 17 | "--error_rate", "-p" 18 | arg_type = Int 19 | help = "physical error rate in unis of 1e-4" 20 | nargs = '+' 21 | required = true 22 | "--out_file", "-o" 23 | arg_type = String 24 | help = "file to which output data is written to" 25 | required = true 26 | "--output_write_interval" 27 | arg_type = Int 28 | help = "after how many samples should the output file be updated" 29 | default = 20 30 | "--num_samples", "-n" 31 | arg_type = Int 32 | help = "number of Monte Carlo samples" 33 | default = 1_000_000 34 | end 35 | args = parse_args(s) 36 | 37 | data = Dict{Tuple{Int,Int}, Tuple{Int,Int}}() # (distance,err_rate,bond_dim) -> (num_succ,num_fail) 38 | for d in args["distance"] for p_int in args["error_rate"] 39 | data[d,p_int] = (0,0) 40 | end end 41 | 42 | 43 | function build_dem(sc3d::PyObject, p::Float64) 44 | Hx = convert(Matrix{Int8}, sc3d.Hx.todense()) 45 | Hz = convert(Matrix{Int8}, sc3d.Hz.todense()) 46 | n_stab_x = size(Hx,1) 47 | n_stab_z = size(Hz,1) 48 | H = zeros(Int8, n_stab_x+n_stab_z, 3*sc3d.n) 49 | 50 | # depol noise [1-p, p/3, p/3, p/3] corresponds to independent X,Y,Z noise 51 | # with strength q: 52 | q = 0.5 - sqrt(9. - 12*p) / 6. 53 | p_vec = q * ones(Float64, 3*sc3d.n) 54 | 55 | for i = 0:sc3d.n-1 56 | H[:,3*i+1] = [zeros(Int8, n_stab_x) ; Hz[:,i+1]] # X error 57 | H[:,3*i+2] = [Hx[:,i+1] ; Hz[:,i+1]] # Y error 58 | H[:,3*i+3] = [Hx[:,i+1] ; zeros(Int8, n_stab_z)] # Z error 59 | end 60 | 61 | return H, p_vec 62 | end 63 | 64 | for d in args["distance"] 65 | sc3d = pqc.surface_3d.Planar3DCode(d,d,d) 66 | for p_int in args["error_rate"] 67 | p = p_int / 10000. 68 | 69 | Hx = convert(Matrix{Int8}, sc3d.Hx.todense()) 70 | Hz = convert(Matrix{Int8}, sc3d.Hz.todense()) 71 | Lx = convert(Vector{Int8}, sc3d.logicals_x[1,:])[1:sc3d.n] 72 | Lz = convert(Vector{Int8}, sc3d.logicals_z[1,:])[sc3d.n+1:end] 73 | 74 | if d == 3 75 | order = 19 76 | else 77 | order = 60 78 | end 79 | decX = ldpc.bposd_decoder( 80 | Hx, 81 | channel_probs=ones(sc3d.n)*2*p/3, 82 | max_iter=sc3d.n, 83 | bp_method="ms", 84 | ms_scaling_factor=0, #min sum scaling factor. If set to zero the variable scaling factor method is used 85 | osd_method="osd_cs", #the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" 86 | osd_order=order #the osd search depth 87 | ) 88 | decZ = ldpc.bposd_decoder( 89 | Hz, 90 | channel_probs=ones(sc3d.n)*2*p/3, 91 | max_iter=sc3d.n, 92 | bp_method="ms", 93 | ms_scaling_factor=0, #min sum scaling factor. If set to zero the variable scaling factor method is used 94 | osd_method="osd_cs", #the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" 95 | osd_order=order #the osd search depth 96 | ) 97 | 98 | for i = 1:args["num_samples"] 99 | # to sample depol noise, sample a random number in [0,1] 100 | # range (0,p/3) : X error 101 | # range (p/3,2*p/3) : Y error 102 | # range (2p/3,p) : Z error 103 | # range (p,1) : no error 104 | err = rand(sc3d.n) 105 | err_x = err .< (p * 2/3) 106 | err_z = (err .> (p * 1/3)) .& (err .< p) 107 | syndrome_x = (Hx * err_z) .% 2 108 | syndrome_z = (Hz * err_x) .% 2 109 | err_Lx = sum(err_z .& Lx) % 2 110 | err_Lz = sum(err_x .& Lz) % 2 111 | 112 | #error_estimate_x = decZ.decode(syndrome_z) 113 | #new_probs = zeros(sc3d.n) 114 | #for i = 1:sc3d.n 115 | # new_probs[i] = (error_estimate_x[i]==1) ? 0.5 : (p/3) / (1. - 2*p/3) 116 | #end 117 | #decX.update_channel_probs(new_probs) 118 | #error_estimate_z = decX.decode(syndrome_x) 119 | 120 | error_estimate_z = decX.decode(syndrome_x) 121 | new_probs = zeros(sc3d.n) 122 | for i = 1:sc3d.n 123 | new_probs[i] = (error_estimate_z[i]==1) ? 0.5 : (p/3) / (1. - 2*p/3) 124 | end 125 | decZ.update_channel_probs(new_probs) 126 | error_estimate_x = decZ.decode(syndrome_z) 127 | 128 | dec_LX = sum( error_estimate_z .& Lx) % 2 129 | dec_LZ = sum( error_estimate_x .& Lz) % 2 130 | 131 | if dec_LX == err_Lx && dec_LZ == err_Lz 132 | data[d,p_int] = data[d,p_int] .+ (1,0) 133 | else 134 | data[d,p_int] = data[d,p_int] .+ (0,1) 135 | end 136 | 137 | if i % args["output_write_interval"] == 0 138 | json_str = JSON.json(Dict( 139 | "args" => args, 140 | "data" => data 141 | )) 142 | open(args["out_file"], "w") do f 143 | write(f, json_str) 144 | end 145 | end 146 | 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /scripts/sweep_depol.jl: -------------------------------------------------------------------------------- 1 | using MKL 2 | using ArgParse 3 | using JSON 4 | include("../tndecoder3d.jl") 5 | using .TNDecoder3D 6 | 7 | s = ArgParseSettings() 8 | @add_arg_table s begin 9 | "--distance", "-d" 10 | arg_type = Int 11 | help = "code distance" 12 | nargs = '+' 13 | required = true 14 | "--error_rate", "-p" 15 | arg_type = Int 16 | help = "physical error rate in unis of 1e-4" 17 | nargs = '+' 18 | required = true 19 | "--bond_dimension", "-b" 20 | arg_type = Int 21 | help = "3D bond dimensions" 22 | nargs = '+' 23 | required = true 24 | "--out_file", "-o" 25 | arg_type = String 26 | help = "file to which output data is written to" 27 | required = true 28 | "--mps_bond_dimension" 29 | arg_type = Int 30 | help = "2D bond dimensions" 31 | default = 8 32 | "--output_write_interval" 33 | arg_type = Int 34 | help = "after how many samples should the output file be updated" 35 | default = 20 36 | "--num_samples", "-n" 37 | arg_type = Int 38 | help = "number of Monte Carlo samples" 39 | default = 1_000_000 40 | "--svd_eps" 41 | arg_type = Int 42 | help = "cuttof in power of 10 passed to ITensors's svd. Default is 16 (corresponding to 1e-16)" 43 | default = 16 44 | end 45 | args = parse_args(s) 46 | 47 | data = Dict{Tuple{Int,Int,Int}, Tuple{Int,Int}}() # (distance,err_rate,bond_dim) -> (num_succ,num_fail) 48 | for d in args["distance"] for p_int in args["error_rate"] for bd in args["bond_dimension"] 49 | data[d,p_int,bd] = (0,0) 50 | end end end 51 | 52 | svd_eps = 10. ^ (-args["svd_eps"]) 53 | 54 | function tuple_add(tlist) 55 | max_exp = maximum([t[2] for t in tlist]) 56 | base = sum([ t[1] * 2. ^ (t[2]-max_exp) for t in tlist ]) 57 | if base == 0. 58 | return (0., -10000000) 59 | else 60 | h = Int(floor(log2(abs(base)))) 61 | return (base/exp2(h) , max_exp+h) 62 | end 63 | end 64 | 65 | for d in args["distance"] 66 | sc3d = unrotated_3D_surface_code(d) 67 | for p_int in args["error_rate"] 68 | p = p_int / 10000. 69 | for i = 1:args["num_samples"] 70 | err = convert(Vector{UInt8}, rand(sc3d.n_qubits) .< p) 71 | for bd in args["bond_dimension"] 72 | # to sample depol noise, sample a random number in [0,1] 73 | # range (0,p/3) : X error 74 | # range (p/3,2*p/3) : Y error 75 | # range (2p/3,p) : Z error 76 | # range (p,1) : no error 77 | err = rand(sc3d.n_qubits) 78 | err_x = err .< (p * 2/3) 79 | err_z = (err .> (p * 1/3)) .& (err .< p) 80 | syndrome_x = convert(Vector{UInt8}, (sc3d.Hx * err_z) .% 2) 81 | syndrome_z = convert(Vector{UInt8}, (sc3d.Hz * err_x) .% 2) 82 | err_Lx = sum(err_z .& sc3d.Lx) % 2 83 | err_Lz = sum(err_x .& sc3d.Lz) % 2 84 | 85 | if err_Lx==0 && err_Lz==0 idx_ref = 1 86 | elseif err_Lx==1 && err_Lz==0 idx_ref = 2 87 | elseif err_Lx==1 && err_Lz==1 idx_ref = 3 88 | elseif err_Lx==0 && err_Lz==1 idx_ref = 4 89 | else error("???") 90 | end 91 | 92 | tnI, tnX, tnY, tnZ = gen_tn_depol(sc3d, d, p, syndrome_x, syndrome_z) 93 | 94 | resI = (0., -10000000) 95 | resX = (0., -10000000) 96 | resY = (0., -10000000) 97 | resZ = (0., -10000000) 98 | try 99 | resI = contract_cubictn(deepcopy(tnI), bd, args["mps_bond_dimension"] ; svd_eps=svd_eps, split_dim=4, ignore_bond_tensors=true) 100 | catch e end 101 | try 102 | resX = contract_cubictn(deepcopy(tnX), bd, args["mps_bond_dimension"] ; svd_eps=svd_eps, split_dim=4, ignore_bond_tensors=true) 103 | catch e end 104 | try 105 | resY = contract_cubictn(deepcopy(tnY), bd, args["mps_bond_dimension"] ; svd_eps=svd_eps, split_dim=4, ignore_bond_tensors=true) 106 | catch e end 107 | try 108 | resZ = contract_cubictn(deepcopy(tnZ), bd, args["mps_bond_dimension"] ; svd_eps=svd_eps, split_dim=4, ignore_bond_tensors=true) 109 | catch e end 110 | 111 | A00 = tuple_add([ ( resI[1],resI[2]), ( resY[1],resY[2]), ( resZ[1],resZ[2]), ( resX[1],resX[2]) ]) 112 | A01 = tuple_add([ ( resI[1],resI[2]), (-resY[1],resY[2]), ( resZ[1],resZ[2]), (-resX[1],resX[2]) ]) 113 | A10 = tuple_add([ ( resI[1],resI[2]), ( resY[1],resY[2]), (-resZ[1],resZ[2]), (-resX[1],resX[2]) ]) 114 | A11 = tuple_add([ ( resI[1],resI[2]), (-resY[1],resY[2]), (-resZ[1],resZ[2]), ( resX[1],resX[2]) ]) 115 | idx_dec = argmax([ reverse(t) for t in [A00,A01,A10,A11] ]) 116 | 117 | if idx_ref == idx_dec 118 | data[d,p_int,bd] = data[d,p_int,bd] .+ (1,0) 119 | else 120 | data[d,p_int,bd] = data[d,p_int,bd] .+ (0,1) 121 | end 122 | end 123 | 124 | if i % args["output_write_interval"] == 0 125 | json_str = JSON.json(Dict( 126 | "args" => args, 127 | "data" => data 128 | )) 129 | open(args["out_file"], "w") do f 130 | write(f, json_str) 131 | end 132 | end 133 | 134 | end 135 | end 136 | end 137 | 138 | 139 | -------------------------------------------------------------------------------- /contract_depol.jl: -------------------------------------------------------------------------------- 1 | function gen_tn_general(sc3d::CSSCode{3}, d::Int, PTensor::Matrix{Float64}, syndrome_x::Vector{UInt8}, syndrome_z::Vector{UInt8}) 2 | pos_qubits = [convert(Pos3D, pos) for pos in sc3d.pos_qubits] 3 | pos_stab_x = [convert(Pos3D, pos) for pos in sc3d.pos_stab_x] 4 | pos_stab_z = [convert(Pos3D, pos) for pos in sc3d.pos_stab_z] 5 | xmin,xmax = extrema([pos[1] for pos in [pos_qubits ; pos_stab_x ; pos_stab_z]]) 6 | ymin,ymax = extrema([pos[2] for pos in [pos_qubits ; pos_stab_x ; pos_stab_z]]) 7 | zmin,zmax = extrema([pos[3] for pos in [pos_qubits ; pos_stab_x ; pos_stab_z]]) 8 | # build empty 3D tensor network 9 | tnI = generate_empty_cubictn(xmin, xmax, ymin, ymax, zmin, zmax) 10 | tnX = generate_empty_cubictn(xmin, xmax, ymin, ymax, zmin, zmax) 11 | tnY = generate_empty_cubictn(xmin, xmax, ymin, ymax, zmin, zmax) 12 | tnZ = generate_empty_cubictn(xmin, xmax, ymin, ymax, zmin, zmax) 13 | for x=xmin:xmax, y=ymin:ymax, z=zmin:zmax 14 | # remove bond tensor connections 15 | # (it's kinda hacky that I use the ContractionNetwork object here) 16 | tnI[(x,y,z)] = ITensor([1.]) 17 | tnX[(x,y,z)] = ITensor([1.]) 18 | tnY[(x,y,z)] = ITensor([1.]) 19 | tnZ[(x,y,z)] = ITensor([1.]) 20 | end 21 | 22 | # build a map of indices 23 | indices = Dict{Set{Pos3D},Index}() 24 | for i=1:sc3d.n_qubits, j=1:size(sc3d.Hx,1) 25 | if sc3d.Hx[j,i] == 1 26 | @assert pos_qubits[i] in neighbors(tnX, pos_stab_x[j]) 27 | indices[Set([pos_qubits[i], pos_stab_x[j]])] = Index(2) 28 | end 29 | end 30 | for i=1:sc3d.n_qubits, j=1:size(sc3d.Hz,1) 31 | if sc3d.Hz[j,i] == 1 32 | @assert pos_qubits[i] in neighbors(tnX, pos_stab_z[j]) 33 | indices[Set([pos_qubits[i], pos_stab_z[j]])] = Index(2) 34 | end 35 | end 36 | 37 | # add check tensors 38 | for j=1:size(sc3d.Hx,1) 39 | pos = pos_stab_x[j] 40 | inds = Vector{Index}() 41 | for pos2 in neighbors(tnX, pos) 42 | if Set([pos,pos2]) in keys(indices) 43 | push!(inds, indices[Set([pos,pos2])]) 44 | end 45 | end 46 | if syndrome_x[j] == 0 47 | arr = check_arr(length(inds), 1., 0.) 48 | else 49 | arr = check_arr(length(inds), 0., 1.) 50 | end 51 | tnI[pos] = ITensor(arr, inds...) 52 | tnX[pos] = ITensor(arr, inds...) 53 | tnY[pos] = ITensor(arr, inds...) 54 | tnZ[pos] = ITensor(arr, inds...) 55 | end 56 | for j=1:size(sc3d.Hz,1) 57 | pos = pos_stab_z[j] 58 | inds = Vector{Index}() 59 | for pos2 in neighbors(tnX, pos) 60 | if Set([pos,pos2]) in keys(indices) 61 | push!(inds, indices[Set([pos,pos2])]) 62 | end 63 | end 64 | if syndrome_z[j] == 0 65 | arr = check_arr(length(inds), 1., 0.) 66 | else 67 | arr = check_arr(length(inds), 0., 1.) 68 | end 69 | tnI[pos] = ITensor(arr, inds...) 70 | tnX[pos] = ITensor(arr, inds...) 71 | tnY[pos] = ITensor(arr, inds...) 72 | tnZ[pos] = ITensor(arr, inds...) 73 | end 74 | 75 | # add depol tensors 76 | for i=1:sc3d.n_qubits 77 | pos = pos_qubits[i] 78 | 79 | # find indices of connected X and Z stabilizers 80 | inds_X = Vector{Index}() 81 | inds_Z = Vector{Index}() 82 | for pos2 in neighbors(tnX, pos) 83 | if Set([pos,pos2]) in keys(indices) 84 | if pos2 in pos_stab_x 85 | push!(inds_X, indices[Set([pos,pos2])]) 86 | else 87 | push!(inds_Z, indices[Set([pos,pos2])]) 88 | end 89 | end 90 | end 91 | 92 | # internal indices 93 | idx_X = Index(2) 94 | idx_Z = Index(2) 95 | 96 | T = ITensor(PTensor, idx_X, idx_Z) 97 | T *= ITensor(delta_arr(length(inds_Z)+1, 1., 1.), inds_Z..., idx_Z) 98 | T *= ITensor(delta_arr(length(inds_X)+1, 1., 1.), inds_X..., idx_X) 99 | tnI[pos] = T 100 | 101 | T = ITensor(PTensor, idx_X, idx_Z) 102 | val = (sc3d.Lx[i]==1) ? (1., -1.) : (1., 1.) 103 | T *= ITensor(delta_arr(length(inds_X)+1, val[1], val[2]), inds_X..., idx_X) 104 | T *= ITensor(delta_arr(length(inds_Z)+1, 1., 1.), inds_Z..., idx_Z) 105 | tnX[pos] = T 106 | 107 | T = ITensor(PTensor, idx_X, idx_Z) 108 | val = (sc3d.Lx[i]==1) ? (1., -1.) : (1., 1.) 109 | T *= ITensor(delta_arr(length(inds_X)+1, val[1], val[2]), inds_X..., idx_X) 110 | val = (sc3d.Lz[i]==1) ? (1., -1.) : (1., 1.) 111 | T *= ITensor(delta_arr(length(inds_Z)+1, val[1], val[2]), inds_Z..., idx_Z) 112 | tnY[pos] = T 113 | 114 | T = ITensor(PTensor, idx_X, idx_Z) 115 | T *= ITensor(delta_arr(length(inds_X)+1, 1., 1.), inds_X..., idx_X) 116 | val = (sc3d.Lz[i]==1) ? (1., -1.) : (1., 1.) 117 | T *= ITensor(delta_arr(length(inds_Z)+1, val[1], val[2]), inds_Z..., idx_Z) 118 | tnZ[pos] = T 119 | end 120 | 121 | # add dummy indices to make sure it's truly cubic 122 | for x=xmin:xmax, y=ymin:ymax, z=zmin:zmax 123 | for pos2 in neighbors(tnX, (x,y,z)) 124 | if length(commoninds(tnX[(x,y,z)], tnX[pos2])) == 0 125 | idx = Index(1) 126 | tnI[(x,y,z)] *= ITensor([1.], idx) 127 | tnI[pos2] *= ITensor([1.], idx) 128 | tnX[(x,y,z)] *= ITensor([1.], idx) 129 | tnX[pos2] *= ITensor([1.], idx) 130 | tnY[(x,y,z)] *= ITensor([1.], idx) 131 | tnY[pos2] *= ITensor([1.], idx) 132 | tnZ[(x,y,z)] *= ITensor([1.], idx) 133 | tnZ[pos2] *= ITensor([1.], idx) 134 | end 135 | end 136 | end 137 | 138 | return tnI, tnX, tnY, tnZ 139 | end 140 | 141 | function gen_tn_depol(sc3d::CSSCode{3}, d::Int, err_rate::Float64, syndrome_x::Vector{UInt8}, syndrome_z::Vector{UInt8}) 142 | return gen_tn_general(sc3d, d, [(1. - err_rate) err_rate/3. ; err_rate/3. err_rate/3.], syndrome_x, syndrome_z) 143 | end 144 | -------------------------------------------------------------------------------- /tools.jl: -------------------------------------------------------------------------------- 1 | DELTA3 = zeros(Float64, 2, 2, 2) 2 | DELTA3[begin] = 1. 3 | DELTA3[end] = 1. 4 | CHECK3 = zeros(Float64, 2, 2, 2) 5 | CHECK3[1,1,1] = 1. 6 | CHECK3[1,2,2] = 1. 7 | CHECK3[2,2,1] = 1. 8 | CHECK3[2,1,2] = 1. 9 | 10 | function delta_arr(num_legs::Int64, val1::Float64, val2::Float64) 11 | t = zeros(2*ones(Int,num_legs)...) 12 | t[begin] = val1 13 | t[end] = val2 14 | return t 15 | end 16 | function check_arr(num_legs::Int64, val1::Float64, val2::Float64) 17 | t = zeros(2*ones(Int,num_legs)...) 18 | for i = 1:2^num_legs 19 | t[i] = (count_ones(i-1)%2 == 0) ? val1 : val2 20 | end 21 | return t 22 | end 23 | 24 | function get_res(res0::Tuple{Float64,Int}, res1::Tuple{Float64,Int}) 25 | if res0[1] == 0. 26 | return UInt8(1) 27 | elseif res1[1] == 0. 28 | return UInt8(0) 29 | end 30 | 31 | if res0[2] == res1[2] 32 | return UInt8(abs(res1[1]) > abs(res0[1])) 33 | else 34 | return UInt8(res1[2] > res0[2]) 35 | end 36 | end 37 | 38 | 39 | function permute_TN(tn::BitFlipTN, perm::Vector{Int}) 40 | @assert length(tn) == length(perm) 41 | tn2 = deepcopy(tn) 42 | iperm = invperm(perm) 43 | permute!(tn2, perm) 44 | for i=1:length(tn2) 45 | tn2[i].adj = [iperm[j] for j in tn2[i].adj] 46 | end 47 | return tn2 48 | end 49 | 50 | 51 | function rank_mod2(A::Matrix{UInt8}) 52 | m = size(A,1) 53 | n = size(A,2) 54 | # WLOG assume we have more cols than rows 55 | tranposed = false 56 | if n < m 57 | transposed = true 58 | A = transpose(A) 59 | n, m = m, n 60 | end 61 | 62 | basis_size = 0 63 | M = zeros(UInt8, (m, m)) # reduced A in lower triangular form 64 | free_indices= zeros(Int32, m) # indices of non-fixed bits that form basis 65 | for i = 1:n 66 | v = copy(A[:,i]) 67 | for j = 1:m 68 | if v[j] == 1 69 | if free_indices[j] != 0 70 | v = (v .+ M[:,j]) .% 2 71 | else 72 | # add to M 73 | M[:,j] = v 74 | free_indices[j] = i 75 | basis_size += 1 76 | break 77 | end 78 | end 79 | end 80 | if basis_size == m 81 | break 82 | end 83 | end 84 | 85 | return basis_size 86 | end 87 | 88 | function linearly_independent_rows(A::Matrix{UInt8}) 89 | num_rows = size(A)[1] 90 | B = zeros(UInt8, size(A)...) 91 | 92 | idx = 1 93 | retval = Vector{Int}() 94 | for i = 1:num_rows 95 | B[idx,:] = A[i,:] 96 | if rank_mod2(B[1:idx,:]) == idx 97 | # row was linearly independent 98 | idx += 1 99 | push!(retval, i) 100 | end 101 | end 102 | return retval 103 | end 104 | 105 | function _solve_mod2_lineq!(A::Matrix{UInt8}, b::Vector{UInt8}) 106 | # we assume that A is full-rank here. 107 | # A and b get modified in-place, solution is stored in b 108 | 109 | # first we bring A to upper triangular form 110 | n = size(A)[1] 111 | for i = 1:n # iterate through columns 112 | if A[i,i] == 0 113 | # need to swap row with another one 114 | found_swapping_row = false 115 | for j = i+1:n 116 | if A[j,i] == 1 117 | A[i,:], A[j,:] = A[j,:], A[i,:] 118 | b[i], b[j] = b[j], b[i] 119 | found_swapping_row = true 120 | break 121 | end 122 | end 123 | if not found_swapping_row 124 | error("Did not find row to swap with") 125 | end 126 | end 127 | # make sure all entries below (i,i) are set to zero 128 | for j = i+1:n 129 | if A[j,i] == 1 130 | A[j,:] = (A[j,:] + A[i,:] ) .% 2 131 | b[j] = (b[j] + b[i] ) .% 2 132 | end 133 | end 134 | end 135 | 136 | # now make A diagonal 137 | for i = 1:n # iterate through columns 138 | for j = 1:i-1 # iterate through rows above i 139 | if A[j,i] == 1 140 | A[j,:] = (A[j,:] + A[i,:] ) .% 2 141 | b[j] = (b[j] + b[i] ) .% 2 142 | end 143 | end 144 | end 145 | end 146 | 147 | 148 | 149 | function find_representatives(H::Matrix{UInt8}, l::Vector{UInt8}, syndromes::Vector{UInt8}) 150 | H_stacked = [H ; transpose(l)] 151 | m = size(H_stacked,1) 152 | n = size(H_stacked,2) 153 | 154 | # goal: find linearly first k linaerly independent columns of H_stacked 155 | basis_size = 0 156 | M = zeros(UInt8, (m, m)) # reduced H in lower triangular form 157 | free_indices= zeros(Int32, m) # indices of non-fixed bits that form basis 158 | for i = 1:n 159 | v = copy(H_stacked[:,i]) 160 | for j = 1:m 161 | if v[j] == 1 162 | if free_indices[j] != 0 163 | v = (v .+ M[:,j]) .% 2 164 | else 165 | # add to M 166 | M[:,j] = v 167 | free_indices[j] = i 168 | basis_size += 1 169 | break 170 | end 171 | end 172 | end 173 | if basis_size == m 174 | break 175 | end 176 | end 177 | if basis_size != m 178 | error("Couldn't find full linearly independent basis set") 179 | end 180 | 181 | H_reduced = H_stacked[:,free_indices] 182 | y0 = vcat(syndromes, Vector{UInt8}([0])) 183 | y1 = vcat(syndromes, Vector{UInt8}([1])) 184 | _solve_mod2_lineq!(copy(H_reduced[:,1:m]), y0) 185 | _solve_mod2_lineq!(copy(H_reduced[:,1:m]), y1) 186 | x0 = zeros(UInt8, n) 187 | x1 = zeros(UInt8, n) 188 | x0[free_indices] = y0 189 | x1[free_indices] = y1 190 | return x0, x1 191 | 192 | end 193 | 194 | function gen_mle_table(H::Matrix{UInt8}, l::Vector{UInt8}, p::Vector{Float64}) 195 | m = size(H,1) 196 | n = size(H,2) 197 | 198 | table_size = 2^(m + 1) 199 | mle_table = zeros(table_size) 200 | temp_buffer = zeros(table_size) 201 | 202 | mle_table[1] = 1 203 | for i = 0:n-1 204 | # iterate through each fault 205 | idx = sum(H[:,i+1] .* (2 .^ (m-1:-1:0))) # error events triggered by fault 206 | idx = 2*idx + l[i+1] # add logical information to index 207 | # update rule 208 | for j = 0:table_size-1 209 | temp_buffer[j+1] = mle_table[j+1] * (1-p[i+1]) + mle_table[ (j⊻idx) + 1] * p[i+1] 210 | end 211 | for j = 0:table_size-1 212 | mle_table[j+1] = temp_buffer[j+1] 213 | end 214 | end 215 | return mle_table 216 | end 217 | function gen_mle_table(H::Matrix{UInt8}, l::Vector{UInt8}, p::Float64) 218 | return gen_mle_table(H, l, p*ones(Float64, length(l))) 219 | end 220 | 221 | function mle_table_lookup(H::Matrix{UInt8}, mle_table::Vector{Float64}, mmt_vector::Vector{UInt8}) 222 | m= size(H,1) 223 | n = size(H,2) 224 | 225 | data_as_num = sum( mmt_vector .* 2 .^ (m-1:-1:0) ) 226 | idx0 = data_as_num * 2 227 | idx1 = data_as_num * 2 + 1 228 | p0 = mle_table[idx0+1] 229 | p1 = mle_table[idx1+1] 230 | 231 | return p0, p1 232 | end 233 | 234 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /contraction_network.jl: -------------------------------------------------------------------------------- 1 | struct ContractionNetwork{P} 2 | tensors::Dict{P, ITensor} 3 | adj::Dict{P, Vector{P}} 4 | bond_matrices::Dict{Set{P}, ITensor} 5 | end 6 | 7 | Base.getindex(net::ContractionNetwork{P}, site::P) where P = net.tensors[site] 8 | neighbors(net::ContractionNetwork{P}, site::P) where P = net.adj[site] 9 | bond_matrix(net::ContractionNetwork{P}, site1::P, site2::P) where P = net.bond_matrices[Set([site1,site2])] 10 | function Base.setindex!(net::ContractionNetwork{P}, T::ITensor, site::P) where P 11 | net.tensors[site] = T 12 | end 13 | function open_indices(net::ContractionNetwork{P}, site::P)::Vector{Index} where P 14 | return setdiff(inds(net.tensors[site]), [inds(bond_matrix(net,site,site2)) for site2 in neighbors(net,site)]...) 15 | end 16 | 17 | function generate_empty_PEPS(xmin::Int, xmax::Int, ymin::Int, ymax::Int) 18 | tensors = Dict{Tuple{Int,Int},ITensor}() 19 | adj = Dict{Tuple{Int,Int}, Vector{Tuple{Int,Int}}}() 20 | bond_matrices = Dict{Set{Tuple{Int,Int}}, ITensor}() 21 | neighbors(x,y) = [(x2,y2) for (x2,y2) in [(x+1,y),(x-1,y),(x,y+1),(x,y-1)] if (xmin<=x2<=xmax && ymin<=y2<=ymax)] 22 | 23 | bonds = Dict{Tuple{Tuple{Int,Int},Tuple{Int,Int}}, Index}() 24 | for x = xmin:xmax for y = ymin:ymax 25 | adj[x,y] = neighbors(x,y) 26 | for (x2,y2) in adj[x,y] if (x2,y2) > (x,y) 27 | idx1 = Index(1) 28 | idx2 = Index(1) 29 | bonds[(x,y),(x2,y2)] = idx1 30 | bonds[(x2,y2),(x,y)] = idx2 31 | bond_matrices[Set([(x,y),(x2,y2)])] = ITensor([1.], idx1, idx2) 32 | end end 33 | end end 34 | for x = xmin:xmax for y = ymin:ymax 35 | tensors[x,y] = ITensor([1.], [bonds[(x,y),(x2,y2)] for (x2,y2) in adj[x,y]]..., Index(1)) 36 | end end 37 | 38 | return ContractionNetwork{Tuple{Int,Int}}(tensors, adj, bond_matrices) 39 | end 40 | 41 | function generate_empty_cubictn(xmin::Int, xmax::Int, ymin::Int, ymax::Int, zmin::Int, zmax::Int) 42 | tensors = Dict{Tuple{Int,Int,Int},ITensor}() 43 | adj = Dict{Tuple{Int,Int,Int}, Vector{Tuple{Int,Int,Int}}}() 44 | bond_matrices = Dict{Set{Tuple{Int,Int,Int}}, ITensor}() 45 | neighbors(x,y,z) = [(x2,y2,z2) for (x2,y2,z2) in [(x+1,y,z),(x-1,y,z),(x,y+1,z),(x,y-1,z),(x,y,z+1),(x,y,z-1)] if (xmin<=x2<=xmax && ymin<=y2<=ymax && zmin<=z2<=zmax)] 46 | 47 | bonds = Dict{Tuple{Tuple{Int,Int,Int},Tuple{Int,Int,Int}}, Index}() 48 | for x = xmin:xmax for y = ymin:ymax for z = zmin:zmax 49 | adj[x,y,z] = neighbors(x,y,z) 50 | for (x2,y2,z2) in adj[x,y,z] if (x2,y2,z2) > (x,y,z) 51 | idx1 = Index(1) 52 | idx2 = Index(1) 53 | bonds[(x,y,z),(x2,y2,z2)] = idx1 54 | bonds[(x2,y2,z2),(x,y,z)] = idx2 55 | bond_matrices[Set([(x,y,z),(x2,y2,z2)])] = ITensor([1.], idx1, idx2) 56 | end end 57 | end end end 58 | for x = xmin:xmax for y = ymin:ymax for z = zmin:zmax 59 | tensors[x,y,z] = ITensor([1.], [bonds[(x,y,z),(x2,y2,z2)] for (x2,y2,z2) in adj[x,y,z]]..., Index(1)) 60 | end end end 61 | 62 | return ContractionNetwork{Tuple{Int,Int,Int}}(tensors, adj, bond_matrices) 63 | end 64 | 65 | 66 | # general note: contraction code is written with assumption in head that the number and dimension of open indices can 67 | # vary for different sites 68 | 69 | # contract 1-qubit gate 70 | function contract1!(net::ContractionNetwork{P}, site::P, T::ITensor) where P 71 | #@assert length(commoninds(net[site], T)) > 0 72 | net[site] *= T 73 | end 74 | 75 | # contract 2-qubit gate 76 | function contract2!(net::ContractionNetwork{P}, site1::P, site2::P, T::ITensor, indices1_out::Vector{Index}, max_bond_dim::Int, svd_eps::Float64=1e-16) where P 77 | @assert length(commoninds(net[site1], T)) > 0 78 | @assert length(commoninds(net[site2], T)) > 0 79 | @assert site1 in neighbors(net, site2) && site2 in neighbors(net, site1) 80 | 81 | # contract environment tensors 82 | T1 = net[site1] 83 | T2 = net[site2] 84 | 85 | # QR decomposition 86 | Q1, R1 = qr(T1, [commonind(T1, bond_matrix(net, site1, site3)) for site3 in neighbors(net, site1) if site3 != site2]...) 87 | Q2, R2 = qr(T2, [commonind(T2, bond_matrix(net, site2, site3)) for site3 in neighbors(net, site2) if site3 != site1]...) 88 | 89 | # contract gate 90 | theta = R1 * bond_matrix(net, site1, site2) * R2 * T 91 | 92 | # SVD and truncate 93 | U, S, V = svd(theta, commonind(Q1,R1), open_indices(net, site1)..., indices1_out..., maxdim=max_bond_dim) 94 | 95 | # store updated tensors 96 | net[site1] = U * Q1 97 | net[site2] = V * Q2 98 | net.bond_matrices[Set([site1,site2])] = S 99 | 100 | return 0 101 | end 102 | 103 | function diag_inv(A::Matrix) 104 | @assert size(A,1) == size(A,2) 105 | d = size(A,1) 106 | B = copy(A) 107 | for i = 1:d 108 | B[i,i] = inv(A[i,i]) 109 | end 110 | return B 111 | end 112 | 113 | 114 | # contract 2-qubit gate 115 | function contract2_SU!(net::ContractionNetwork{P}, site1::P, site2::P, T::ITensor, indices1_out::Vector{Index}, max_bond_dim::Int, svd_eps::Float64=1e-16) where P 116 | @assert length(commoninds(net[site1], T)) > 0 117 | @assert length(commoninds(net[site2], T)) > 0 118 | @assert site1 in neighbors(net, site2) && site2 in neighbors(net, site1) 119 | 120 | # contract environment tensors 121 | T1 = net[site1] 122 | T2 = net[site2] 123 | for site3 in neighbors(net, site1) if site3 != site2 124 | T1 *= bond_matrix(net, site1, site3) 125 | end end 126 | for site3 in neighbors(net, site2) if site3 != site1 127 | T2 *= bond_matrix(net, site2, site3) 128 | end end 129 | expval = 0 130 | h = Int(floor(log2(LinearAlgebra.norm( T1 )))) 131 | T1 /= exp2(h) 132 | expval += h 133 | h = Int(floor(log2(LinearAlgebra.norm( T2 )))) 134 | T2 /= exp2(h) 135 | expval += h 136 | 137 | # QR decomposition 138 | Q1, R1 = qr(T1, [commonind(T1, net[site3]) for site3 in neighbors(net, site1) if site3 != site2]...) 139 | Q2, R2 = qr(T2, [commonind(T2, net[site3]) for site3 in neighbors(net, site2) if site3 != site1]...) 140 | 141 | # contract gate 142 | theta = R1 * bond_matrix(net, site1, site2) * R2 * T 143 | 144 | # SVD and truncate 145 | U, S, V = svd(theta, commonind(Q1,R1), open_indices(net, site1)..., indices1_out..., maxdim=max_bond_dim, cutoff=svd_eps) 146 | 147 | # extract environment again 148 | U = U * Q1 149 | V = V * Q2 150 | for site3 in neighbors(net, site1) if site3 != site2 151 | bondmat_inds = inds(bond_matrix(net, site1, site3)) 152 | bondmat_inv = ITensor( diag_inv(Array(bond_matrix(net, site1, site3), bondmat_inds...)), bondmat_inds...) 153 | U *= bondmat_inv 154 | end end 155 | for site3 in neighbors(net, site2) if site3 != site1 156 | bondmat_inds = inds(bond_matrix(net, site2, site3)) 157 | bondmat_inv = ITensor( diag_inv(Array(bond_matrix(net, site2, site3), bondmat_inds...)), bondmat_inds...) 158 | V *= bondmat_inv 159 | end end 160 | 161 | # store updated tensors 162 | net[site1] = U 163 | net[site2] = V 164 | net.bond_matrices[Set([site1,site2])] = S 165 | 166 | return expval 167 | end 168 | 169 | 170 | function identity_SU!(net::ContractionNetwork{P}, site1::P, site2::P, max_bond_dim::Int, svd_eps::Float64=1e-16) where P 171 | @assert site1 in neighbors(net, site2) && site2 in neighbors(net, site1) 172 | expval = 0 173 | 174 | # contract environment tensors 175 | T1 = net[site1] 176 | T2 = net[site2] 177 | for site3 in neighbors(net, site1) if site3 != site2 178 | T1 *= bond_matrix(net, site1, site3) 179 | end end 180 | for site3 in neighbors(net, site2) if site3 != site1 181 | T2 *= bond_matrix(net, site2, site3) 182 | end end 183 | expval = 0 184 | h = Int(floor(log2(LinearAlgebra.norm( T1 )))) 185 | T1 /= exp2(h) 186 | expval += h 187 | h = Int(floor(log2(LinearAlgebra.norm( T2 )))) 188 | T2 /= exp2(h) 189 | expval += h 190 | 191 | # QR decomposition 192 | Q1, R1 = qr(T1, open_indices(net, site1)..., [commonind(T1, net[site3]) for site3 in neighbors(net, site1) if site3 != site2]...) 193 | Q2, R2 = qr(T2, open_indices(net, site2)..., [commonind(T2, net[site3]) for site3 in neighbors(net, site2) if site3 != site1]...) 194 | 195 | # contract gate 196 | theta = R1 * bond_matrix(net, site1, site2) * R2 197 | 198 | # SVD and truncate 199 | U, S, V = svd(theta, commonind(Q1,R1), maxdim=max_bond_dim, cutoff=svd_eps) 200 | 201 | # extract environment again 202 | U = U * Q1 203 | V = V * Q2 204 | for site3 in neighbors(net, site1) if site3 != site2 205 | bondmat_inds = inds(bond_matrix(net, site1, site3)) 206 | bondmat_inv = ITensor( diag_inv(Array(bond_matrix(net, site1, site3), bondmat_inds...)), bondmat_inds...) 207 | U *= bondmat_inv 208 | end end 209 | for site3 in neighbors(net, site2) if site3 != site1 210 | bondmat_inds = inds(bond_matrix(net, site2, site3)) 211 | bondmat_inv = ITensor( diag_inv(Array(bond_matrix(net, site2, site3), bondmat_inds...)), bondmat_inds...) 212 | V *= bondmat_inv 213 | end end 214 | 215 | # store updated tensors 216 | net[site1] = U 217 | net[site2] = V 218 | net.bond_matrices[Set([site1,site2])] = S 219 | 220 | return expval 221 | end 222 | 223 | function renormalize_tensor!(net::ContractionNetwork{P}, site::P) where P 224 | h = Int(floor(log2(LinearAlgebra.norm( net[site] )))) 225 | net[site] /= exp2(h) 226 | return h 227 | end 228 | 229 | function renormalize_bond_matrix!(net::ContractionNetwork{P}, site1::P, site2::P) where P 230 | h = Int(floor(log2(LinearAlgebra.norm( net.bond_matrices[Set([site1,site2])] )))) 231 | net.bond_matrices[Set([site1,site2])] /= exp2(h) 232 | return h 233 | end 234 | 235 | function elementwise_sqrt(T::ITensor) 236 | indices = inds(T) 237 | @assert length(indices) == 2 238 | arr = Matrix(T, indices[1], indices[2]) 239 | return ITensor( sqrt.(arr), indices[1], indices[2] ) 240 | end 241 | function Base.sqrt(T::ITensor) 242 | indices = inds(T) 243 | @assert length(indices) == 2 244 | arr = Matrix(T, indices[1], indices[2]) 245 | return ITensor( sqrt(arr), indices[1], indices[2] ) 246 | end 247 | 248 | # returns a measure of canonicalness, i.e. how close we are to vidal gauge. 249 | function canonicalness(net::ContractionNetwork{P}) where P 250 | f = 0.0 251 | 252 | for (site1,v1) in net.tensors 253 | for site2 in neighbors(net, site1) 254 | 255 | # compute isometry 256 | iso = net[site1] 257 | for site3 in neighbors(net, site1) if site3 != site2 258 | iso *= bond_matrix(net, site1, site3) 259 | end end 260 | # compute contraction of isometry with itself 261 | idx = commonind(iso, bond_matrix(net,site1,site2)) 262 | idx2 = Index(dim(idx)) 263 | iso_prime = iso * delta(idx, idx2) 264 | T = iso * iso_prime 265 | 266 | # compare with identity 267 | LHS = T / sum(diag(T)) 268 | RHS = dense(delta(idx, idx2)) 269 | RHS /= sum(diag(RHS)) 270 | f += 0.5 * norm( LHS - RHS ) 271 | end 272 | 273 | end 274 | 275 | return f / length(net.bond_matrices) 276 | end 277 | -------------------------------------------------------------------------------- /contract_circ.jl: -------------------------------------------------------------------------------- 1 | function gen_tn_sc3d_circ(dem::DEM{3}, max_bond_dim::Int; svd_eps::Float64=1e-16, do_gauging::Bool=false, canonicalness_target::Float64=1e-4) 2 | n_detectors = size(dem.H, 1) 3 | 4 | # the X and Y positions in stim take steps of 2 5 | pos_checks = [convert(Tuple{Int,Int,Int}, round.((0.5*pos[1], 0.5*pos[2], pos[3]))) for pos in dem.pos_checks] 6 | xmin,xmax = extrema([pos[1] for pos in pos_checks]) 7 | ymin,ymax = extrema([pos[2] for pos in pos_checks]) 8 | zmin,zmax = extrema([pos[3] for pos in pos_checks]) 9 | println(dem.n_bits, " ", n_detectors) 10 | println("$xmin:$xmax $ymin:$ymax $zmin:$zmax") 11 | 12 | # build empty 3D tensor network 13 | tn3d = generate_empty_cubictn(xmin, xmax, ymin, ymax, zmin, zmax) 14 | for j = 1:n_detectors 15 | idx = Index(2) 16 | pos = pos_checks[j] 17 | @assert length(open_indices(tn3d,pos)) == 1 18 | T = ITensor([1., 0.], idx, open_indices(tn3d,pos)[1]) 19 | contract1!(tn3d, pos, T) 20 | end 21 | open_index = Dict{Pos3D,Index}() 22 | for x=xmin:xmax for y=ymin:ymax for z=zmin:zmax 23 | @assert length(open_indices(tn3d,(x,y,z))) == 1 24 | open_index[x,y,z] = open_indices(tn3d,(x,y,z))[1] 25 | end end end 26 | 27 | expval = 0 28 | # add faults 29 | for i = 1:dem.n_bits 30 | println("$i / ",dem.n_bits) 31 | affected_dets = [j for j=1:n_detectors if dem.H[j,i]==1] 32 | n_dets = length(affected_dets) 33 | 34 | if (dem.L[i]==0) 35 | val0,val1 = (1. - dem.p[i], dem.p[i]) 36 | else 37 | # Hadamard trick 38 | val0,val1 = (1. - dem.p[i], -dem.p[i]) 39 | end 40 | 41 | if n_dets == 1 # single-site hyperedge 42 | idx_middle = Index(2) 43 | idx_out = Index(2) 44 | pos = pos_checks[affected_dets[1]] 45 | T = ITensor([val0,val1], idx_middle) 46 | T *= ITensor(CHECK3, open_index[pos], idx_middle, idx_out) 47 | contract1!(tn3d, pos, T) 48 | open_index[pos] = idx_out 49 | else 50 | @assert n_dets in [2,3,4] 51 | # keep track of "connected" components inside affected detectors 52 | components = Vector(1:n_dets) 53 | # store the resulting bonds 54 | bonds = Set{Set{Pos3D}}() # set of bonds, where each bond is a set of 2 sites 55 | # check out which detectors are neighbors 56 | for i = 1:n_dets 57 | posi = pos_checks[affected_dets[i]] 58 | for j = i+1:n_dets 59 | posj = pos_checks[affected_dets[j]] 60 | if components[i]!=components[j] && sum(abs.(posi .- posj)) < 1.5 61 | # i and j are in the same connected component 62 | replace!(components, components[j]=>components[i]) 63 | push!(bonds, Set([posi,posj])) 64 | end 65 | end 66 | end 67 | 68 | function bonds_between(pos1::Pos3D, pos2::Pos3D) 69 | dx = sign(pos2[1]-pos1[1]) 70 | dy = sign(pos2[2]-pos1[2]) 71 | dz = sign(pos2[3]-pos1[3]) 72 | 73 | path_bonds = Vector{Set{Pos3D}}() 74 | curr = pos1 75 | while curr != pos2 76 | if curr[1]!=pos2[1] 77 | next = curr .+ (dx,0,0) 78 | elseif curr[2]!=pos2[2] 79 | next = curr .+ (0,dy,0) 80 | else 81 | next = curr .+ (0,0,dz) 82 | end 83 | push!(path_bonds, Set([curr,next])) 84 | curr = next 85 | if length(bonds) > 100 86 | error("stuck in loop") 87 | end 88 | end 89 | return path_bonds 90 | end 91 | function closest_distance(comp1::Int, comp2::Int) 92 | @assert comp1 != comp2 93 | mindist = 99999999 94 | mindist_bonds = Vector{Set{Pos3D}}() 95 | for i = 1:n_dets if components[i] == comp1 96 | posi = pos_checks[affected_dets[i]] 97 | for j = 1:n_dets if components[j] == comp2 98 | posj = pos_checks[affected_dets[j]] 99 | d = sum(abs.(posi .- posj)) 100 | if d < mindist 101 | mindist = d 102 | mindist_bonds = bonds_between(posi, posj) 103 | end 104 | end end 105 | end end 106 | return mindist, mindist_bonds 107 | end 108 | # while there are more than one connected component, find the smallest distances 109 | # between the component, then merge the two closest ones 110 | while length(unique(components)) > 1 111 | unique_comps = unique(components) 112 | combinations = [(c1,c2) for c1 in unique_comps for c2 in unique_comps if c2!=c1] 113 | dist = [closest_distance(c1,c2) for (c1,c2) in combinations] 114 | idx = argmin([val[1] for val in dist]) 115 | c1,c2 = combinations[idx] 116 | replace!(components, c2=>c1) 117 | bonds = union(bonds, Set(dist[idx][2])) 118 | end 119 | 120 | # figure out which sites are used for "bridging" 121 | main_sites = [pos_checks[j] for j in affected_dets] 122 | bridging_sites = setdiff(union(bonds...), main_sites) 123 | 124 | # open up sites 125 | temp_open_index = Dict{Pos3D,Index}() 126 | for pos in main_sites 127 | idx_new = Index(2) 128 | idx_middle = Index(2) 129 | idx_temp = Index(2) 130 | T = ITensor(CHECK3, open_index[pos], idx_middle, idx_new) 131 | arr = (pos == main_sites[1]) ? [val0 0. ; 0. val1] : [1. 0. ; 0. 1.] 132 | T *= ITensor(arr, idx_middle, idx_temp) 133 | contract1!(tn3d, pos, T) 134 | open_index[pos] = idx_new 135 | temp_open_index[pos] = idx_temp 136 | end 137 | for pos in bridging_sites 138 | idx_temp = Index(2) 139 | T = ITensor([1., 1.], idx_temp) 140 | contract1!(tn3d, pos, T) 141 | temp_open_index[pos] = idx_temp 142 | end 143 | 144 | # apply 2-qubit gates 145 | for b in bonds 146 | bvec = collect(b) 147 | @assert length(bvec) == 2 148 | pos1,pos2 = (bvec[1], bvec[2]) 149 | idx1 = Index(2) 150 | idx2 = Index(2) 151 | idxmid = Index(2) 152 | T = ITensor(DELTA3, temp_open_index[pos1], idxmid, idx1) 153 | T *= ITensor(DELTA3, temp_open_index[pos2], idxmid, idx2) 154 | expval += contract2_SU!(tn3d, pos1, pos2, T, Vector{Index}([idx1]), max_bond_dim, svd_eps) 155 | expval += renormalize_tensor!(tn3d, pos1) 156 | expval += renormalize_tensor!(tn3d, pos2) 157 | expval += renormalize_bond_matrix!(tn3d, pos1, pos2) 158 | temp_open_index[pos1] = idx1 159 | temp_open_index[pos2] = idx2 160 | end 161 | 162 | # close sites 163 | for pos in main_sites 164 | T = ITensor([1.,1.], temp_open_index[pos]) 165 | contract1!(tn3d, pos, T) 166 | end 167 | for pos in bridging_sites 168 | T = ITensor([1.,1.], temp_open_index[pos]) 169 | contract1!(tn3d, pos, T) 170 | end 171 | 172 | # gauging step 173 | if do_gauging 174 | while canonicalness(tn3d) > canonicalness_target 175 | for x=xmin:xmax for y=ymin:ymax for z=zmin:zmax 176 | for pos2 in neighbors(tn3d,(x,y,z)) if pos2>(x,y,z) 177 | if dim(commonind( tn3d[(x,y,z)], bond_matrix(tn3d, (x,y,z),pos2) )) > 1 178 | expval += identity_SU!(tn3d, (x,y,z), pos2, max_bond_dim, svd_eps) 179 | expval += renormalize_tensor!(tn3d, (x,y,z)) 180 | expval += renormalize_tensor!(tn3d, pos2) 181 | expval += renormalize_bond_matrix!(tn3d, (x,y,z), pos2) 182 | end 183 | end end 184 | end end end 185 | end 186 | end 187 | end 188 | end 189 | 190 | return tn3d, expval, open_index 191 | end 192 | 193 | function contract_cubictn(tn::ContractionNetwork, max_bond_dim::Int, sc_bond_dim::Int ; svd_eps::Float64=1e-16, do_gauging::Bool=false, canonicalness_threshold::Float64=1e-4, split_dim::Int=2, ignore_bond_tensors::Bool=false) 194 | xmin,xmax = extrema([k[1] for (k,v) in tn.tensors]) 195 | ymin,ymax = extrema([k[2] for (k,v) in tn.tensors]) 196 | zmin,zmax = extrema([k[3] for (k,v) in tn.tensors]) 197 | 198 | # split up the bond tensors and put them onto sites 199 | if !ignore_bond_tensors 200 | for x=xmin:xmax for y=ymin:ymax for z=zmin:zmax 201 | pos1 = (x,y,z) 202 | for pos2 in neighbors(tn, pos1) if pos2 > pos1 203 | idx1 = commonind(tn[pos1], bond_matrix(tn, pos1, pos2)) 204 | idx2 = commonind(tn[pos2], bond_matrix(tn, pos1, pos2)) 205 | idxnew = Index(dim(idx1)) 206 | arr = sqrt.(Matrix(bond_matrix(tn,pos1,pos2), idx1, idx2)) 207 | tn[pos1] *= ITensor(arr, idx1, idxnew) 208 | tn[pos2] *= ITensor(arr, idx2, idxnew) 209 | end end 210 | end end end 211 | end 212 | 213 | # set up PEPS 214 | peps = generate_empty_PEPS(xmin, xmax, ymin, ymax) 215 | open_edge = Dict{Tuple{Int,Int},Index}() 216 | for x=xmin:xmax for y=ymin:ymax 217 | open_edge[x,y] = open_indices(peps, (x,y))[1] 218 | tn[(x,y,zmin)] *= ITensor([1.], open_edge[x,y]) 219 | end end 220 | 221 | # contract 222 | expval = 0 223 | for z=zmin:zmax 224 | # contract two site bonds on same level 225 | for x=xmin:xmax for y=ymin:ymax 226 | for (x2,y2) in neighbors(peps, (x,y)) if (x2,y2) > (x,y) 227 | downidx1 = commonind(peps[(x,y)], tn[(x,y,z)]) 228 | downidx2 = commonind(peps[(x2,y2)], tn[(x2,y2,z)]) 229 | link = commonind(tn[(x,y,z)], tn[(x2,y2,z)]) 230 | 231 | U1,S1,V1 = svd(tn[(x,y,z)], [idx for idx in [downidx1, link] if !isnothing(idx)], maxdim=split_dim) 232 | U2,S2,V2 = svd(tn[(x2,y2,z)], [idx for idx in [downidx2, link] if !isnothing(idx)], maxdim=split_dim) 233 | #U1,S1,V1 = svd(tn[(x,y,z)], [idx for idx in [downidx1, link] if !isnothing(idx)], maxdim=max_bond_dim) 234 | #U2,S2,V2 = svd(tn[(x2,y2,z)], [idx for idx in [downidx2, link] if !isnothing(idx)], maxdim=max_bond_dim) 235 | upidx1 = commonind(U1,S1) 236 | upidx2 = commonind(U2,S2) 237 | tn[(x,y,z)] = S1*V1 238 | tn[(x2,y2,z)] = S2*V2 239 | 240 | expval += renormalize_tensor!(tn, (x,y,z)) 241 | expval += renormalize_tensor!(tn, (x2,y2,z)) 242 | h = Int(floor(log2(LinearAlgebra.norm( U1 )))) 243 | U1 /= exp2(h) 244 | expval += h 245 | h = Int(floor(log2(LinearAlgebra.norm( U2 )))) 246 | U2 /= exp2(h) 247 | expval += h 248 | expval += contract2_SU!(peps, (x,y), (x2,y2), U1*U2, Vector{Index}([upidx1]), max_bond_dim, svd_eps) 249 | expval += renormalize_tensor!(peps, (x,y)) 250 | expval += renormalize_tensor!(peps, (x2,y2)) 251 | expval += renormalize_bond_matrix!(peps, (x,y), (x2,y2)) 252 | open_edge[x,y] = upidx1 253 | open_edge[x2,y2] = upidx2 254 | end end 255 | end end 256 | 257 | # contract the final remaining R into the peps 258 | for x=xmin:xmax for y=ymin:ymax 259 | peps[(x,y)] *= tn[(x,y,z)] 260 | expval += renormalize_tensor!(peps, (x,y)) 261 | if z < zmax 262 | @assert length(open_indices(peps, (x,y))) == 1 263 | open_edge[x,y] = open_indices(peps, (x,y))[1] 264 | end 265 | end end 266 | 267 | # gauge 268 | if z < zmax && do_gauging 269 | n_steps = 0 270 | while canonicalness(peps) > canonicalness_threshold && n_steps < 100 271 | for x=xmin:xmax for y=ymin:ymax 272 | for (x2,y2) in neighbors(peps, (x,y)) if (x2,y2) > (x,y) 273 | expval += identity_SU!(peps, (x,y), (x2,y2), max_bond_dim, svd_eps) 274 | expval += renormalize_tensor!(peps, (x,y)) 275 | expval += renormalize_tensor!(peps, (x2,y2)) 276 | expval += renormalize_bond_matrix!(peps, (x,y), (x2,y2)) 277 | end end 278 | end end 279 | end 280 | end 281 | end 282 | 283 | # contract final PEPS with sweep contractor 284 | res = contract_PEPS(peps, sc_bond_dim, svd_eps) 285 | return (res[1], res[2]+expval) 286 | end 287 | -------------------------------------------------------------------------------- /contract_bitflip.jl: -------------------------------------------------------------------------------- 1 | const Pos3D = Tuple{Int,Int,Int} 2 | 3 | struct CubicTNTensorDescription 4 | type::TensorType 5 | val::Tuple{Float64,Float64} 6 | end 7 | struct CubicTN 8 | xmin::Int 9 | xmax::Int 10 | ymin::Int 11 | ymax::Int 12 | zmin::Int 13 | zmax::Int 14 | 15 | sites::Dict{Pos3D,CubicTNTensorDescription} 16 | one_site_bonds::Dict{Pos3D,CubicTNTensorDescription} 17 | two_site_bonds::Dict{Set{Pos3D},CubicTNTensorDescription} 18 | end 19 | 20 | function gen_tn_sc3d_phenomenological(sc3d::CSSCode{3}, d::Int, err_rate::Float64, syndrome::Vector{UInt8}) 21 | # Z-type stabilizers are the star ones (i.e. weight 6) 22 | # therefore we want to do the DEM picture of the Z sector 23 | num_Z_stabs = size(sc3d.Hz, 1) 24 | 25 | # find the dimensions of our qubic lattice 26 | pos_stab_z = [convert(Tuple{Int,Int,Int}, round.(0.5.*pos)) for pos in sc3d.pos_stab_z] 27 | pos_qubits = [0.5.*pos for pos in sc3d.pos_qubits] 28 | xmin, xmax = extrema([pos[1] for pos in pos_stab_z]) 29 | ymin, ymax = extrema([pos[2] for pos in pos_stab_z]) 30 | zmin, zmax = extrema([pos[3] for pos in pos_stab_z]) 31 | 32 | # iterate through all Z stabs 33 | sites = Dict{Pos3D,CubicTNTensorDescription}() 34 | for i = 1:num_Z_stabs 35 | val = (syndrome[i]==1) ? (0.,1.) : (1.,0.) 36 | sites[pos_stab_z[i]] = CubicTNTensorDescription(TT_CHECK, val) 37 | end 38 | 39 | # iterate through all qubits 40 | one_site_bonds0 = Dict{Pos3D,CubicTNTensorDescription}() 41 | one_site_bonds1 = Dict{Pos3D,CubicTNTensorDescription}() 42 | two_site_bonds0 = Dict{Set{Pos3D},CubicTNTensorDescription}() 43 | two_site_bonds1 = Dict{Set{Pos3D},CubicTNTensorDescription}() 44 | for i = 1:sc3d.n_qubits 45 | val0 = (1-err_rate, err_rate) 46 | val1 = (sc3d.Lz[i]==0) ? (1-err_rate, err_rate) : (1-err_rate, -err_rate) 47 | pos = pos_qubits[i] 48 | @assert sum(isinteger.(pos)) == 2 49 | nbors = [convert(Tuple{Int,Int,Int}, floor.(pos)), convert(Tuple{Int,Int,Int}, ceil.(pos))] 50 | nbors = [p for p in nbors if xmin<=p[1]<=xmax && ymin<=p[2]<=ymax && zmin<=p[3]<=zmax] 51 | if length(nbors) == 1 52 | @assert !(nbors[1] in keys(one_site_bonds0)) 53 | one_site_bonds0[nbors[1]] = CubicTNTensorDescription(TT_DELTA, val0) 54 | one_site_bonds1[nbors[1]] = CubicTNTensorDescription(TT_DELTA, val1) 55 | else 56 | key = Set([nbors[1], nbors[2]]) 57 | @assert !(key in keys(two_site_bonds0)) 58 | two_site_bonds0[key] = CubicTNTensorDescription(TT_DELTA, val0) 59 | two_site_bonds1[key] = CubicTNTensorDescription(TT_DELTA, val1) 60 | end 61 | end 62 | 63 | ctn0 = CubicTN(xmin, xmax, ymin, ymax, zmin, zmax, sites, one_site_bonds0, two_site_bonds0) 64 | ctn1 = CubicTN(xmin, xmax, ymin, ymax, zmin, zmax, sites, one_site_bonds1, two_site_bonds1) 65 | return ctn0, ctn1 66 | end 67 | 68 | 69 | function gen_tn_sc3d_dual(sc3d::CSSCode{3}, d::Int, err_rate::Float64, representative0::Vector{UInt8}, representative1::Vector{UInt8}) 70 | # Z-type stabilizers are the star ones (i.e. weight 6) 71 | # therefore we want to do the gauge picture of the X sector 72 | num_Z_stabs = size(sc3d.Hz, 1) 73 | 74 | # find the dimensions of our qubic lattice 75 | pos_stab_z = [convert(Tuple{Int,Int,Int}, round.(0.5.*pos)) for pos in sc3d.pos_stab_z] 76 | pos_qubits = [0.5.*pos for pos in sc3d.pos_qubits] 77 | xmin, xmax = extrema([pos[1] for pos in pos_stab_z]) 78 | ymin, ymax = extrema([pos[2] for pos in pos_stab_z]) 79 | zmin, zmax = extrema([pos[3] for pos in pos_stab_z]) 80 | 81 | # iterate through all Z stabs 82 | sites = Dict{Pos3D,CubicTNTensorDescription}() 83 | for i = 1:num_Z_stabs 84 | sites[pos_stab_z[i]] = CubicTNTensorDescription(TT_DELTA, (1.,1.)) 85 | end 86 | 87 | # iterate through all qubits 88 | one_site_bonds0 = Dict{Pos3D,CubicTNTensorDescription}() 89 | one_site_bonds1 = Dict{Pos3D,CubicTNTensorDescription}() 90 | two_site_bonds0 = Dict{Set{Pos3D},CubicTNTensorDescription}() 91 | two_site_bonds1 = Dict{Set{Pos3D},CubicTNTensorDescription}() 92 | for i = 1:sc3d.n_qubits 93 | val0 = (representative0[i]==0) ? (1-err_rate, err_rate) : (err_rate, 1-err_rate) 94 | val1 = (representative1[i]==0) ? (1-err_rate, err_rate) : (err_rate, 1-err_rate) 95 | pos = pos_qubits[i] 96 | @assert sum(isinteger.(pos)) == 2 97 | nbors = [convert(Tuple{Int,Int,Int}, floor.(pos)), convert(Tuple{Int,Int,Int}, ceil.(pos))] 98 | nbors = [p for p in nbors if xmin<=p[1]<=xmax && ymin<=p[2]<=ymax && zmin<=p[3]<=zmax] 99 | if length(nbors) == 1 100 | @assert !(nbors[1] in keys(one_site_bonds0)) 101 | one_site_bonds0[nbors[1]] = CubicTNTensorDescription(TT_CHECK, val0) 102 | one_site_bonds1[nbors[1]] = CubicTNTensorDescription(TT_CHECK, val1) 103 | else 104 | key = Set([nbors[1], nbors[2]]) 105 | @assert !(key in keys(two_site_bonds0)) 106 | two_site_bonds0[key] = CubicTNTensorDescription(TT_CHECK, val0) 107 | two_site_bonds1[key] = CubicTNTensorDescription(TT_CHECK, val1) 108 | end 109 | end 110 | 111 | ctn0 = CubicTN(xmin, xmax, ymin, ymax, zmin, zmax, sites, one_site_bonds0, two_site_bonds0) 112 | ctn1 = CubicTN(xmin, xmax, ymin, ymax, zmin, zmax, sites, one_site_bonds1, two_site_bonds1) 113 | return ctn0, ctn1 114 | end 115 | 116 | function contract_exact(ctn::CubicTN) 117 | xmin = ctn.xmin 118 | xmax = ctn.xmax 119 | ymin = ctn.ymin 120 | ymax = ctn.ymax 121 | zmin = ctn.zmin 122 | zmax = ctn.zmax 123 | neighbors(x,y,z) = [(x2,y2,z2) for (x2,y2,z2) in [(x+1,y,z),(x-1,y,z),(x,y+1,z),(x,y-1,z),(x,y,z+1),(x,y,z-1)] if (xmin<=x2<=xmax && ymin<=y2<=ymax && zmin<=z2<=zmax)] 124 | 125 | # generate indices 126 | indices = Dict{Set{Pos3D},Index}() 127 | for x=xmin:xmax for y=ymin:ymax for z=zmin:zmax 128 | for pos2 in neighbors(x,y,z) if pos2 > (x,y,z) 129 | indices[Set([(x,y,z), pos2])] = Index(2) 130 | end end 131 | end end end 132 | 133 | # build 3D TN 134 | tn3d = Dict{Pos3D,ITensor}() 135 | for x=xmin:xmax for y=ymin:ymax for z=zmin:zmax 136 | # create site 137 | t = ctn.sites[x,y,z] 138 | i = [indices[Set([(x,y,z),pos2])] for pos2 in neighbors(x,y,z)] 139 | if (x,y,z) in keys(ctn.one_site_bonds) 140 | osb_idx = Index(2) 141 | push!(i, osb_idx) 142 | end 143 | n_nbors = length(i) 144 | arr = (t.type == TT_DELTA) ? delta_arr(n_nbors, t.val[1], t.val[2]) : check_arr(n_nbors, t.val[1], t.val[2]) 145 | T = ITensor(arr, i...) 146 | 147 | # contract one-site bond 148 | if (x,y,z) in keys(ctn.one_site_bonds) 149 | tosb = ctn.one_site_bonds[x,y,z] 150 | T *= ITensor([tosb.val[1], tosb.val[2]], osb_idx) 151 | end 152 | 153 | # contract two-site bonds 154 | i = Index(2) 155 | for (x2,y2,z2) in neighbors(x,y,z) if (x2,y2,z2) > (x,y,z) 156 | tbond = ctn.two_site_bonds[Set([(x,y,z), (x2,y2,z2)])] 157 | arrbond = (tbond.type == TT_DELTA) ? delta_arr(2, tbond.val[1], tbond.val[2]) : check_arr(2, tbond.val[1], tbond.val[2]) 158 | T *= ITensor(arrbond, i, indices[Set([(x,y,z),(x2,y2,z2)])]) 159 | T *= delta(i, indices[Set([(x,y,z),(x2,y2,z2)])]) 160 | end end 161 | 162 | tn3d[(x,y,z)] = T 163 | end end end 164 | 165 | # contract 3D TN 166 | T = ITensor([1.]) 167 | expval = 0 168 | for x=xmin:xmax for y=ymin:ymax for z=zmin:zmax 169 | T *= tn3d[(x,y,z)] 170 | h = Int(floor(log2(LinearAlgebra.norm( T )))) 171 | T /= exp2(h) 172 | expval += h 173 | end end end 174 | 175 | return (storage(T)[1], expval) 176 | end 177 | 178 | 179 | function contract(ctn::CubicTN, max_bond_dim::Int, sc_bond_dim::Int, svd_eps::Float64=1e-16, do_gauging::Bool=false, canonicalness_threshold::Float64=1e-4 ) 180 | xmin = ctn.xmin 181 | xmax = ctn.xmax 182 | ymin = ctn.ymin 183 | ymax = ctn.ymax 184 | zmin = ctn.zmin 185 | zmax = ctn.zmax 186 | 187 | # set up PEPS 188 | peps = generate_empty_PEPS(xmin, xmax, ymin, ymax) 189 | open_edge = Dict{Tuple{Int,Int},Index}() 190 | for x=xmin:xmax for y=ymin:ymax 191 | open_edge[x,y] = open_indices(peps, (x,y))[1] 192 | end end 193 | 194 | # contract 195 | expval = 0 196 | for z=zmin:zmax 197 | # contract value tensor on each site 198 | for x=xmin:xmax for y=ymin:ymax 199 | t = ctn.sites[x,y,z] 200 | if z == zmin 201 | arr = [t.val[1], t.val[2]] 202 | else 203 | if t.type == TT_DELTA 204 | arr = [t.val[1] 0 ; 0 t.val[2]] 205 | else 206 | arr = [t.val[1] t.val[2] ; t.val[2] t.val[1]] 207 | end 208 | end 209 | idx = Index(2) 210 | contract1!(peps, (x,y), ITensor(arr, open_edge[x,y], idx)) 211 | open_edge[x,y] = idx 212 | end end 213 | 214 | # contract one site bonds 215 | for x=xmin:xmax for y=ymin:ymax 216 | if (x,y,z) in keys(ctn.one_site_bonds) 217 | t = ctn.one_site_bonds[x,y,z] 218 | idx_out = Index(2) 219 | idx_bond = Index(2) 220 | arr = (ctn.sites[x,y,z].type == TT_DELTA) ? DELTA3 : CHECK3 221 | T = ITensor(arr, open_edge[x,y], idx_out, idx_bond) 222 | T *= ITensor([t.val[1], t.val[2]], idx_bond) 223 | contract1!(peps, (x,y), T) 224 | open_edge[x,y] = idx_out 225 | end 226 | end end 227 | 228 | # contract two site bonds on same level 229 | for x=xmin:xmax for y=ymin:ymax 230 | for (x2,y2) in neighbors(peps, (x,y)) if (x2,y2) > (x,y) 231 | t1 = ctn.sites[x,y,z] 232 | t2 = ctn.sites[x2,y2,z] 233 | tbond = ctn.two_site_bonds[Set([(x,y,z), (x2,y2,z)])] 234 | 235 | if tbond.type == TT_DELTA 236 | arrbond = [tbond.val[1] 0 ; 0 tbond.val[2]] 237 | else 238 | arrbond = [tbond.val[1] tbond.val[2] ; tbond.val[2] tbond.val[1]] 239 | end 240 | arr1 = (t1.type==TT_DELTA) ? DELTA3 : CHECK3 241 | arr2 = (t2.type==TT_DELTA) ? DELTA3 : CHECK3 242 | 243 | idx1_out = Index(2) 244 | idx2_out = Index(2) 245 | idx1_bond = Index(2) 246 | idx2_bond = Index(2) 247 | T = ITensor(arr1, open_edge[x,y], idx1_out, idx1_bond) 248 | T *= ITensor(arrbond, idx1_bond, idx2_bond) 249 | T *= ITensor(arr2, open_edge[x2,y2], idx2_out, idx2_bond) 250 | expval += contract2_SU!(peps, (x,y), (x2,y2), T, Vector{Index}([idx1_out]), max_bond_dim, svd_eps) 251 | expval += renormalize_tensor!(peps, (x,y)) 252 | expval += renormalize_tensor!(peps, (x2,y2)) 253 | expval += renormalize_bond_matrix!(peps, (x,y), (x2,y2)) 254 | open_edge[x,y] = idx1_out 255 | open_edge[x2,y2] = idx2_out 256 | 257 | #for i = 1:0#while canonicalness(peps) > canonicalness_threshold && n_steps < 100 258 | # for x=xmin:xmax for y=ymin:ymax 259 | # for (x2,y2) in neighbors(peps, (x,y)) if (x2,y2) > (x,y) 260 | # expval += identity_SU!(peps, (x,y), (x2,y2), max_bond_dim, svd_eps) 261 | # expval += renormalize_tensor!(peps, (x,y)) 262 | # expval += renormalize_tensor!(peps, (x2,y2)) 263 | # expval += renormalize_bond_matrix!(peps, (x,y), (x2,y2)) 264 | # end end 265 | # end end 266 | #end 267 | end end 268 | end end 269 | 270 | # contract subsequent two-site bond 271 | for x=xmin:xmax for y=ymin:ymax 272 | if z == zmax 273 | t = ctn.sites[x,y,z] 274 | arr = (t.type == TT_DELTA) ? [1. 1.] : [1. 0.] 275 | T = ITensor(arr, open_edge[x,y]) 276 | contract1!(peps, (x,y), T) 277 | else 278 | t = ctn.two_site_bonds[Set([(x,y,z), (x,y,z+1)])] 279 | if t.type == TT_DELTA 280 | arr = [t.val[1] 0 ; 0 t.val[2]] 281 | else 282 | arr = [t.val[1] t.val[2] ; t.val[2] t.val[1]] 283 | end 284 | idx = Index(2) 285 | T = ITensor(arr, open_edge[x,y], idx) 286 | contract1!(peps, (x,y), T) 287 | open_edge[x,y] = idx 288 | end 289 | end end 290 | 291 | end 292 | 293 | # contract final PEPS with sweep contractor 294 | res = contract_PEPS(peps, sc_bond_dim, svd_eps) 295 | return (res[1], res[2]+expval) 296 | end 297 | 298 | 299 | function contract_PEPS(peps::ContractionNetwork{P}, sc_bond_dim::Int, svd_eps::Float64=1e-16) where P 300 | xmin, xmax = extrema([pos[1] for (pos,v) in peps.tensors]) 301 | ymin, ymax = extrema([pos[2] for (pos,v) in peps.tensors]) 302 | 303 | # contract bond matrices 304 | for x=xmin:xmax for y=ymin:ymax 305 | for (x2,y2) in neighbors(peps, (x,y)) if (x2,y2) > (x,y) 306 | peps.tensors[x,y] *= bond_matrix(peps, (x,y), (x2,y2)) 307 | end end 308 | end end 309 | 310 | # we sweep from ymin to ymax 311 | expval = 0 312 | mps = MPS([peps.tensors[x,ymin] for x=xmin:xmax]) 313 | for y = ymin+1:ymax 314 | # contract 315 | for x = xmin:xmax 316 | i = x - xmin + 1 317 | mps.data[i] *= peps.tensors[x,y] 318 | end 319 | # normalize 320 | for i = 1:length(mps.data) 321 | h = Int(floor(log2(LinearAlgebra.norm( mps.data[i] )))) 322 | mps.data[i] /= exp2(h) 323 | expval += h 324 | end 325 | # truncate 326 | truncate!(mps, maxdim=sc_bond_dim, cutoff=svd_eps) 327 | # normalize 328 | for i = 1:length(mps.data) 329 | h = Int(floor(log2(LinearAlgebra.norm( mps.data[i] )))) 330 | mps.data[i] /= exp2(h) 331 | expval += h 332 | end 333 | end 334 | 335 | # get output 336 | T = ITensor([1.]) 337 | for T2 in mps.data 338 | T *= T2 339 | end 340 | val = storage(T)[1] 341 | h = Int(floor(log2(LinearAlgebra.norm( val )))) 342 | val /= exp2(h) 343 | expval += h 344 | 345 | return val, expval 346 | end 347 | 348 | --------------------------------------------------------------------------------