Skip to content

Commit

Permalink
simplify DPS hierarchy
Browse files Browse the repository at this point in the history
  • Loading branch information
araujoms committed Jul 26, 2024
1 parent 3c2177c commit 2cc5136
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 80 deletions.
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ GenericLinearAlgebra = "0.3"
Hypatia = "0.8.1"
JuMP = "1.22"
LinearAlgebra = "1"
Nemo = "0.39 - 0.45, 0.46"
Nemo = "0.39 - 0.46"
Quadmath = "0.5.10"
Random = "1"
Requires = "1"
Expand All @@ -37,8 +37,8 @@ julia = "1.9"
DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
Quadmath = "be4d8f0f-7fa4-5f49-b795-2f01399ab2dd"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["CyclotomicNumbers", "DoubleFloats", "Quadmath", "Random", "Test", "SCS"]
1 change: 1 addition & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ conditional_entropy
```@docs
schmidt_decomposition
entanglement_entropy
random_robustness
entanglement_dps
```

Expand Down
16 changes: 7 additions & 9 deletions src/basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -272,18 +272,16 @@ function _orthonormal_range_svd!(
tol::Union{Real,Nothing} = nothing,
alg = LA.default_svd_alg(A)
) where {T<:Number}
dec = LA.svd!(A, alg=alg)
dec = LA.svd!(A; alg = alg)
tol = isnothing(tol) ? maximum(dec.S) * _eps(T) * minimum(size(A)) : tol
rank = sum(dec.S .> tol)
dec.U[:, 1:rank]
end

_orthonormal_range_svd(A::AbstractMatrix; tol::Union{Real,Nothing} = nothing) = _orthonormal_range_svd!(deepcopy(A); tol = tol)
_orthonormal_range_svd(A::AbstractMatrix; tol::Union{Real,Nothing} = nothing) =
_orthonormal_range_svd!(deepcopy(A); tol = tol)

function _orthonormal_range_qr(
A::SA.AbstractSparseMatrix{T, M};
tol::Union{Real,Nothing} = nothing,
) where {T<:Number, M}
function _orthonormal_range_qr(A::SA.AbstractSparseMatrix{T,M}; tol::Union{Real,Nothing} = nothing) where {T<:Number,M}
dec = LA.qr(A)
tol = isnothing(tol) ? maximum(abs.(dec.R)) * _eps(T) : tol
rank = sum(abs.(LA.Diagonal(dec.R)) .> tol)
Expand All @@ -300,12 +298,12 @@ Tolerance `tol` is used to compute the rank and is automatically set if not prov
function orthonormal_range(
A::SA.AbstractMatrix{T};
mode::Integer = -1,
tol::Union{Real, Nothing} = nothing,
tol::Union{Real,Nothing} = nothing
) where {T<:Number}
mode == 1 && SA.issparse(A) && throw(ArgumentError("SVD does not work with sparse matrices, use a dense matrix."))
mode == -1 && (mode = SA.issparse(A) ? 0 : 1)

return (mode == 0 ? _orthonormal_range_qr(A; tol=tol) : _orthonormal_range_svd(A; tol=tol))
return (mode == 0 ? _orthonormal_range_qr(A; tol = tol) : _orthonormal_range_svd(A; tol = tol))
end
export orthonormal_range

Expand All @@ -326,7 +324,7 @@ function symmetric_projection(::Type{T}, dim::Integer, n::Integer; partial::Bool
end
perms = Combinatorics.permutations(1:n)
for perm in perms
P .+= permutation_matrix(dim, perm; sp=true)
P .+= permutation_matrix(dim, perm; sp = true)
end
P ./= length(perms)
if partial
Expand Down
143 changes: 102 additions & 41 deletions src/entanglement.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ function _equal_sizes(arg::AbstractVecOrMat)
end

"""
schmidt_decomposition(ψ::AbstractVector, dims::AbstractVector{<:Integer})
schmidt_decomposition(ψ::AbstractVector, dims::AbstractVector{<:Integer} = _equal_sizes(ψ))
Produces the Schmidt decomposition of `ψ` with subsystem dimensions `dims`. Returns the (sorted) Schmidt coefficients λ and isometries U, V such that
kron(U', V')*`ψ` is of Schmidt form.
Produces the Schmidt decomposition of `ψ` with subsystem dimensions `dims`. If the argument `dims` is omitted equally-sized subsystems are assumed. Returns the (sorted) Schmidt coefficients λ and isometries U, V such that kron(U', V')*`ψ` is of Schmidt form.
Reference: [Schmidt decomposition](https://en.wikipedia.org/wiki/Schmidt_decomposition).
"""
function schmidt_decomposition::AbstractVector, dims::AbstractVector{<:Integer})
function schmidt_decomposition::AbstractVector, dims::AbstractVector{<:Integer} = _equal_sizes(ψ))
length(dims) != 2 && throw(ArgumentError("Two subsystem sizes must be specified."))
m = transpose(reshape(ψ, dims[2], dims[1])) #necessary because the natural reshaping would be row-major, but Julia does it col-major
U, λ, V = LA.svd(m)
Expand All @@ -22,20 +21,11 @@ end
export schmidt_decomposition

