QuantumLegos.jl/src/game.jl

281 lines
7.7 KiB
Julia

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