From f772e2718285d0c83c8957fb8746f626ab9cf540 Mon Sep 17 00:00:00 2001 From: qwjyh Date: Tue, 7 May 2024 11:11:07 +0900 Subject: [PATCH] init (migrating and renaming) --- .gitignore | 6 + LICENSE | 21 + Project.toml | 31 ++ README.md | 64 +++ docs/Project.toml | 6 + docs/make.jl | 23 ++ docs/src/checkmatrix.md | 82 ++++ docs/src/distance.md | 236 +++++++++++ docs/src/img/bench_ref_optimized.jpg | Bin 0 -> 32942 bytes docs/src/img/bench_ref_vs_aarref.jpg | Bin 0 -> 74031 bytes docs/src/index.md | 201 ++++++++++ docs/src/pauliops.md | 46 +++ examples/t6_2legos_notebook.jl | 47 +++ src/PauliOps/PauliOps.jl | 204 ++++++++++ src/QuantumLegos.jl | 18 + src/checkmatrix.jl | 565 +++++++++++++++++++++++++++ src/game.jl | 281 +++++++++++++ test/pauliops.jl | 26 ++ test/runtests.jl | 405 +++++++++++++++++++ 19 files changed, 2262 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Project.toml create mode 100644 README.md create mode 100644 docs/Project.toml create mode 100644 docs/make.jl create mode 100644 docs/src/checkmatrix.md create mode 100644 docs/src/distance.md create mode 100644 docs/src/img/bench_ref_optimized.jpg create mode 100644 docs/src/img/bench_ref_vs_aarref.jpg create mode 100644 docs/src/index.md create mode 100644 docs/src/pauliops.md create mode 100644 examples/t6_2legos_notebook.jl create mode 100644 src/PauliOps/PauliOps.jl create mode 100644 src/QuantumLegos.jl create mode 100644 src/checkmatrix.jl create mode 100644 src/game.jl create mode 100644 test/pauliops.jl create mode 100644 test/runtests.jl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95731a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.jl.*.cov +*.jl.cov +*.jl.mem +/Manifest.toml +/docs/Manifest.toml +/docs/build/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..badeb32 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 qwjyh and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..48713a7 --- /dev/null +++ b/Project.toml @@ -0,0 +1,31 @@ +name = "QuantumLegos" +uuid = "6c892f99-6e80-4382-8dc3-97545b5cb80e" +authors = ["qwjyh "] +version = "0.1.0-DEV" + +[deps] +IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[compat] +AbstractAlgebra = "0.34" +Aqua = "0.8" +Documenter = "1" +IterTools = "1.8" +JET = "0.8" +LinearAlgebra = "1" +StaticArrays = "1" +Test = "1" +julia = "1.9" + +[extras] +AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["AbstractAlgebra", "Aqua", "DataStructures", "Documenter", "JET", "LinearAlgebra", "Test"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..78e2447 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# Legos + +[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://qwjyh.github.io/QuantumLegos.jl/stable/) +[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://qwjyh.github.io/QuantumLegos.jl/dev/) +[![Build Status](https://github.com/qwjyh/QuantumLegos.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/qwjyh/QuantumLegos.jl/actions/workflows/CI.yml?query=branch%3Amain) + +[![Aqua](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) + +## How-Tos for Julia beginners +### How to get Julia +Download binary from https://julialang.org/downloads/. +Or use [juliaup](https://github.com/JuliaLang/juliaup)(recommended). + +### How to run (for dev.)(init) +1. clone this repo +2. cd to here +3. `julia --project` +4. type `]` to enter Pkg mode in REPL. +5. `instantiate` to download all dependencies +6. press backspace to exit Pkg mode +7. `using Legos` to use (normally starts from here) + +Recommended tools for REPL: +- OhMyREPL: for syntax highlight and interactive history search in REPL +- Revise: for auto-loading changes to the package + +### How to build and browse docs locally(init) +1. cd to `docs/` +2. `julia --project` +3. `]` to enter Pkg mode +5. `Pkg> dev ..` to add `Legos` to available package +4. `Pkg> instantiate` +6. Exit Pkg mode(backspace) and `include("make.jl")` to build. Now you can exit julia REPL by `exit()` or `Ctrl-D`. +7. Alternatively, run `julia --project make.jl` from any shell. +8. To browse file on browser, I recommend either to use LiveServer.jl (from Pkg mode REPL, `add LiveServer`) and `julia -e 'using LiveServer; serve(dir="docs/build")'` or cd to `docs/build` and `python -m http.server --bind localhost`. Details on [Note on Documenter.jl docs](https://documenter.juliadocs.org/stable/man/guide/#Building-an-Empty-Document) + +- You can stop the local server by just stopping LiveServer or python http.server (just `Ctrl-C`) +- You can update the docs by `julia --project make.jl` on `docs/` and reload on your browser. + +## TODO +- [x] write test for CheckMatrix constructor + - [x] add `Base.==` method for CheckMatrix +- [x] implement checkmatrix constructor from stabilizer generator +- [x] implement constructor for State + - [x] implement initial constructor for State with only 1 lego and 0 edge + - [x] implement action function on State + - [x] implement tracing function for CheckMatrix + - [ ] test for self-tracing + - [x] implement map function from LegoLeg to checkmatrix index +- [x] implement functions to glean stabilizer generator from CheckMatrix +- [ ] test functions with examples on the paper +- [x] improve perf of ref! +- [ ] implement function to calculate enumerator polynomial + +## ref! optimization +before + +![Benchmark of ref! vs AbstractAlgebra.rref](./docs/src/img/bench_ref_vs_aarref.jpg) + +(so many allocs, take profile) + +after + +![Optimized](./docs/src/img/bench_ref_optimized.jpg) diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..a5252db --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,6 @@ +[deps] +DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" +Legos = "6c892f99-6e80-4382-8dc3-97545b5cb80e" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000..3599446 --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,23 @@ +#! format: off +using Legos +using Documenter + +DocMeta.setdocmeta!(Legos, :DocTestSetup, :(using Legos); recursive=true) + +makedocs(; + modules=[Legos], + authors="", + # repo="", + sitename="Legos.jl", + format=Documenter.HTML(; + prettyurls=get(ENV, "CI", "false") == "true", + edit_link="main", + assets=String[], + ), + pages=[ + "Home" => "index.md", + "PauliOps" => "pauliops.md", + "checkmatrix.md", + "distance.md", + ], +) diff --git a/docs/src/checkmatrix.md b/docs/src/checkmatrix.md new file mode 100644 index 0000000..4d6fa4e --- /dev/null +++ b/docs/src/checkmatrix.md @@ -0,0 +1,82 @@ +# Details on check matrix operations + +## Basic features of check matrix + +- All action on state can be represented as operations on check matrix. +- QuantumLegos which don't connect each other are represented by block diagonal check matrix. + +Thus, one needs only one check matrix to represent state. + +- Generated group from generators represented by check matrix is invariant under row-swap and row-multiplying transforms on the check matrix. + +Thus, one can freely perform these row operations on check matrices. + +## Construction of check matrices and retrieving features + +Use [`CheckMatrix`](@ref) from `Matrix{Bool}` and [`checkmatrix`](@ref) from `Vector{PauliOp}`. + +```@docs +CheckMatrix +checkmatrix +``` + +Get xpart and zpart of check matrix. + +```@docs +QuantumLegos.xpart +QuantumLegos.zpart +``` + +Get generators from check matrix with [`generators`](@ref). + +```@docs +generators +``` + +See also, [`GeneratedPauliGroup`](@ref) and [`Base.Set`](https://docs.julialang.org/en/v1/base/collections/#Base.Set) to get group generated by generators. + +## Direct sum + +Used when adding lego without edge. + +```@docs +QuantumLegos.direct_sum +``` + +## Self-tracing + +### self-tracing + +```@docs +QuantumLegos.self_trace! +``` + +During self-tracing, pre-formatting and post-formatting described below is performed. + +### pre-formatting + +```@docs +QuantumLegos.eliminate_column! +``` + +```@docs +QuantumLegos.align_row! +``` + +[`QuantumLegos.align_row!`](@ref) + +By this process, all columns to be traced have `1`s on only 1st to 4th rows. +So during [`QuantumLegos.self_trace!`](@ref), all stabilizers generated from top three rows are calculated and perform operator matching. + +### post-formatting + +Remove rows which are linear combinations of other rows. + +```@docs +QuantumLegos.ref! +``` + +```@docs +QuantumLegos.eliminate_dependent_row! +``` + diff --git a/docs/src/distance.md b/docs/src/distance.md new file mode 100644 index 0000000..2df98ce --- /dev/null +++ b/docs/src/distance.md @@ -0,0 +1,236 @@ +# How to calculate code distance from the state. + +!!! warning "WIP" + This document is not fully completed. + +## Definition of code distance. + +Let's consider encoding circuit with $1$ logical bit and $k$ physical qubits[^1]. +Then this encoding has two physical basis, $\ket{0}_L$ and $\ket{1}_L$. +The **distance** of this encoding is the minimum bit flip required to convert between $\ket{0}_L$ and $\ket{1}_L$. + +[^1]: Not all state can be formalized like this. TODO + +## Classification of the stabilizers. + +When treating `State`, the logical leg is not assigned and one can treat all stabilizers equally. +However, if logical leg is assigned to the state, these stabilizers can be classified to $4$ groups. + +1. stabilizers on physical qubits +2. ​$\bar{X}$, which corresponds to logical $X$ +3. ​$\bar{Z}$, which corresponds to logical $Z$ +4. ​$\bar{Y}$, which corresponds to logical $Y$ + +Let $\ket{V}$ is the dual state of the channel or encoding map $[[n, 1, d]]$, + +```math +distance = \min_{S \in stabilizers} \#\left\{ i \mid \bar{Z}_i ≠ S_i \right\} +``` + +## Calculating code distance from the check matrix. + +TODO: nor required if the performance doesn't matter. + +## Examples + +### $[[5, 1, 3]]$ code + +​$[[5, 1, 3]]$ code has $4$ stabilizers generators, $XZZXI, IXZZX, XIXZZ, ZXIXZ$ and $2$ logical operators, $\bar{X} = XXXXX$ and $\bar{Z} = ZZZZZ$. +Therefore, stabilizer generators for the corresponding state $[[6, 0]]$ is $IXZZXI, IIXZZX, IXIXZZ, IZXIXZ, XXXXXX, ZZZZZZ$. + +Let's construct $[[6, 0]]$ state on QuantumLegos.jl. +```jldoctest 1 +julia> using QuantumLegos + +julia> stab_513 = pauliop.(["IXZZXI", "IIXZZX", "IXIXZZ", "IZXIXZ", "XXXXXX", "ZZZZZZ"]) +6-element Vector{StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("IXZZXI") + pauliop("IIXZZX") + pauliop("IXIXZZ") + pauliop("IZXIXZ") + pauliop("XXXXXX") + pauliop("ZZZZZZ") + +julia> lego_513 = Lego(stab_513) +Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IXZZXI"), pauliop("IIXZZX"), pauliop("IXIXZZ"), pauliop("IZXIXZ"), pauliop("XXXXXX"), pauliop("ZZZZZZ")]) + +julia> state_513 = State([lego_513], edge.([])) +State(Lego[Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IXZZXI"), pauliop("IIXZZX"), pauliop("IXIXZZ"), pauliop("IZXIXZ"), pauliop("XXXXXX"), pauliop("ZZZZZZ")])], Tuple{LegoLeg, LegoLeg}[], CheckMatrix(Bool[0 1 … 0 0; 0 0 … 1 0; … ; 1 1 … 0 0; 0 0 … 1 1], 6, 6)) + +``` + +Then collect generators of the state. +```jldoctest 1 +julia> normalizers = state_513.cmat |> generators |> GeneratedPauliGroup |> collect +64-element Vector{StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("IIIIII") + pauliop("IXZZXI") + pauliop("IIXZZX") + pauliop("IXYIYX") + pauliop("IXIXZZ") + pauliop("IIZYYZ") + pauliop("IXXYIY") + pauliop("IIYXXY") + pauliop("IZXIXZ") + pauliop("IYYZIZ") + ⋮ + pauliop("YYIZZI") + pauliop("YXZYZX") + pauliop("YIIXYX") + pauliop("YXYXII") + pauliop("YIXYXI") + pauliop("YIZZIY") + pauliop("YXIIXY") + pauliop("YIYIZZ") + pauliop("YXXZYZ") + +``` + +Get stabilizer and normalizers of the $[[5, 1, 3]]$ code by assigning the first leg as logical. +```jldoctest 1 +julia> stabs = filter(x -> x[1] == PauliOps.I, normalizers) +16-element Vector{StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("IIIIII") + pauliop("IXZZXI") + pauliop("IIXZZX") + pauliop("IXYIYX") + pauliop("IXIXZZ") + pauliop("IIZYYZ") + pauliop("IXXYIY") + pauliop("IIYXXY") + pauliop("IZXIXZ") + pauliop("IYYZIZ") + pauliop("IZIZYY") + pauliop("IYZIZY") + pauliop("IYXXYI") + pauliop("IZYYZI") + pauliop("IYIYXX") + pauliop("IZZXIX") + +julia> norm_x = filter(x -> x[1] == PauliOps.X, normalizers) +16-element Vector{StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("XXXXXX") + pauliop("XIYYIX") + pauliop("XXIYYI") + pauliop("XIZXZI") + pauliop("XIXIYY") + pauliop("XXYZZY") + pauliop("XIIZXZ") + pauliop("XXZIIZ") + pauliop("XYIXIY") + pauliop("XZZYXY") + pauliop("XYXYZZ") + pauliop("XZYXYZ") + pauliop("XZIIZX") + pauliop("XYZZYX") + pauliop("XZXZII") + pauliop("XYYIXI") + +julia> norm_y = filter(x -> x[1] == PauliOps.Y, normalizers) +16-element Vector{StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("YYYYYY") + pauliop("YZXXZY") + pauliop("YYZXXZ") + pauliop("YZIYIZ") + pauliop("YZYZXX") + pauliop("YYXIIX") + pauliop("YZZIYI") + pauliop("YYIZZI") + pauliop("YXZYZX") + pauliop("YIIXYX") + pauliop("YXYXII") + pauliop("YIXYXI") + pauliop("YIZZIY") + pauliop("YXIIXY") + pauliop("YIYIZZ") + pauliop("YXXZYZ") + +julia> norm_z = filter(x -> x[1] == PauliOps.Z, normalizers) +16-element Vector{StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("ZZZZZZ") + pauliop("ZYIIYZ") + pauliop("ZZYIIY") + pauliop("ZYXZXY") + pauliop("ZYZYII") + pauliop("ZZIXXI") + pauliop("ZYYXZX") + pauliop("ZZXYYX") + pauliop("ZIYZYI") + pauliop("ZXXIZI") + pauliop("ZIZIXX") + pauliop("ZXIZIX") + pauliop("ZXYYXZ") + pauliop("ZIXXIZ") + pauliop("ZXZXYY") + pauliop("ZIIYZY") + +``` + +These normalizers are generated from one logical operator and stabilizers. +```jldoctest 1 +julia> map(x -> x .* pauliop("XXXXXX"), stabs) |> Set == Set(norm_x) +true + +julia> map(x -> x .* pauliop("ZZZZZZ"), stabs) |> Set == Set(norm_x) +false + +julia> map(x -> x .* pauliop("ZZZZZZ"), stabs) |> Set == Set(norm_z) +true + +julia> map(x -> x .* pauliop("YYYYYY"), stabs) |> Set == Set(norm_y) +true + +julia> using IterTools + +julia> groupby(x -> x[1], normalizers) .|> Set == Set.([stabs, norm_x, norm_z, norm_y]) +true + +``` + +Define a function to get weight of the operator. +```jldoctest 1 +julia> function weight(x, i = 1) + count(x[i:end] .!= PauliOps.I) + end +weight (generic function with 2 methods) + +julia> weight(pauliop("XIXIXI")) +3 + +julia> weight(pauliop("XIXIXI"), 2) +2 + +``` + +Calculate coefficients of enumerator polynomial. +```jldoctest 1 +julia> using DataStructures + +julia> stabs .|> weight |> counter +Accumulator{Int64, Int64} with 2 entries: + 0 => 1 + 4 => 15 + +julia> function weight(i::Integer) + Base.Fix2(weight, i) + end +weight (generic function with 3 methods) + +julia> normalizers .|> weight(2) |> counter +Accumulator{Int64, Int64} with 4 entries: + 0 => 1 + 4 => 15 + 5 => 18 + 3 => 30 + +julia> [norm_x..., norm_y..., norm_z...] .|> weight(2) |> counter +Accumulator{Int64, Int64} with 2 entries: + 5 => 18 + 3 => 30 + +julia> [norm_x..., norm_y..., norm_z...] .|> weight(2) |> counter |> keys |> minimum +3 + +``` +Code distance of the encoding is the minimum degree of the non-zero term in the normalizer's polynomial($B$) and not in the stabilizer's polynomial($A$). +So the code distance of this encoding is $3$. diff --git a/docs/src/img/bench_ref_optimized.jpg b/docs/src/img/bench_ref_optimized.jpg new file mode 100644 index 0000000000000000000000000000000000000000..227215a00087bf2782857b719e86bcbe46bc0471 GIT binary patch literal 32942 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
  • L^v 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 literal 0 HcmV?d00001 diff --git a/docs/src/img/bench_ref_vs_aarref.jpg b/docs/src/img/bench_ref_vs_aarref.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b64926b6767e33bf40552e72f67fe416848ee41d GIT binary patch literal 74031 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 I@6XBq0PT using QuantumLegos + +julia> stabilizers = pauliop.(["IIXXXX", "IIZZZZ", "ZIZZII", "IZZIZI", "IXXXII", "XIXIXI"]) +6-element Vector{StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("IIXXXX") + pauliop("IIZZZZ") + pauliop("ZIZZII") + pauliop("IZZIZI") + pauliop("IXXXII") + pauliop("XIXIXI") + +julia> cmat = checkmatrix(stabilizers) +CheckMatrix with 6 generators, 6 legs: + 0 0 1 1 1 1 | 0 0 0 0 0 0 + 0 0 0 0 0 0 | 0 0 1 1 1 1 + 0 0 0 0 0 0 | 1 0 1 1 0 0 + 0 0 0 0 0 0 | 0 1 1 0 1 0 + 0 1 1 1 0 0 | 0 0 0 0 0 0 + 1 0 1 0 1 0 | 0 0 0 0 0 0 + + +julia> cmat.nlegs +6 + +julia> cmat.ngens +6 + +julia> cmat.cmat +6×12 Matrix{Bool}: + 0 0 1 1 1 1 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 1 1 1 1 + 0 0 0 0 0 0 1 0 1 1 0 0 + 0 0 0 0 0 0 0 1 1 0 1 0 + 0 1 1 1 0 0 0 0 0 0 0 0 + 1 0 1 0 1 0 0 0 0 0 0 0 + +julia> # define lego + +julia> lego = Lego(stabilizers) +Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")]) + +julia> lego.stabgens |> checkmatrix +CheckMatrix with 6 generators, 6 legs: + 0 0 1 1 1 1 | 0 0 0 0 0 0 + 0 0 0 0 0 0 | 0 0 1 1 1 1 + 0 0 0 0 0 0 | 1 0 1 1 0 0 + 0 0 0 0 0 0 | 0 1 1 0 1 0 + 0 1 1 1 0 0 | 0 0 0 0 0 0 + 1 0 1 0 1 0 | 0 0 0 0 0 0 + +``` + +- [`pauliop`](@ref) +- [`checkmatrix`](@ref) and [`CheckMatrix`](@ref) +- [`Lego`](@ref) + +## Defining and Updating State +```jldoctest +julia> using QuantumLegos + +julia> stabilizers = pauliop.(["IIXXXX", "IIZZZZ", "ZIZZII", "IZZIZI", "IXXXII", "XIXIXI"]) +6-element Vector{StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("IIXXXX") + pauliop("IIZZZZ") + pauliop("ZIZZII") + pauliop("IZZIZI") + pauliop("IXXXII") + pauliop("XIXIXI") + +julia> lego = Lego(stabilizers) +Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")]) + +julia> # state with 1 lego, 0 leg + +julia> st = State([lego, ], Tuple{LegoLeg, LegoLeg}[]) +State(Lego[Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")])], Tuple{LegoLeg, LegoLeg}[], CheckMatrix(Bool[0 0 … 0 0; 0 0 … 1 1; … ; 0 1 … 0 0; 1 0 … 0 0], 6, 6)) + +julia> st.cmat.cmat +6×12 Matrix{Bool}: + 0 0 1 1 1 1 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 1 1 1 1 + 0 0 0 0 0 0 1 0 1 1 0 0 + 0 0 0 0 0 0 0 1 1 0 1 0 + 0 1 1 1 0 0 0 0 0 0 0 0 + 1 0 1 0 1 0 0 0 0 0 0 0 + +julia> add_lego!(st, lego) +State(Lego[Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")]), Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")])], Tuple{LegoLeg, LegoLeg}[], CheckMatrix(Bool[0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0], 12, 12)) + +julia> st.cmat.cmat +12×24 Matrix{Bool}: + 0 0 1 1 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 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 + 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 1 0 1 0 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 0 1 1 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 0 0 0 0 0 0 1 1 1 1 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 + 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 + +julia> # state with 2 legos, 0 leg + +julia> st2 = State([lego, lego], Tuple{LegoLeg, LegoLeg}[]) +State(Lego[Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")]), Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")])], Tuple{LegoLeg, LegoLeg}[], CheckMatrix(Bool[0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0], 12, 12)) + +julia> st == st2 +true +``` + +## 2 Lego 1 edge state +```jldoctest +julia> using QuantumLegos + +julia> stabilizers = pauliop.(["IIXXXX", "IIZZZZ", "ZIZZII", "IZZIZI", "IXXXII", "XIXIXI"]) +6-element Vector{StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("IIXXXX") + pauliop("IIZZZZ") + pauliop("ZIZZII") + pauliop("IZZIZI") + pauliop("IXXXII") + pauliop("XIXIXI") + +julia> lego = Lego(stabilizers) +Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")]) + +julia> state = State([lego, lego], edge.([((1, 3), (2, 3))])) +State(Lego[Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")]), Lego{6}(6, StaticArraysCore.SVector{6, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXXXX"), pauliop("IIZZZZ"), pauliop("ZIZZII"), pauliop("IZZIZI"), pauliop("IXXXII"), pauliop("XIXIXI")])], Tuple{LegoLeg, LegoLeg}[(LegoLeg(1, 3), LegoLeg(2, 3))], CheckMatrix(Bool[1 0 … 0 0; 0 1 … 0 0; … ; 0 0 … 1 1; 0 0 … 0 1], 10, 10)) + +julia> state.cmat +CheckMatrix with 10 generators, 10 legs: + 1 0 1 0 1 0 0 0 0 0 | 0 0 0 0 0 0 0 0 0 0 + 0 1 0 1 1 0 0 0 0 0 | 0 0 0 0 0 0 0 0 0 0 + 0 0 1 1 1 0 0 1 1 1 | 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 1 0 1 0 1 | 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 1 0 1 1 | 0 0 0 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 1 1 0 1 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 | 0 0 1 1 1 0 0 1 1 1 + 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 1 1 0 1 + + +julia> pg = state.cmat |> generators |> GeneratedPauliGroup +GeneratedPauliGroup{10}(StaticArraysCore.SVector{10, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("XIXIXIIIII"), pauliop("IXIXXIIIII"), pauliop("IIXXXIIXXX"), pauliop("IIIIIXIXIX"), pauliop("IIIIIIXIXX"), pauliop("ZIIZZIIIII"), pauliop("IZZIZIIIII"), pauliop("IIZZZIIZZZ"), pauliop("IIIIIZIIZZ"), pauliop("IIIIIIZZIZ")], IterTools.Subsets{Vector{StaticArraysCore.SVector{N, QuantumLegos.PauliOps.SinglePauliOp} where N}}(StaticArraysCore.SVector{N, QuantumLegos.PauliOps.SinglePauliOp} where N[pauliop("XIXIXIIIII"), pauliop("IXIXXIIIII"), pauliop("IIXXXIIXXX"), pauliop("IIIIIXIXIX"), pauliop("IIIIIIXIXX"), pauliop("ZIIZZIIIII"), pauliop("IZZIZIIIII"), pauliop("IIZZZIIZZZ"), pauliop("IIIIIZIIZZ"), pauliop("IIIIIIZZIZ")])) + +julia> pauliop("XIIXIXIIXI") in pg +true + +``` + +# Internal(how it works) + +## Notes on Overall flow +Details on [^1] +- state is translated to a single check matrix + - the size is ≤ $N \times 2N$ where $N$ is maximum number of lego logs. +- any contraction can be performed on this single check matrix +- if the check matrix can be represented as direct sum of matrices with $k N$ columns where $k ∈ ℕ$, then they are not contracted + +### Construction of State +Construction of `State` is completed by calling `State` constructor recursively. + +1. Construct `State` without edge. Just adding legos. Checkmatrix is just a direct sum of each lego's checkmatrix +2. Concatenate each edges. During this operation, self tracing of checkmatrix is evaluated. + +Each constructor calls action function (which is a map from `State` to `State`). +Therefore, action functions can be used both for direct construction of `State` and action application to `State` during the game. + +# API + +```@index +``` + +```@autodocs +Modules = [QuantumLegos] +Pages = ["game.jl"] +``` + +[^1]: [C. Cao and B. Lackey, ‘Approximate Bacon-Shor code and holography’, J. High Energ. Phys., vol. 2021, no. 5, p. 127, May 2021, doi: 10.1007/JHEP05(2021)127.](https://doi.org/10.1007/JHEP05(2021)127) + diff --git a/docs/src/pauliops.md b/docs/src/pauliops.md new file mode 100644 index 0000000..fe6e58d --- /dev/null +++ b/docs/src/pauliops.md @@ -0,0 +1,46 @@ +# PauliOps + +submodule + +## Example +```jldoctest +julia> using QuantumLegos + +julia> p = PauliOps.single_pauliop('I') +I::SinglePauliOp = 0 + +julia> typeof(p) +Enum QuantumLegos.PauliOps.SinglePauliOp: +I = 0 +X = 1 +Y = 2 +Z = 3 + +julia> pauliop("IXYZ") +4-element PauliOp: + I::SinglePauliOp = 0 + X::SinglePauliOp = 1 + Y::SinglePauliOp = 2 + Z::SinglePauliOp = 3 + +julia> typeof(ans) +SVector{4, SinglePauliOp} (alias for StaticArraysCore.SArray{Tuple{4}, QuantumLegos.PauliOps.SinglePauliOp, 1, 4}) + +julia> PauliOps.I * PauliOps.X +X::SinglePauliOp = 1 + +julia> PauliOps.X * PauliOps.Z +Y::SinglePauliOp = 2 + +julia> pauliop("IIX") .* pauliop("XIY") +3-element PauliOp: + X::SinglePauliOp = 1 + I::SinglePauliOp = 0 + Z::SinglePauliOp = 3 +``` + +## API +```@autodocs +Modules = [PauliOps] +``` + diff --git a/examples/t6_2legos_notebook.jl b/examples/t6_2legos_notebook.jl new file mode 100644 index 0000000..936b62a --- /dev/null +++ b/examples/t6_2legos_notebook.jl @@ -0,0 +1,47 @@ +### A Pluto.jl notebook ### +# v0.17.7 + +using Markdown +using InteractiveUtils + +# ╔═╡ 3c6bf5ec-909f-11ee-06bd-83347655e198 +begin + import Pkg + Pkg.develop(path = "..") + using QuantumLegos +end + +# ╔═╡ 9088d661-7bf1-43fb-ab88-77fa325a5cf3 +stabilizers = pauliop.(["IIXXXX", "IIZZZZ", "ZIZZII", "IZZIZI", "IXXXII", "XIXIXI"]) + +# ╔═╡ f806287c-592d-476b-a912-205d2031fd93 +lego = Lego(stabilizers) + +# ╔═╡ 93251f25-5829-45a7-8aed-f76c834050a9 +state = State([lego, lego], Tuple{LegoLeg, LegoLeg}[]) + +# ╔═╡ 924588fb-0020-47e6-a918-98084d1fabad +state.cmat + +# ╔═╡ 99f153a1-da44-499a-b8af-e5c484b70597 +QuantumLegos.self_trace!(state.cmat, 3, 9) + +# ╔═╡ 726061b5-0d3a-4bf4-aebd-81a2c0fe7ea1 +state.cmat |> generators + +# ╔═╡ 69a71bfd-81d3-4961-9051-5f19be20f286 +pg = state.cmat |> generators |> GeneratedPauliGroup |> collect + +# ╔═╡ 656d8d7a-0ede-4621-99f0-9f83619c6a73 +pauliop("XIIXIXIIXI") in pg # example on Fig.6 + +# ╔═╡ Cell order: +# ╠═3c6bf5ec-909f-11ee-06bd-83347655e198 +# ╠═9088d661-7bf1-43fb-ab88-77fa325a5cf3 +# ╠═f806287c-592d-476b-a912-205d2031fd93 +# ╠═93251f25-5829-45a7-8aed-f76c834050a9 +# ╠═924588fb-0020-47e6-a918-98084d1fabad +# ╠═99f153a1-da44-499a-b8af-e5c484b70597 +# ╠═726061b5-0d3a-4bf4-aebd-81a2c0fe7ea1 +# ╠═69a71bfd-81d3-4961-9051-5f19be20f286 +# ╠═656d8d7a-0ede-4621-99f0-9f83619c6a73 diff --git a/src/PauliOps/PauliOps.jl b/src/PauliOps/PauliOps.jl new file mode 100644 index 0000000..dce3ceb --- /dev/null +++ b/src/PauliOps/PauliOps.jl @@ -0,0 +1,204 @@ +""" +Pauli operator +""" +module PauliOps + +using StaticArrays +using IterTools + +export SinglePauliOp, PauliOp +export single_pauliop, pauliop +export weight, xweight, zweight +export GeneratedPauliGroup + +""" + @enum SinglePauliOp begin + I + X + Y + Z + end + +Pauli Operator on a single qubit. +""" +@enum SinglePauliOp begin + I + X + Y + Z +end + +""" + PauliOp{N} + +Pauli operator on multiple qubits. +""" +const PauliOp{N} = SVector{N, SinglePauliOp} + +""" + single_pauliop(char::Char)::SinglePauliOp + +Convert `char` to `SinglePauliOp`. +""" +function single_pauliop(char::Char)::SinglePauliOp + char == 'I' && return I + char == 'X' && return X + char == 'Y' && return Y + char == 'Z' && return Z + throw(ArgumentError("invalid char for pauli operator (must be I, X, Y, or Z)")) +end + +function Base.String(p::SinglePauliOp) + p == I && return "I" + p == X && return "X" + p == Y && return "Y" + p == Z && return "Z" +end + +@inline function single_pauli_product(x::T, y::T)::T where {T <: SinglePauliOp} + if x == I + return y + elseif y == I + return x + elseif x == y + return I + else + x == X && y == Y && return Z + x == X && y == Z && return Y + x == Y && y == Z && return X + return single_pauli_product(y, x) + end +end + +Base.:(*)(x::T, y::T) where {T <: SinglePauliOp} = single_pauli_product(x, y) + +""" + pauliop(str::AbstractString)::PauliOp + +Convert `str` to `PauliOp`. +""" +function pauliop(str::AbstractString)::PauliOp + SVector(single_pauliop.(collect(str))...) +end + +Base.String(p::PauliOp) = join(String.(p)) +Base.show(io::IO, p::PauliOp) = print(io, "pauliop(\"$(String(p))\")") +Base.summary(io::IO, p::PauliOp) = print(io, "$(length(p))-element PauliOp") +# function Base.show(io::IO, ::MIME"text/plain", p::PauliOp) +# if get(io, :compact, false) +# print(io, String(p)) +# else +# summary(io, p) +# print(io, ": ", String(p)) +# end +# end + +""" + weight(p::PauliOp, [init = 1]) + +Weight of the operator `p`, i.e. non \$I\$ operator. +""" +function weight(p::PauliOp, init::Integer = 1) + # length(filter(!=(PauliOps.I), p)) + count = 0 + for i in eachindex(p) + i < init && continue + if p[i] != PauliOps.I + count += 1 + end + end + count +end + +""" + xweight(p::PauliOp, [init = 1]) + +Number of \$X, Y\$ in `p`. +""" +function xweight(p::PauliOp, init::Integer = 1) + count = 0 + for i in eachindex(p) + i < init && continue + if p[i] == PauliOps.X || p[i] == PauliOps.Y + count += 1 + end + end + count +end + +""" + zweight(p::PauliOp, [init = 1]) + +Number of \$Z, Y\$ in `p`. +a""" +function zweight(p::PauliOp, init::Integer = 1) + # length(filter(!=(PauliOps.I), p)) + count = 0 + for i in eachindex(p) + i < init && continue + if p[i] == PauliOps.Z || p[i] == PauliOps.Y + count += 1 + end + end + count +end + +# should have compatibility with other packages like AbstractAlgebra? +""" + struct GeneratedPauliGroup + +Iterator for group generated from `gens`. + + GeneratedPauliGroup(gens::AbstractVector{T}) where {T <: PauliOp} + +# Examples +```jldoctest +julia> gens = pauliop.(["IIXX", "IZZI"]) +2-element Vector{StaticArraysCore.SVector{4, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("IIXX") + pauliop("IZZI") + +julia> g = PauliOps.GeneratedPauliGroup(gens) +GeneratedPauliGroup{4}(StaticArraysCore.SVector{4, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXX"), pauliop("IZZI")], IterTools.Subsets{Vector{StaticArraysCore.SVector{4, QuantumLegos.PauliOps.SinglePauliOp}}}(StaticArraysCore.SVector{4, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("IIXX"), pauliop("IZZI")])) + +julia> collect(g) +4-element Vector{StaticArraysCore.SVector{4, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("IIII") + pauliop("IIXX") + pauliop("IZZI") + pauliop("IZYX") +``` +""" +struct GeneratedPauliGroup{N} + gens::AbstractVector{PauliOp{N}} + subsets::IterTools.Subsets + function GeneratedPauliGroup(gens::AbstractVector{T}) where {T <: PauliOp} + length.(gens) |> unique |> length |> ==(1) || + throw(ArgumentError("All generators must have the same length.")) + subsets = IterTools.subsets(gens) + N = length(gens[1]) + new{N}(gens, subsets) + end +end + +function Base.iterate(g::GeneratedPauliGroup) + next = iterate(g.subsets) + isnothing(next) && return nothing + + subset, state = next + init::PauliOp{length(g.gens[1])} = fill(I, length(g.gens[1])) + return (init, state) +end + +function Base.iterate(g::GeneratedPauliGroup, state) + ret = iterate(g.subsets, state) + isnothing(ret) && return nothing + + subset, state = ret + return (reduce(.*, subset), state) +end + +Base.length(g::GeneratedPauliGroup) = length(g.subsets) +Base.eltype(::Type{GeneratedPauliGroup{N}}) where {N} = PauliOp{N} + +end # module PauliOps diff --git a/src/QuantumLegos.jl b/src/QuantumLegos.jl new file mode 100644 index 0000000..c1ecac0 --- /dev/null +++ b/src/QuantumLegos.jl @@ -0,0 +1,18 @@ +module QuantumLegos + +export PauliOps +export pauliop, GeneratedPauliGroup, weight + +export Lego, LegoLeg, edge, CheckMatrix, checkmatrix, generators, State, add_lego!, add_edge!, distance + +using StaticArrays +using IterTools + +# PauliOps submodule +include("PauliOps/PauliOps.jl") +using .PauliOps + +include("checkmatrix.jl") +include("game.jl") + +end diff --git a/src/checkmatrix.jl b/src/checkmatrix.jl new file mode 100644 index 0000000..ec5d827 --- /dev/null +++ b/src/checkmatrix.jl @@ -0,0 +1,565 @@ +""" +CheckMatrix + +# Fields +- `cmat`: matrix itself (matrix of `Bool` since we consider only pauli ops.) +- `nlegs`: number of columns ÷ 2 +- `ngens`: number of rows + +# Constructor + CheckMatrix(cmat::AbstractMatrix{Bool}) +See also [`checkmatrix`](@ref). +""" +mutable struct CheckMatrix + # change this to AbstractMatrix{Bool} to accept BitMatrix? + cmat::Matrix{Bool} + nlegs::Int64 + ngens::Int64 + function CheckMatrix(cmat::Matrix{Bool}, nlegs::Integer, ngens::Integer) + size(cmat)[1] == ngens || + throw(ArgumentError("`ngens` must equal to number of row of `cmat`")) + size(cmat)[2] == nlegs * 2 || + throw(ArgumentError("`nlegs * 2` must equal to number of row of `cmat`")) + new(cmat, nlegs, ngens) + end +end + +function CheckMatrix(cmat::AbstractMatrix{Bool}) + s = size(cmat) + s[2] % 2 == 0 || throw(ArgumentError("invalid `cmat` size")) + CheckMatrix(cmat, s[2] ÷ 2, s[1]) +end + +Base.:(==)(x::T, y::T) where {T <: CheckMatrix} = (==)(x.cmat, y.cmat) +Base.copy(x::CheckMatrix) = CheckMatrix(copy(x.cmat)) + +function Base.show(io::IO, ::MIME"text/plain", cmat::CheckMatrix) + print(io, "CheckMatrix with $(cmat.ngens) generators, $(cmat.nlegs) legs:\n") + display_height, display_width = displaysize(io) + cur_vpos = 1 + for row in eachrow(cmat.cmat) + cur_hpos = 1 + for (i, elem) in enumerate(row) + sep = i == cmat.nlegs + 1 ? " | " : " " + elem_width = length(sep) + 1 + print(io, sep) + if elem + printstyled(io, 1, color = :default) + cur_hpos += elem_width + else + printstyled(io, 0, color = :light_black) + cur_hpos += elem_width + end + if cur_hpos > display_width - 5 + print(io, " …") + break + end + end + print(io, "\n") + cur_vpos += 1 + if cur_vpos > display_height - 6 + cur_hpos = 1 + for i in 1:(cmat.nlegs * 2) + sep = i == cmat.nlegs + 1 ? " | " : " " + elem_width = length(sep) + 1 + print(io, sep) + print(io, "⋮") + cur_hpos += elem_width + if cur_hpos > display_width - 5 + print(io, " ⋱\n") + break + end + end + return nothing + end + end +end + +""" + checkmatrix(stbgens::AbstractVector{T})::CheckMatrix where {T <: PauliOp} + +Get [`CheckMatrix`](@ref) from stabilizer generator. +""" +function checkmatrix(stbgens::AbstractVector{T})::CheckMatrix where {T <: PauliOp} + # no stbgens + if length(stbgens) == 0 + throw(ArgumentError("No stabilizer is provided. Need at least one.")) + end + # check whether all stbgens have the same length + stbgens_length = length.(stbgens) + all(==(stbgens_length[1]), stbgens_length[2:end]) || begin + @error "lengths of stbgens: " length.(stbgens) + throw(ArgumentError("All stabilizer must have the same length")) + end + + ngens = length(stbgens) + nlegs = length(stbgens[1]) + cmat = zeros(Bool, (ngens, nlegs * 2)) + for (i, gen) in enumerate(stbgens) + # get X Z component (can be more optimized) + xs = gen .== PauliOps.X + zs = gen .== PauliOps.Z + ys = gen .== PauliOps.Y + xs = xs .| ys + zs = zs .| ys + cmat[i, :] .= vcat(xs, zs) + end + return CheckMatrix(cmat) +end + +""" + xpart(cmat::CheckMatrix) + +Get X part (left half) of CheckMatrix. +""" +function xpart(cmat::CheckMatrix) + cmat.cmat[:, 1:(cmat.nlegs)] +end + +""" + zpart(cmat::CheckMatrix) + +Get Z part (right half) of CheckMatrix. +""" +function zpart(cmat::CheckMatrix) + cmat.cmat[:, (cmat.nlegs + 1):end] +end + +function xzparts(cmat::CheckMatrix) + return xpart(cmat), zpart(cmat) +end + +""" + generators(cmat::CheckMatrix)::Vector{PauliOp} + +Get generators from [`CheckMatrix`](@ref). +""" +function generators(cmat::CheckMatrix)::Vector{PauliOp} + gens = PauliOp{cmat.nlegs}[] + for row in eachrow(cmat.cmat) + gen = map(zip(row[1:(cmat.nlegs)], row[(cmat.nlegs + 1):end])) do (x, z) + g = PauliOps.I + x && (g = g * PauliOps.X) + z && (g = g * PauliOps.Z) + g + end + push!(gens, gen) + end + gens +end + +@doc raw""" + direct_sum(cmat_1::T, cmat_2::T)::T where {T <: CheckMatrix} + +Returns block diagonal `CheckMatrix` consists of `cmat_1` and `cmat_2`. + +# CheckMatrix transform +When adding a lego $l$ with check matrix $H_l$ to a state with check matrix $H_s$, +the resulting check matrix of the state will be +```math +\begin{pmatrix} + H_s & O \\ + O & H_l \\ +\end{pmatrix} +``` +""" +function direct_sum(cmat_1::T, cmat_2::T)::T where {T <: CheckMatrix} + # expand size and fill? + old_x, old_z = xzparts(cmat_1) + add_x, add_z = xzparts(cmat_2) + new_x = cat(old_x, add_x, dims = (1, 2)) + new_z = cat(old_z, add_z, dims = (1, 2)) + CheckMatrix(hcat(new_x, new_z)) +end + +""" + eliminate_column!( + cmat::CheckMatrix, + col::Integer, + avoid_row::AbstractVector{T}, + )::Union{Nothing, Int64} where {T <: Integer} + +Perform Gauss Elimination on `col`. +Keep only one `1` on `col` and remove from other rows by performing multiplication. +Choose row not in `avoid_row`. +If all rows with 1 on `col` are in `avoid_row`, then the last row of them is chose to keep 1. +If all rows on `col` is 0, return nothing. + +# Return +Row index which have 1 on `col`. +If all row on `col` is 0, return nothing. + +# Example +```jldoctest +julia> ex_cmat = CheckMatrix(Bool[1 0 1 0; 1 1 1 1]) +CheckMatrix with 2 generators, 2 legs: + 1 0 | 1 0 + 1 1 | 1 1 + +julia> QuantumLegos.eliminate_column!(ex_cmat, 1, Int64[]) +1 + +julia> ex_cmat +CheckMatrix with 2 generators, 2 legs: + 1 0 | 1 0 + 0 1 | 0 1 +``` +""" +function eliminate_column!( + cmat::CheckMatrix, + col::Integer, + avoid_row::AbstractVector{T}, +)::Union{Nothing, Int64} where {T <: Integer} + all(avoid_row .≤ cmat.ngens) || throw(ArgumentError("avoid_row is out of index")) + @assert col ≤ cmat.nlegs * 2 + + row_ids = findall(cmat.cmat[:, col]) # row with 1 on `col` + @debug "ids of rows with 1 on col $(col) = $(row_ids)" + if isempty(row_ids) + @debug "No 1 on column $(col)." + return nothing + end + if length(row_ids) == 1 + # already have only 1 1 + @debug "Already have only 1 1. Nothing to do." + return row_ids[1] + else + @debug "More than 1 1. Need some operation." + row_id_id = findfirst(!∈(avoid_row), row_ids) + row_id = if isnothing(row_id_id) + @debug "Rows with 1 are duplicated with avoid_row." + # select last row_ids + pop!(row_ids) + else + popat!(row_ids, row_id_id) + end + @debug "Selected row:$(row_id) to keep 1 on col:$(col)." + for row in row_ids + cmat.cmat[row, :] .⊻= cmat.cmat[row_id, :] + end + @debug "Finished. now only row:$(row_id) has 1 on col:$(col)" + return row_id + end + error("Unreachable") +end + +""" + eliminate_column!(cmat::CheckMatrix, col::Integer, + avoid_row::AbstractVector{T}) where {T <: Union{Nothing, Integer}} + +`avoid_row` can include `Nothing`, which is ignored in actual evaluation. +""" +function eliminate_column!( + cmat::CheckMatrix, + col::Integer, + avoid_row::AbstractVector{T}, +) where {T <: Union{Nothing, Integer}} + eliminate_column!(cmat, col, Vector{Integer}(filter(!isnothing, avoid_row))) +end + +@inline function swap_row!(m::AbstractMatrix, i::Integer, j::Integer) + i == j && return nothing + @inbounds m[i, :], m[j, :] = m[j, :], m[i, :] +end + +""" + align_row!(m::AbstractMatrix, row::Integer, occupied::Vector{Union{Nothing, Integer}}) -> Integer + align_row!(m::AbstractMatrix, row::Nothing, occupied::Vector{Union{Nothing, Integer}}) -> Nothing + +Swap row at `row` in `m` and row at next to the maximum in `occupied`. +`occupied` is supposed to be a list of returns from [`eliminate_column!`](@ref). +If `row` is in `occupied`, do nothing and returns `row`. +If `row` is `nothing`, return `nothing`. + +# Arguments +- `m::AbstractMatrix`: mutated +- `row::Union{Nothing, Integer}`: row to be aligned +- `occupied::Vector{Union{Nothing, Integer}}`: indices of already occupied rows. `row` will be next to these rows. + +# Return +- Row index where `row` is moved. +""" +function align_row! end + +function align_row!( + m::AbstractMatrix, + row::Integer, + occupied::AbstractVector{T}, +) where {T <: Integer} + if row ∈ occupied + return row + end + target = if isempty(occupied) + 1 # begin + else + maximum(occupied) + 1 # TODO: might cause out of index + end + swap_row!(m, row, target) + target +end + +function align_row!( + _::AbstractMatrix, + row::Nothing, + _::AbstractVector{T}, +) where {T <: Union{Nothing, Integer}} + nothing +end +function align_row!( + m::AbstractMatrix, + row::Integer, + occupied::AbstractVector{T}, +) where {T <: Union{Nothing, Integer}} + filter!(!isnothing, occupied) + occupied = Vector{Integer}(occupied) + align_row!(m, row, occupied) +end + +# TODO: improve perf (see README.md) +""" + ref!(cmat::CheckMatrix) -> Int + +Convert `cmat` to row echelon form. + +Returns rank of check matrix. + +# Examples +```jldoctest +julia> cmat = CheckMatrix(Bool[ + 1 0 1 0 1 1 0 1 + 0 1 0 0 0 1 0 0 + 1 1 1 0 1 0 1 1 + 0 1 0 0 0 1 0 0 + ]) +CheckMatrix with 4 generators, 4 legs: + 1 0 1 0 | 1 1 0 1 + 0 1 0 0 | 0 1 0 0 + 1 1 1 0 | 1 0 1 1 + 0 1 0 0 | 0 1 0 0 + + +julia> QuantumLegos.ref!(cmat) +3 + +julia> cmat +CheckMatrix with 4 generators, 4 legs: + 1 0 1 0 | 1 1 0 1 + 0 1 0 0 | 0 1 0 0 + 0 0 0 0 | 0 0 1 0 + 0 0 0 0 | 0 0 0 0 + +``` +""" +function ref!(cmat::CheckMatrix) + # TODO: remain cmat shape when rank is max + # For manual debugging stabilizer generators + # Requires LinearAlgebra + # if cmat.ngens == rank(cmat.cmat) + # return cmat.ngens + # end + r = 0 # row + # for col in eachcol(cmat.cmat) + for i in 1:(cmat.nlegs * 2) + isall0 = true + rid = 0 + for k in (r + 1):(cmat.ngens) + if cmat.cmat[k, i] + isall0 = false + rid = k + break + end + end + if isall0 + continue + end + r += 1 + swap_row!(cmat.cmat, r, rid) + for k in (rid + 1):(cmat.ngens) + if cmat.cmat[k, i] + @. cmat.cmat[k, :] = cmat.cmat[r, :] ⊻ cmat.cmat[k, :] + end + end + # + # rids = Int[] + # for j in (r + 1):(cmat.ngens) + # cmat.cmat[j, i] && push!(rids, j) + # end + # # rids = findall(col) |> filter(>(r)) + # if isempty(rids) # all 0 + # continue + # end + # r += 1 + # rid = popfirst!(rids) + # swap_row!(cmat.cmat, r, rid) + # if isempty(rids) # only 1 1 + # continue + # end + # for i in rids + # @. cmat.cmat[i, :] = cmat.cmat[r, :] ⊻ cmat.cmat[i, :] + # end + if r == cmat.ngens + break + end + end + r # rank +end + +""" + eliminate_dependent_row!(cmat::CheckMatrix) -> CheckMatrix + +Remove dependent rows to keep only independent generators. +""" +function eliminate_dependent_row!(cmat::CheckMatrix) + # convert to reduced row echelon form: ref! + # remove all-zero rows + r = ref!(cmat) + num_zero = cmat.ngens - r + if num_zero > 0 + cmat.cmat = cmat.cmat[1:r, :] + cmat.ngens = r + end + return cmat +end + +""" + self_trace!(cmat::CheckMatrix, col_1::Integer, col_2::Integer) + +Take a self-trace of checkmatrix `cmat` with column `col_1` and `col_2`. + +# Example +TODO +""" +function self_trace!(cmat::CheckMatrix, col_1::Integer, col_2::Integer)#::CheckMatrix + if !(col_1 ≤ cmat.nlegs && col_2 ≤ cmat.nlegs) + throw( + ArgumentError( + "Invalid column index(specified index is too large: $(max(col_1, col_2)) > $(cmat.nlegs))", + ), + ) + end + if col_1 == col_2 + throw(ArgumentError("Can't trace two same legs")) + end + # sort to col_1 < col_2 + if col_1 > col_2 + col_1, col_2 = col_2, col_1 + end + @debug "Initial cmat" cmat + + # TODO: cmat.nlegs ≤ 2 ? + + # organize cmat to where col_1 and col_2 have less than 3 true + ## do for X on col_1 + col_1_x_id = eliminate_column!(cmat, col_1, Int64[]) + col_1_x_id = align_row!(cmat.cmat, col_1_x_id, Int64[]) + @debug "Finished eliminating of X" cmat + ## do for Z on col_1 + col_1_z_id = eliminate_column!(cmat, col_1 + cmat.nlegs, [col_1_x_id]) + col_1_z_id = align_row!(cmat.cmat, col_1_z_id, [col_1_x_id]) + @debug "Finished elimination of Z" cmat + @debug "col_1 X:" findall(cmat.cmat[:, col_1]) + @debug "col_1 Z:" findall(cmat.cmat[:, col_1 + cmat.nlegs]) + ## repeat for col_2 + ## for X on col_2 + col_2_x_id = eliminate_column!(cmat, col_2, [col_1_x_id, col_1_z_id]) + col_2_x_id = align_row!(cmat.cmat, col_2_x_id, [col_1_x_id, col_1_z_id]) + @debug "Finished eliminating of X" cmat + ## do for Z on col_1 + col_2_z_id = + eliminate_column!(cmat, col_2 + cmat.nlegs, [col_1_x_id, col_1_z_id, col_2_x_id]) + col_2_z_id = align_row!(cmat.cmat, col_2_z_id, [col_1_x_id, col_1_z_id, col_2_x_id]) + @debug "Finished elimination of Z" cmat + @debug "col_2 X:" findall(cmat.cmat[:, col_2]) + @debug "col_2 Z:" findall(cmat.cmat[:, col_2 + cmat.nlegs]) + + @debug "Finished Gauss Elimination" cmat + # @info "col x/z s $([col_1_x_id, col_1_z_id, col_2_x_id, col_2_z_id])" + + # then adding them + # select rows which have same value on col_1 and col_2 + # trace (remove col_1, col_2) and remove duplicate row(or dependent row: i.e. row-elim.) + @debug "Tracing" + # assert that col_1, col_2 have 1s only on row 1, 2, 3, 4 + @assert findall(cmat.cmat[:, col_1]) .|> ≤(1) |> all + @assert findall(cmat.cmat[:, col_1 + cmat.nlegs]) .|> ≤(2) |> all + @assert findall(cmat.cmat[:, col_2]) .|> ≤(3) |> all + @assert findall(cmat.cmat[:, col_2 + cmat.nlegs]) .|> ≤(4) |> all + col_12_xz_ids = [col_1_z_id, col_1_x_id, col_2_x_id, col_2_z_id] |> filter(!isnothing) + + # note that col_1 < col_2 + remaining_x_cols = + [1:(col_1 - 1)..., (col_1 + 1):(col_2 - 1)..., (col_2 + 1):(cmat.nlegs)...] + remaining_cols = [remaining_x_cols..., (remaining_x_cols .+ cmat.nlegs)...] + if cmat.ngens ≤ 3 + # TODO: + # @warn "cmat has ≤ 3 generators" cmat + subsets_row = eachrow(cmat.cmat) |> subsets |> collect + filter!(!isempty, subsets_row) + generated_rows = map(subsets_row) do vs + reduce(.⊻, vs) + end + # matching on col + filter!(generated_rows) do v + v[[col_1, col_2]] == v[[col_1 + cmat.nlegs, col_2 + cmat.nlegs]] + end + new_ngens = length(generated_rows) + new_cmat = zeros(Bool, (new_ngens, 2 * (cmat.nlegs - 2))) + for (i, v) in enumerate(generated_rows) + new_cmat[i, :] .= v[remaining_cols] + end + + cmat.cmat = new_cmat + cmat.nlegs -= 2 + @assert size(cmat.cmat)[2] == 2 * cmat.nlegs "cmat size mismatched" + cmat.ngens = size(cmat.cmat)[1] + @assert cmat.ngens == new_ngens + elseif cmat.cmat[1, col_1] && + cmat.cmat[2, col_1 + cmat.nlegs] && + cmat.cmat[3, col_2] && + cmat.cmat[4, col_2 + cmat.nlegs] + @assert Set([col_1_x_id, col_1_z_id, col_2_x_id, col_2_z_id]) == Set([1, 2, 3, 4]) + # All errors correctable (D.9) (row 2, 3 swapped) + cmat.cmat[1, :] .⊻= cmat.cmat[3, :] + cmat.cmat[2, :] .⊻= cmat.cmat[4, :] + @assert cmat.cmat[1, col_1] && cmat.cmat[3, col_2] "Test whether the form is D.10" + @assert cmat.cmat[2, col_1 + cmat.nlegs] && cmat.cmat[4, col_2 + cmat.nlegs] "Test whether the form is D.10" + new_cmat = zeros(Bool, (cmat.ngens - 2, 2 * (cmat.nlegs - 2))) + new_cmat[1, :] .= cmat.cmat[1, remaining_cols] + new_cmat[2, :] .= cmat.cmat[2, remaining_cols] + new_cmat[3:end, :] .= cmat.cmat[5:end, remaining_cols] + + cmat.cmat = new_cmat + cmat.nlegs -= 2 + cmat.ngens -= 2 + else#if maximum(col_12_xz_ids) == 3 # TODO: can be split + # TODO need some cases + # @warn "Implementing..." + # @info cmat cmat + + row_123 = eachrow(cmat.cmat)[1:3] + subsets_from_123 = collect(subsets(row_123)) + filter!(!isempty, subsets_from_123) + generated_from_123 = map(subsets_from_123) do vs + reduce(.⊻, vs) + end + filter!(generated_from_123) do v + # matched + v[[col_1, col_2]] == v[[col_1 + cmat.nlegs, col_2 + cmat.nlegs]] + end + n_generated_from_123 = length(generated_from_123) + new_ngens = n_generated_from_123 + cmat.ngens - 3 + new_cmat = zeros(Bool, (new_ngens, 2 * (cmat.nlegs - 2))) + for (i, v) in enumerate(generated_from_123) + new_cmat[i, :] .= v[remaining_cols] + end + new_cmat[(n_generated_from_123 + 1):end, :] .= cmat.cmat[4:end, remaining_cols] + + cmat.cmat = new_cmat + cmat.nlegs -= 2 + @assert cmat.nlegs == size(new_cmat)[2] ÷ 2 + cmat.ngens = size(new_cmat)[1] + end + # ngens is updated below + eliminate_dependent_row!(cmat) + return cmat +end diff --git a/src/game.jl b/src/game.jl new file mode 100644 index 0000000..b680193 --- /dev/null +++ b/src/game.jl @@ -0,0 +1,281 @@ +macro todo() + error("TODO: Unimplemented") +end +macro todo(msg) + error("TODO: Unimplemented: $msg") +end + +""" +Quantum lego with `N` legs. + +# Fields +- `nlegs::Int64`: number of legs, equals `N` +- `stabgens::SVector{N, PauliOp{N}}`: stabilizer generators. vector of [`PauliOp`](@ref) + +# Constructor + Lego([nlegs::Integer], stabgens::AbstractVector{PauliOp{N}}) + +Constructor for [`Lego`](@ref). +`nlegs` is optional (default is length of the first stabilizer generator). + +# Example +```jldoctest +julia> stabgens = pauliop.(["II", "XX"]) +2-element Vector{StaticArraysCore.SVector{2, QuantumLegos.PauliOps.SinglePauliOp}}: + pauliop("II") + pauliop("XX") + +julia> Lego(stabgens) +Lego{2}(2, StaticArraysCore.SVector{2, QuantumLegos.PauliOps.SinglePauliOp}[pauliop("II"), pauliop("XX")]) +``` +""" +struct Lego{N} + nlegs::Int64 + stabgens::SVector{N, PauliOp{N}} # There two Ns must be the same? + function Lego(nlegs::Integer, stabgens::AbstractVector{PauliOp{N}}) where {N} + all(length(stabgens[1]) .== length.(stabgens)) || + throw(ArgumentError("All stabgens have the same length")) + nlegs == length(stabgens[1]) || + throw(ArgumentError("`nlegs` must equal to length of stabilizer")) + new{N}(nlegs, stabgens) + end +end + +Lego(stabgens::AbstractVector{PauliOp{N}}) where {N} = Lego(length(stabgens[1]), stabgens) + +function nlegs(lego::Lego)::Integer + lego.nlegs +end + +""" + mutable struct State + +To be used in [`State`](@ref). + +# Fields +- `lego_id::Int64`: index in `legos` in `State` +- `edge_id::Int64`: index in `Lego` in `legos` in `State`. No validation check included in `LegoLeg`. + +# Example +```jldoctest +julia> x = LegoLeg.([(2, 1), (1, 1), (1, 0)]) +3-element Vector{LegoLeg}: + LegoLeg(2, 1) + LegoLeg(1, 1) + LegoLeg(1, 0) + +julia> sort(x) +3-element Vector{LegoLeg}: + LegoLeg(1, 0) + LegoLeg(1, 1) + LegoLeg(2, 1) +``` +""" +struct LegoLeg + lego_id::Int64 + leg_id::Int64 +end + +LegoLeg(t::Tuple{Integer, Integer}) = LegoLeg(t...) + +""" +Helper function to create `Tuple{LegoLeg, LegoLeg}` to represent edge. +""" +function edge end + +""" + edge(t::Tuple{T, T}) where {T <: Tuple{Integer, Integer}} +""" +function edge(t::Tuple{T, T}) where {T <: Tuple{Integer, Integer}} + (LegoLeg(t[1]), LegoLeg(t[2])) +end + +""" + edge(t::Tuple{T, T, T, T}) where {T <: Integer} +""" +function edge(t::Tuple{T, T, T, T}) where {T <: Integer} + edge(((t[1], t[2]), (t[3], t[4]))) +end + +""" + edge(x::T, y::T, z::T, w::T) where {T <: Integer} +""" +function edge(x::T, y::T, z::T, w::T) where {T <: Integer} + edge(((x, y), (z, w))) +end + +function Base.isless(x::T, y::T) where {T <: LegoLeg} + if x.lego_id != y.lego_id + return x.lego_id < y.lego_id + else + return x.leg_id < y.leg_id + end +end + +""" + mutable struct State + +State (in p.4) + +# Fields +- `legos`: `Vector{Lego}` +- `edges`: Vector of ((`lego_i, leg_n`), (`lego_j, leg_m`)). + Each element is sorted (i.e. `lego_i < lego_j` or `lego_i == lego_j && leg_n < leg_m`). + This feature is used in [`is_connected_to_firstlego`](@ref). +- `cmat::CheckMatrix`: CheckMatrix + +# Constructor + State(legos::Vector{Lego{N}}, edges::Vector{Tuple{LegoLeg, LegoLeg}}) + +# Methods with +- [`add_lego!`](@ref) +- [`add_edge!`](@ref) + +# Example +TODO +""" +mutable struct State + legos::Vector{Lego} + edges::Vector{Tuple{LegoLeg, LegoLeg}} + cmat::CheckMatrix + function State(legos::Vector{Lego{N}}, edges::Vector{Tuple{LegoLeg, LegoLeg}}) where {N} + if length(edges) == 0 + if length(legos) == 0 + throw(ArgumentError("Need at least one lego")) + elseif length(legos) == 1 + cmat = checkmatrix(legos[1].stabgens) + return new(legos, edges, cmat) + else + # just iterate instead of recursion? + return add_lego!(State(legos[1:(end - 1)], edges), legos[end]) + end + else + new_edge = pop!(edges) + if new_edge[1] == new_edge[2] + throw(ArgumentError("Can't make edges between the same leg.")) + end + # sort + if new_edge[1] > new_edge[2] + new_edge = (new_edge[2], new_edge[1]) + end + state = State(legos, edges) + return add_edge!(state, new_edge) + end + end +end + +function Base.:(==)(x::T, y::T) where {T <: State} + x.legos == y.legos && x.edges == y.edges && x.cmat == y.cmat +end + +""" + add_lego!(state::State, lego::Lego) -> State + +Add a new lego, updating `state`. +""" +function add_lego!(state::State, lego::Lego)::State + push!(state.legos, lego) + # direct sum + state.cmat = direct_sum(state.cmat, checkmatrix(lego.stabgens)) + return state +end + +""" + cmat_index(state::State, leg::LegoLeg)::Int64 + +Get column index corresponds to `leg` in check matrix of `state`. +If given `leg` is already connected, it throws `ArgumentError`. +If given `lego_id` of `leg` is out of `state.legos`, throws `ArgumentError`. +""" +function cmat_index(state::State, leg::LegoLeg)::Int64 + connected_legs = if isempty(state.edges) + LegoLeg[] + else + mapreduce(x -> [x...], vcat, state.edges) + end + if leg in connected_legs + throw(ArgumentError("The specified leg:$(leg) is already connected.")) + end + if leg.lego_id > length(state.legos) + throw(ArgumentError("state doesn't have lego:$(leg.lego_id)")) + end + sort!(connected_legs) + filter!(<(leg), connected_legs) + n_connected_edges = length(connected_legs) + n_lego_legs = state.legos[1:(leg.lego_id - 1)] .|> nlegs |> sum + return n_lego_legs + leg.leg_id - n_connected_edges +end + +""" + add_edge!(state::State, leg_1::LegoLeg, leg_2::LegoLeg)::State + +Add a new edge between `leg_1` and `leg_2`, updating `state`. +""" +function add_edge!(state::State, leg_1::LegoLeg, leg_2::LegoLeg)::State + # sort + if leg_1 > leg_2 + leg_1, leg_2 = leg_2, leg_1 + end + col_1 = cmat_index(state, leg_1) + col_2 = cmat_index(state, leg_2) + self_trace!(state.cmat, col_1, col_2) # mutates cmat + push!(state.edges, (leg_1, leg_2)) + return state +end + +add_edge!(state::State, edge::Tuple{LegoLeg, LegoLeg}) = add_edge!(state, edge...) + +""" + is_connected_to_firstlego(state::State)::BitVector + +Returns vector which stores whether each lego is connected to the first lego. +""" +function is_connected_to_firstlego(state::State)::BitVector + connected_to_first = falses(state.legos |> length) + connected_to_first[1] = true + sort!(state.edges) + for edge in state.edges + is_edge_1_connected = connected_to_first[edge[1].lego_id] + is_edge_2_connected = connected_to_first[edge[2].lego_id] + if is_edge_1_connected && is_edge_2_connected + continue + elseif is_edge_1_connected + connected_to_first[edge[2].lego_id] = true + elseif is_edge_2_connected + connected_to_first[edge[1].lego_id] = true + else + continue + end + end + return connected_to_first +end + +""" + distance(state::State) -> Int + +Calculate code distance when the first leg of `state` is assigned as logical. +""" +distance(state::State) = _naive_distance(state) + +# TODO +""" +Calculate distance. +Use minimum distance of all generated normalizers. +""" +function _naive_distance(state::State) + # not optimized(can use less alloc)? + _naive_distance(state.cmat) +end + +function _naive_distance(cmat::CheckMatrix) + # not optimized(can use less alloc)? + if cmat.ngens != cmat.nlegs + return 0 + end + normalizers = cmat |> generators |> GeneratedPauliGroup |> collect + filter!(x -> x[1] != PauliOps.I, normalizers) + isempty(normalizers) && @warn "No stabilizer in the code" state + distance::Integer = minimum(weight, normalizers) - 1 # substitute 1 because first op is always ≠I + @assert distance >= 0 + return distance +end diff --git a/test/pauliops.jl b/test/pauliops.jl new file mode 100644 index 0000000..455a356 --- /dev/null +++ b/test/pauliops.jl @@ -0,0 +1,26 @@ +@testset "SinglePauliOp" begin + @test PauliOps.single_pauliop('I') == PauliOps.I + @test_throws ArgumentError PauliOps.single_pauliop('a') +end + +@testset "SinglePauliOp Product" begin + using QuantumLegos.PauliOps: I, X, Y, Z + @test I * X == X + @test Z * I == Z + @test X * Y == Z + @test Z * X == Y + @test X * X == I +end + +@testset "PauliOp" begin + @test pauliop("IXYZ") == [PauliOps.I, PauliOps.X, PauliOps.Y, PauliOps.Z] + @test pauliop("IIXXZZ") == + [PauliOps.I, PauliOps.I, PauliOps.X, PauliOps.X, PauliOps.Z, PauliOps.Z] +end + +@testset "weight" begin + p = pauliop("IXYZIXYZ") + @test weight(p) == 6 + @test QuantumLegos.xweight(p) == 4 + @test QuantumLegos.zweight(p) == 4 +end diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 0000000..415ca53 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,405 @@ +# for language server completion +# must be removed for test +true || include("../src/QuantumLegos.jl") + +using QuantumLegos +using Test +using Documenter +using Aqua +using JET + +@testset "QuantumLegos.jl" begin + @testset "PauliOps" begin + include("pauliops.jl") + end + + @testset "Lego" begin + stabilizers = pauliop.(["II", "XX"]) + wrong_stabilizers = pauliop.(["I", "XX"]) # not SVector + @info stabilizers + @test_throws ArgumentError QuantumLegos.Lego(3, stabilizers) + @test_throws MethodError QuantumLegos.Lego(2, wrong_stabilizers) + end + + @testset "CheckMatrix" begin + @test_throws ArgumentError QuantumLegos.CheckMatrix([true false; false true], 2, 2) + @test_throws ArgumentError QuantumLegos.CheckMatrix([true false; false true], 1, 1) + @test QuantumLegos.CheckMatrix([true false; false true]) == + QuantumLegos.CheckMatrix(Bool[1 0; 0 1], 1, 2) + @test_throws ArgumentError QuantumLegos.CheckMatrix([true false true; false true true]) + + @test_throws MethodError QuantumLegos.checkmatrix(pauliop.(["I", "IX"])) + @test_throws ArgumentError QuantumLegos.checkmatrix(PauliOps.PauliOp[]) + @test QuantumLegos.checkmatrix(pauliop.(["IIXX", "IZZI"])) == + QuantumLegos.CheckMatrix(Bool[0 0 1 1 0 0 0 0; 0 0 0 0 0 1 1 0]) + @test QuantumLegos.checkmatrix(pauliop.(["YIXX", "IZYI"])) == + QuantumLegos.CheckMatrix(Bool[1 0 1 1 1 0 0 0; 0 0 1 0 0 1 1 0]) + + let + gens = pauliop.(["IIXX", "IZZI"]) + cmat_1 = QuantumLegos.checkmatrix(gens) + @test QuantumLegos.xpart(cmat_1) == Bool[0 0 1 1; 0 0 0 0] + @test QuantumLegos.zpart(cmat_1) == Bool[0 0 0 0; 0 1 1 0] + @test generators(cmat_1) == gens + end + let + gens = pauliop.(["YIXX", "IZYI"]) + cmat_2 = QuantumLegos.checkmatrix(gens) + @test QuantumLegos.xpart(cmat_2) == Bool[1 0 1 1; 0 0 1 0] + @test QuantumLegos.zpart(cmat_2) == Bool[1 0 0 0; 0 1 1 0] + @test generators(cmat_2) == gens + end + + @testset "eliminate_column!" begin + let + cmat = QuantumLegos.CheckMatrix(Bool[ + 1 0 0 1 + 1 0 1 1 + ]) + @test QuantumLegos.eliminate_column!(cmat, 1, Int64[]) == 1 + @test cmat.cmat == Bool[ + 1 0 0 1 + 0 0 1 0 + ] + end + let + cmat = QuantumLegos.CheckMatrix(Bool[ + 1 0 0 1 + 1 0 1 1 + ]) + @test QuantumLegos.eliminate_column!(cmat, 1, Int64[1]) == 2 + @test cmat.cmat == Bool[ + 0 0 1 0 + 1 0 1 1 + ] + end + let + cmat = QuantumLegos.CheckMatrix( + Bool[ + 1 1 0 1 0 0 1 1 + 0 0 1 1 0 1 0 1 + 0 1 1 0 1 0 1 1 + 0 1 0 1 0 1 0 1 + ], + ) + @test QuantumLegos.eliminate_column!(cmat, 2, [1]) == 3 + @test cmat.cmat == Bool[ + 1 0 1 1 1 0 0 0 + 0 0 1 1 0 1 0 1 + 0 1 1 0 1 0 1 1 + 0 0 1 1 1 1 1 0 + ] + end + let + cmat = QuantumLegos.CheckMatrix( + Bool[ + 1 1 0 1 0 0 1 1 + 0 0 1 1 0 1 0 1 + 0 1 1 0 1 0 1 1 + 0 1 0 1 0 1 0 1 + ], + ) + @test QuantumLegos.eliminate_column!(cmat, 2, [1, nothing]) == 3 + @test cmat.cmat == Bool[ + 1 0 1 1 1 0 0 0 + 0 0 1 1 0 1 0 1 + 0 1 1 0 1 0 1 1 + 0 0 1 1 1 1 1 0 + ] + end + let + cmat = QuantumLegos.CheckMatrix( + Bool[ + 1 0 0 0 1 1 0 1 + 0 1 0 1 0 1 1 0 + 0 1 0 0 0 1 0 0 + 1 1 0 1 0 0 0 1 + ], + ) + @test QuantumLegos.eliminate_column!(cmat, 3, Int[]) === nothing + end + let + # random test (robustness) + function random_test()::Bool + test_mat = rand(Bool, (8, 16)) + cmat = QuantumLegos.CheckMatrix(copy(test_mat)) + keep_indices = (1:8)[rand(1:8, rand(0:8))] + keep_index = rand(1:16) + try + QuantumLegos.eliminate_column!(cmat, keep_index, keep_indices) + return true + catch e + # unexpected error + @error "Error on eliminate_column! with keep_index: $(keep_index), avoid: $(keep_indices), matrix:" cmat + @error "error: " e + return false + end + end + + for i in 1:100 + # @info i + @test random_test() + end + end + @testset "ArgumentError" begin + cmat = CheckMatrix(rand(Bool, (6, 12))) + @test_throws ArgumentError QuantumLegos.eliminate_column!(cmat, 3, [1, 7]) + end + end + @testset "swap_row!" begin + let + m = rand(Bool, (10, 10)) + m_copy = copy(m) + QuantumLegos.swap_row!(m, 3, 7) + QuantumLegos.swap_row!(m, 3, 7) + @test m == m_copy + end + let + m = rand(Bool, (10, 10)) + m = BitMatrix(m) + m_copy = copy(m) + QuantumLegos.swap_row!(m, 4, 2) + QuantumLegos.swap_row!(m, 4, 2) + @test m == m_copy + end + end + @testset "align_row!" begin + mat = Bool[ + 1 0 1 1 0 0 1 0 + 0 1 0 1 0 1 0 0 + 1 1 1 0 0 1 0 0 + 1 0 0 1 0 1 0 0 + ] + mat_1 = copy(mat) + @test 1 == QuantumLegos.align_row!(mat_1, 3, Int64[]) + @test mat_1 == Bool[ + 1 1 1 0 0 1 0 0 + 0 1 0 1 0 1 0 0 + 1 0 1 1 0 0 1 0 + 1 0 0 1 0 1 0 0 + ] + mat_1 = copy(mat) + @test 1 == QuantumLegos.align_row!(mat_1, 3, [nothing]) + @test mat_1 == Bool[ + 1 1 1 0 0 1 0 0 + 0 1 0 1 0 1 0 0 + 1 0 1 1 0 0 1 0 + 1 0 0 1 0 1 0 0 + ] + mat_2 = copy(mat) + @test 2 == QuantumLegos.align_row!(mat_2, 3, [1]) + @test mat_2 == Bool[ + 1 0 1 1 0 0 1 0 + 1 1 1 0 0 1 0 0 + 0 1 0 1 0 1 0 0 + 1 0 0 1 0 1 0 0 + ] + mat_3 = copy(mat) + @test 3 == QuantumLegos.align_row!(mat_3, 3, [1, nothing, 3]) + @test mat_3 == Bool[ + 1 0 1 1 0 0 1 0 + 0 1 0 1 0 1 0 0 + 1 1 1 0 0 1 0 0 # 3 + 1 0 0 1 0 1 0 0 # 4 + ] + mat_3_2 = copy(mat_3) + @test 4 == QuantumLegos.align_row!(mat_3, 4, [1, nothing, 3]) + @test mat_3 == mat_3_2 + @test nothing === QuantumLegos.align_row!(mat_3, nothing, [1, 2]) + @test nothing === QuantumLegos.align_row!(mat_3, nothing, [1, 2, nothing]) + @test nothing === QuantumLegos.align_row!(mat_3, nothing, [nothing]) + end + @testset "ref!" begin + @testset "compare with AbstractAlgebra" begin + # test using existing package + using AbstractAlgebra + F₂ = GF(2) # finite field + + """ + Compare self-implemented `ref!` with AbstractAlgebra's `rref!` or `rank`. + """ + function random_test(size::Tuple{T, T}) where {T <: Integer} + mat = rand(Bool, size) + cmat = CheckMatrix(mat) + S = matrix_space(F₂, size...) + cmat_aa = S(F₂.(mat)) + # r_aa, A_aa = AbstractAlgebra.rref(cmat_aa) + # cmat_aa = A_aa.entries .|> ==(1) |> Matrix{Bool} |> CheckMatrix + r_aa = rank(cmat_aa) + r = QuantumLegos.ref!(cmat) + # @info "compare cmat" cmat cmat_aa + @test r == r_aa + end + for _ in 1:10 + random_test((2, 4)) + random_test((4, 8)) + random_test((100, 200)) + end + end + @testset "manual sample" begin + import LinearAlgebra + mat = + Bool[1 0 0 1 1 0 0 0; 0 1 0 0 1 1 0 1; 0 0 1 1 0 1 0 1; 0 1 0 0 1 0 0 1] + cmat = CheckMatrix(mat) + @test QuantumLegos.ref!(cmat) == LinearAlgebra.rank(mat) + @test cmat == CheckMatrix( + Bool[ + 1 0 0 1 1 0 0 0 + 0 1 0 0 1 1 0 1 + 0 0 1 1 0 1 0 1 + 0 0 0 0 0 1 0 0 + ], + 4, + 4, + ) + let + mat = copy(mat) + dependent_row = reduce(.⊻, eachrow(mat)[[1, 2]]) + mat[4, :] = dependent_row + cmat = CheckMatrix(mat) + @test QuantumLegos.ref!(cmat) == LinearAlgebra.rank(mat) == 3 + end + let + mat = copy(mat) + dependent_row = reduce(.⊻, eachrow(mat)[[1, 2]]) + mat = vcat(mat, dependent_row') + dependent_row = reduce(.⊻, eachrow(mat)[[1, 2, 3]]) + mat = vcat(mat, dependent_row') + dependent_row = reduce(.⊻, eachrow(mat)[[2, 4]]) + mat = vcat(mat, dependent_row') + cmat = CheckMatrix(mat) + @test QuantumLegos.ref!(cmat) == rank(mat) == 3 + end + end + @testset "generated group is invariant under ref!" begin + for _ in 1:10 + cmat = CheckMatrix(rand(Bool, (8, 12))) + before = cmat |> generators |> GeneratedPauliGroup |> Set + QuantumLegos.ref!(cmat) + @test cmat |> generators |> GeneratedPauliGroup |> Set |> ==(before) + end + end + end + @testset "eliminate_dependent_row!" begin + @testset "trivial" begin + cmat = CheckMatrix( + Bool[ + 1 0 0 1 0 1 0 1 + 0 1 1 0 0 1 1 0 + 0 0 0 1 1 0 1 0 + 0 0 0 0 0 0 0 0 + ], + ) + @test QuantumLegos.eliminate_dependent_row!(cmat) == CheckMatrix( + Bool[1 0 0 1 0 1 0 1; 0 1 1 0 0 1 1 0; 0 0 0 1 1 0 1 0], + 4, + 3, + ) + end + @testset "less trivial" begin + cmat = CheckMatrix( + Bool[ + 1 0 0 1 0 1 0 1 + 0 1 1 0 0 1 1 0 + 0 0 0 1 1 0 1 0 + 1 1 1 1 0 0 1 1 + ], + ) + @test QuantumLegos.eliminate_dependent_row!(cmat) == CheckMatrix( + Bool[1 0 0 1 0 1 0 1; 0 1 1 0 0 1 1 0; 0 0 0 1 1 0 1 0], + 4, + 3, + ) + end + # TODO: add more? + end + @testset "self_trace!" begin + cmat = CheckMatrix(rand(Bool, (8, 16))) + @test_throws ArgumentError QuantumLegos.self_trace!(cmat, 16, 17) + @test_throws ArgumentError QuantumLegos.self_trace!(cmat, 18, 17) + end + end + + @testset "State" begin + @testset "LegoLeg" begin + @test LegoLeg(0, 1) < LegoLeg(1, 0) + @test LegoLeg(2, 1) > LegoLeg(1, 0) + @test LegoLeg(1, 0) < LegoLeg(1, 1) + @test LegoLeg(1, 1) == LegoLeg(1, 1) + + @test sort(LegoLeg.([(1, 2), (2, 3), (0, 2), (0, 1)])) == + LegoLeg[LegoLeg(0, 1), LegoLeg(0, 2), LegoLeg(1, 2), LegoLeg(2, 3)] + end + @testset "edge" begin + @test edge(1, 2, 3, 4) == (LegoLeg(1, 2), LegoLeg(3, 4)) + @test edge((1, 2, 3, 4)) == (LegoLeg(1, 2), LegoLeg(3, 4)) + @test edge(((1, 2), (3, 4))) == (LegoLeg(1, 2), LegoLeg(3, 4)) + end + @testset "0 lego, 0 leg" begin + @test_throws ArgumentError QuantumLegos.State(Lego{6}[], Tuple{LegoLeg, LegoLeg}[]) + end + @testset "1 lego, 0 leg" begin + stabgens = pauliop.(["IIXX", "XXII", "IZZI", "ZIIZ"]) + lego = QuantumLegos.Lego(stabgens) + state = QuantumLegos.State([lego], Tuple{LegoLeg, LegoLeg}[]) + cmat = QuantumLegos.checkmatrix(stabgens) + @test state == QuantumLegos.State([lego], Tuple{LegoLeg, LegoLeg}[]) + @test state.legos == [lego] + @test state.edges == Tuple{LegoLeg, LegoLeg}[] + @test state.cmat == cmat + end + @testset "2+ legos, 0 leg" begin + stabgens = pauliop.(["IIXX", "XXII", "IZZI", "ZIIZ"]) + lego = QuantumLegos.Lego(stabgens) + state_1 = QuantumLegos.State([lego], Tuple{LegoLeg, LegoLeg}[]) + add_lego!(state_1, lego) + @test state_1.legos == [lego, lego] + @test state_1.edges == Tuple{LegoLeg, LegoLeg}[] + @test state_1.cmat.ngens == 8 + @test state_1.cmat.nlegs == 8 + state_2 = QuantumLegos.State([lego, lego], Tuple{LegoLeg, LegoLeg}[]) + @test state_1 == state_2 + @test all(state_2.cmat.cmat[5:8, 1:4] .== false) # block diagonal + end + @testset "2+ legos, 1+ legs" begin + stabgens = pauliop.(["IIXX", "XXII", "IZZI", "ZIIZ"]) + lego = QuantumLegos.Lego(stabgens) + @test_throws ArgumentError State([lego, lego], edge.([(1, 1, 1, 1)])) + end + @testset "is_connected_to_firstlego" begin + stabilizers = pauliop.(["IIXXXX", "IIZZZZ", "ZIZIZI", "IZIZIZ", "IXIIXX", "XIXXII"]) + lego = Lego(stabilizers) + let + state = + State(fill(lego, 6), edge.([(3, 2, 1, 5), (5, 2, 3, 3), (4, 1, 5, 1)])) + @test QuantumLegos.is_connected_to_firstlego(state) == + BitVector([1, 0, 1, 1, 1, 0]) + end + let + state = State( + fill(lego, 7), + edge.([(3, 2, 1, 5), (5, 2, 3, 3), (4, 1, 5, 1), (2, 2, 3, 1)]), + ) + @test QuantumLegos.is_connected_to_firstlego(state) == + BitVector([1, 1, 1, 1, 1, 0, 0]) + end + end + end + + @testset "Doctest" begin + DocMeta.setdocmeta!( + QuantumLegos, + :DocTestSetup, + :(using QuantumLegos; ENV["JULIA_DEBUG"] = ""); + recursive = true, + ) + doctest(QuantumLegos) + end + + # @testset "Code quality (Aqua.jl)" begin + # Aqua.test_all(QuantumLegos) + # end + # @testset "Code linting (JET.jl)" begin + # JET.test_package(QuantumLegos; target_defined_modules = true) + # end +end