"""
schmidt_decomposition(ψ::AbstractVector)
Produces the Schmidt decomposition of `ψ` assuming equally-sized subsystems. Returns the (sorted) Schmidt coefficients λ and isometries U, V such that kron(U', V')*`ψ` is of Schmidt form.
Reference: [Schmidt decomposition](https://en.wikipedia.org/wiki/Schmidt_decomposition).
"""
schmidt_decomposition::AbstractVector) = schmidt_decomposition(ψ, _equal_sizes(ψ))
entanglement_entropy(ψ::AbstractVector, dims::AbstractVector{<:Integer} = _equal_sizes(ψ))
Computes the relative entropy of entanglement of a bipartite pure state `ψ` with subsystem dimensions `dims`. If the argument `dims` is omitted equally-sized subsystems are assumed.
"""
entanglement_entropy(ψ::AbstractVector, dims::AbstractVector{<:Integer})
Computes the relative entropy of entanglement of a bipartite pure state `ψ` with subsystem dimensions `dims`.
"""
function entanglement_entropy::AbstractVector, dims::AbstractVector{<:Integer})
function entanglement_entropy::AbstractVector, dims::AbstractVector{<:Integer} = _equal_sizes(ψ))
length(dims) != 2 && throw(ArgumentError("Two subsystem sizes must be specified."))
max_sys = argmax(dims)
ρ = partial_trace(ketbra(ψ), max_sys, dims)
Expand All @@ -44,18 +34,11 @@ end
export entanglement_entropy

"""
entanglement_entropy(ψ::AbstractVector)
Computes the relative entropy of entanglement of a bipartite pure state `ψ` assuming equally-sized subsystems.
"""
entanglement_entropy::AbstractVector) = entanglement_entropy(ψ, _equal_sizes(ψ))
entanglement_entropy(ρ::AbstractMatrix, dims::AbstractVector = _equal_sizes(ρ), n::Integer = 1)
Lower bounds the relative entropy of entanglement of a bipartite state `ρ` with subsystem dimensions `dims` using level `n` of the DPS hierarchy. If the argument `dims` is omitted equally-sized subsystems are assumed.
"""
entanglement_entropy(ρ::AbstractMatrix, dims::AbstractVector)
Lower bounds the relative entropy of entanglement of a bipartite state `ρ` with subsystem dimensions `dims`.
"""
function entanglement_entropy::AbstractMatrix{T}, dims::AbstractVector) where {T}
function entanglement_entropy::AbstractMatrix{T}, dims::AbstractVector = _equal_sizes(ρ), n::Integer = 1) where {T}
LA.ishermitian(ρ) || throw(ArgumentError("State needs to be Hermitian"))
length(dims) != 2 && throw(ArgumentError("Two subsystem sizes must be specified."))

Expand All @@ -67,13 +50,10 @@ function entanglement_entropy(ρ::AbstractMatrix{T}, dims::AbstractVector) where

if is_complex
JuMP.@variable(model, σ[1:d, 1:d], Hermitian)
σT = partial_transpose(σ, 2, dims)
JuMP.@constraint(model, σT in JuMP.HermitianPSDCone())
else
JuMP.@variable(model, σ[1:d, 1:d], Symmetric)
σT = partial_transpose(σ, 2, dims)
JuMP.@constraint(model, σT in JuMP.PSDCone())
end
_dps_constraints!(model, σ, dims, n; is_complex)
JuMP.@constraint(model, LA.tr(σ) == 1)

vec_dim = Cones.svec_length(Ts, d)
Expand All @@ -89,13 +69,6 @@ function entanglement_entropy(ρ::AbstractMatrix{T}, dims::AbstractVector) where
return JuMP.objective_value(model), LA.Hermitian(JuMP.value.(σ))
end

"""
entanglement_entropy(ρ::AbstractMatrix)
Lower bounds the relative entropy of entanglement of a bipartite state `ρ` assuming equally-sized subsystems.
"""
entanglement_entropy::AbstractMatrix) = entanglement_entropy(ρ, _equal_sizes(ρ))

"""
_svec(M::AbstractMatrix, ::Type{R})
Expand Down Expand Up @@ -193,9 +166,8 @@ function entanglement_dps(
verbose::Bool = false,
solver = Hypatia.Optimizer{_solver_type(T)},
witness::Bool = true,
noise::Union{AbstractMatrix,Nothing} = nothing,
noise::Union{AbstractMatrix,Nothing} = nothing
) where {T<:Number}

LA.ishermitian(ρ) || throw(ArgumentError("State must be Hermitian"))
sn >= 1 || throw(ArgumentError("Schmidt number must be larger of equal to 1"))

Expand All @@ -210,7 +182,7 @@ function entanglement_dps(

# Dimension of the extension space w/ bosonic symmetries: AA' dim. + `n` copies of BB'
sym_dim = d1 * binomial(n + d2 - 1, d2 - 1)
V = kron(LA.I(d1), symmetric_projection(T, d2, n; partial=true)) # Bosonic subspace isometry
V = kron(LA.I(d1), symmetric_projection(T, d2, n; partial = true)) # Bosonic subspace isometry

model = JuMP.GenericModel{_solver_type(T)}()
JuMP.@variable(model, Q[1:sym_dim, 1:sym_dim] in JuMP.HermitianPSDCone())
Expand Down Expand Up @@ -246,7 +218,7 @@ function entanglement_dps(
end

obj = JuMP.objective_value(model)
if witness
if witness
if sn == 1
wit = JuMP.dual.(wit_ctr)
wit = status == MOI.INFEASIBLE ? -wit / LA.tr(wit * ρ) : JuMP.value.(lifted)
Expand All @@ -258,3 +230,92 @@ function entanglement_dps(
return obj
end
export entanglement_dps

"""
random_robustness(
ρ::AbstractMatrix{T},
dims::AbstractVector{<:Integer} = _equal_sizes(ρ),
n::Integer = 1;
ppt::Bool = true,
verbose::Bool = false,
solver = Hypatia.Optimizer{_solver_type(T)})
Lower bounds the random robustness of state `ρ` with subsystem dimensions `dims` using level `n` of the DPS hierarchy. Argument `ppt` indicates whether to include the partial transposition constraints.
"""
function random_robustness(
ρ::AbstractMatrix{T},
dims::AbstractVector{<:Integer} = _equal_sizes(ρ),
n::Integer = 1;
ppt::Bool = true,
verbose::Bool = false,
solver = Hypatia.Optimizer{_solver_type(T)}
) where {T<:Number}
LA.ishermitian(ρ) || throw(ArgumentError("State must be Hermitian"))

is_complex = (T <: Complex)
wrapper = is_complex ? LA.Hermitian : LA.Symmetric

model = JuMP.GenericModel{_solver_type(T)}()

JuMP.@variable(model, λ)
noisy_state = wrapper+ λ * LA.I(size(ρ, 1)))
_dps_constraints!(model, noisy_state, dims, n; ppt, is_complex)
JuMP.@objective(model, Min, λ)

JuMP.set_optimizer(model, solver)
# JuMP.set_optimizer(model, Dualization.dual_optimizer(solver)) #necessary for acceptable performance with some solvers
!verbose && JuMP.set_silent(model)
JuMP.optimize!(model)

if JuMP.is_solved_and_feasible(model)
W = JuMP.dual(model[:witness_constraint])
W = wrapper(LA.Diagonal(W) + 0.5(W - LA.Diagonal(W))) #this is a workaround for a bug in JuMP
return JuMP.objective_value(model), W
else
return "Something went wrong: $(raw_status(model))"
end
end
export random_robustness

"""
_dps_constraints!(model::JuMP.GenericModel, ρ::AbstractMatrix, dims::AbstractVector{<:Integer}, n::Integer; ppt::Bool = true, is_complex::Bool = true)
Constrains state `ρ` of dimensions `dims` in JuMP model `model` to respect the DPS constraints of level `n`.
"""
function _dps_constraints!(
model::JuMP.GenericModel{T},
ρ::AbstractMatrix,
dims::AbstractVector{<:Integer},
n::Integer;
ppt::Bool = true,
is_complex::Bool = true
) where {T}
LA.ishermitian(ρ) || throw(ArgumentError("State must be Hermitian"))

dA, dB = dims
ext_dims = [dA; repeat([dB], n)]

# Dimension of the extension space w/ bosonic symmetries: A dim. + `n` copies of B
d = dA * binomial(n + dB - 1, n)
V = kron(LA.I(dA), symmetric_projection(T, dB, n; partial = true)) # Bosonic subspace isometry

if is_complex
psd_cone = JuMP.HermitianPSDCone()
wrapper = LA.Hermitian
else
psd_cone = JuMP.PSDCone()
wrapper = LA.Symmetric
end

JuMP.@variable(model, s[1:d, 1:d] in psd_cone)
lifted = wrapper(V * s * V')
reduced = partial_trace(lifted, 3:n+1, ext_dims)

JuMP.@constraint(model, witness_constraint, ρ == reduced)

if ppt
for i in 2:n+1
JuMP.@constraint(model, partial_transpose(lifted, 2:i, ext_dims) in psd_cone)
end
end
end
20 changes: 10 additions & 10 deletions src/multilinear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ function permute_systems!(X::AbstractVector{T}, perm::AbstractVector{<:Integer},

p = _idxperm(perm, dims)
permute!(X, p)
return X
return X
end
export permute_systems!

Expand All @@ -248,17 +248,17 @@ Permutes the order of the subsystems of vector `X`, which is composed by two sub
"""
permute_systems!(X::AbstractVector, perm::AbstractVector{<:Integer}) = permute_systems!(X, perm, _equal_sizes(X))

@doc"""
permute_systems(X::AbstractMatrix, perm::Vector, dims::Vector)
@doc """
permute_systems(X::AbstractMatrix, perm::Vector, dims::Vector)
Permutes the order of the subsystems of the square matrix `X`, which is composed by square subsystems of dimensions `dims`, according to the permutation `perm`.
""" permute_systems(X::AbstractMatrix, perm::AbstractVector, dims::Vector; rows_only::Bool=false)
Permutes the order of the subsystems of the square matrix `X`, which is composed by square subsystems of dimensions `dims`, according to the permutation `perm`.
""" permute_systems(X::AbstractMatrix, perm::AbstractVector, dims::Vector; rows_only::Bool = false)
for (T, wrapper) in
[(:AbstractMatrix, :identity), (:(LA.Hermitian), :(LA.Hermitian)), (:(LA.Symmetric), :(LA.Symmetric))]
@eval begin
function permute_systems(X::$T, perm::Vector{<:Integer}, dims::Vector{<:Integer}; rows_only::Bool=false)
function permute_systems(X::$T, perm::Vector{<:Integer}, dims::Vector{<:Integer}; rows_only::Bool = false)
perm == 1:length(perm) && return X

p = _idxperm(perm, dims)
return rows_only ? $wrapper(X[p, 1:end]) : $wrapper(X[p, p])
end
Expand Down Expand Up @@ -293,8 +293,8 @@ export permute_systems
Unitary that permutes subsystems of dimension `dims` according to the permutation `perm`.
If `dims` is an Integer, assumes there are `length(perm)` subsystems of equal dimensions `dims`.
"""
function permutation_matrix(dims::Union{Integer,Vector{<:Integer}}, perm::AbstractVector{<:Integer}; sp::Bool=true)
dims = dims isa Integer ? [dims for _=1:length(perm)] : dims
permute_systems(LA.I(prod(dims), sp), perm, dims; rows_only=true)
function permutation_matrix(dims::Union{Integer,Vector{<:Integer}}, perm::AbstractVector{<:Integer}; sp::Bool = true)
dims = dims isa Integer ? [dims for _ = 1:length(perm)] : dims
permute_systems(LA.I(prod(dims), sp), perm, dims; rows_only = true)
end
export permutation_matrix
2 changes: 1 addition & 1 deletion test/basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@
end
end
end
end
end
26 changes: 18 additions & 8 deletions test/entanglement.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,25 @@
end
end
@testset "DPS hierarchy" begin
for d in 2:4
ρ = state_ghz(d, 2)
o, w = entanglement_dps(ρ)
@test o == false && isapprox(tr(w * ρ), -1, atol = 1e-4, rtol = 1e-4)
@test isapprox(entanglement_dps(ρ, witness=false), 1 / (d + 1), atol = 1e-4, rtol = 1e-4)
for R in [Float64, Double64]
ρ = state_ghz(R, 2, 2)
s, W = random_robustness(ρ)
@test eltype(W) == R
@test s 0.5 atol = 1e-5 rtol = 1e-5
@test dot(ρ, W) -s atol = 1e-5 rtol = 1e-5
T = Complex{R}
ρ = state_ghz(T, 2, 2)
s, W = random_robustness(ρ)
@test eltype(W) == T
@test s 0.5 atol = 1e-5 rtol = 1e-5
@test dot(ρ, W) -s atol = 1e-5 rtol = 1e-5
end
o, w = entanglement_dps(state_ghz(2, 2), 3)
@test o == false && isapprox(tr(w * state_ghz(2, 2)), -1, atol = 1e-4, rtol = 1e-4)
# This is slightly long (but smallest case) and requires SCS otherwise it will run out of memory
@test isapprox(entanglement_dps(state_ghz(3, 2); sn=2, witness=false, solver=SCS.Optimizer), 0.625, atol = 1e-3, rtol=1e-3)
@test isapprox(
entanglement_dps(state_ghz(3, 2); sn = 2, witness = false, solver = SCS.Optimizer),
0.625,
atol = 1e-3,
rtol = 1e-3
)
end
end
Loading

0 comments on commit 2cc5136

Please sign in to comment.