Skip to content

Commit 326c946

Browse files
authored
Fix incorrect negativity and partial_transpose for arbitrary subsystem dimension (#539)
1 parent 75f33c4 commit 326c946

File tree

3 files changed

+40
-20
lines changed

3 files changed

+40
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- Changes to `SteadyStateODESolver`. ([#537])
1212
- Introduce the tolerances for `steadystate` terminate condition (two new fields: `terminate_reltol = 1e-5` and `terminate_abstol = 1e-7`)
1313
- Fix keyword argument handling for `SteadyStateODESolver` before passing to `mesolve`.
14+
- Fix incorrect `negativity` and `partial_transpose` for arbitrary subsystem dimension. ([#539])
1415

1516
## [v0.34.1]
1617
Release date: 2025-08-23
@@ -308,3 +309,4 @@ Release date: 2024-11-13
308309
[#531]: https://github.com/qutip/QuantumToolbox.jl/issues/531
309310
[#536]: https://github.com/qutip/QuantumToolbox.jl/issues/536
310311
[#537]: https://github.com/qutip/QuantumToolbox.jl/issues/537
312+
[#539]: https://github.com/qutip/QuantumToolbox.jl/issues/539

src/negativity.jl

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,20 @@ end
8585

8686
# for dense matrices
8787
function _partial_transpose::QuantumObject{Operator}, mask::Vector{Bool})
88-
mask2 = [1 + Int(i) for i in mask]
88+
nsys = length(mask)
89+
mask2 = reverse([mask[s] ? 2 : 1 for s in 1:nsys])
8990
# mask2 has elements with values equal to 1 or 2
90-
# 1 - the subsystem don't need to be transposed
91-
# 2 - the subsystem need be transposed
91+
# 1 - the subsystem (in reversed order) don't need to be transposed
92+
# 2 - the subsystem (in reversed order) need to be transposed
9293

93-
nsys = length(mask2)
94-
dims = dimensions_to_dims(get_dimensions_to(ρ))
94+
dims_rev = reverse(dimensions_to_dims(get_dimensions_to(ρ)))
9595
pt_dims = reshape(Vector(1:(2*nsys)), (nsys, 2))
9696
pt_idx = [
9797
[pt_dims[n, mask2[n]] for n in 1:nsys] # origin value in mask2
9898
[pt_dims[n, 3-mask2[n]] for n in 1:nsys] # opposite value in mask2 (1 -> 2, and 2 -> 1)
9999
]
100100
return QuantumObject(
101-
reshape(permutedims(reshape.data, (dims..., dims...)), pt_idx), size(ρ)),
101+
reshape(permutedims(reshape.data, (dims_rev..., dims_rev...)), pt_idx), size(ρ)),
102102
Operator(),
103103
Dimensions.dimensions.to),
104104
)
@@ -110,7 +110,8 @@ function _partial_transpose(
110110
mask::Vector{Bool},
111111
) where {DimsType<:AbstractDimensions}
112112
M, N = size(ρ)
113-
dimsTuple = Tuple(dimensions_to_dims(get_dimensions_to(ρ)))
113+
dims_rev = reverse(Tuple(dimensions_to_dims(get_dimensions_to(ρ))))
114+
mask_rev = reverse(mask)
114115
colptr = ρ.data.colptr
115116
rowval = ρ.data.rowval
116117
nzval = ρ.data.nzval
@@ -130,13 +131,13 @@ function _partial_transpose(
130131
I_pt[n] = i
131132
J_pt[n] = j
132133
else
133-
ket_pt = [Base._ind2sub(dimsTuple, i)...]
134-
bra_pt = [Base._ind2sub(dimsTuple, j)...]
135-
for sys in findall(m -> m, mask)
134+
ket_pt = [Base._ind2sub(dims_rev, i)...]
135+
bra_pt = [Base._ind2sub(dims_rev, j)...]
136+
for sys in findall(m -> m, mask_rev)
136137
@inbounds ket_pt[sys], bra_pt[sys] = bra_pt[sys], ket_pt[sys]
137138
end
138-
I_pt[n] = Base._sub2ind(dimsTuple, ket_pt...)
139-
J_pt[n] = Base._sub2ind(dimsTuple, bra_pt...)
139+
I_pt[n] = Base._sub2ind(dims_rev, ket_pt...)
140+
J_pt[n] = Base._sub2ind(dims_rev, bra_pt...)
140141
end
141142
V_pt[n] = nzval[p]
142143
end

test/core-test/negativity_and_partial_transpose.jl

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@testitem "Negativity and Partial Transpose" begin
22
@testset "negativity" begin
3-
rho = (1 / 40) * Qobj(
3+
rho1 = (1 / 40) * Qobj(
44
[
55
15 1 1 15
66
1 5 -3 1
@@ -9,15 +9,32 @@
99
];
1010
dims = (2, 2),
1111
)
12-
Neg = negativity(rho, 1)
13-
@test Neg 0.25
14-
@test negativity(rho, 2) Neg
15-
@test negativity(rho, 1; logarithmic = true) log2(2 * Neg + 1)
16-
@test_throws ArgumentError negativity(rho, 3)
12+
Neg1 = negativity(rho1, 1)
13+
@test Neg1 0.25
14+
@test negativity(rho1, 2) Neg1
15+
@test negativity(rho1, 1; logarithmic = true) log2(2 * Neg1 + 1)
16+
@test_throws ArgumentError negativity(rho1, 3)
17+
18+
# a maximally entanglment state with subsystem dimension (3, 2):
19+
# (|1,0⟩ - i|2,1⟩) / √2
20+
rho2_d = ket2dm((tensor(basis(3, 1), basis(2, 0)) - 1im * tensor(basis(3, 2), basis(2, 1))) / 2)
21+
rho2_s = to_sparse(rho2_d)
22+
@test negativity(rho2_d, 1) 0.5
23+
@test negativity(rho2_d, 2) 0.5
24+
@test negativity(rho2_s, 1) 0.5
25+
@test negativity(rho2_s, 2) 0.5
26+
27+
# a separable state with subsystem dimension (3, 2)
28+
rho3_d = tensor(rand_dm(3), rand_dm(2))
29+
rho3_s = to_sparse(rho3_d)
30+
@test abs(negativity(rho3_d, 1)) < 1e-10
31+
@test abs(negativity(rho3_d, 2)) < 1e-10
32+
@test abs(negativity(rho3_s, 1)) < 1e-10
33+
@test abs(negativity(rho3_s, 2)) < 1e-10
1734

1835
@testset "Type Inference (negativity)" begin
19-
@inferred negativity(rho, 1)
20-
@inferred negativity(rho, 1; logarithmic = true)
36+
@inferred negativity(rho1, 1)
37+
@inferred negativity(rho1, 1; logarithmic = true)
2138
end
2239
end
2340

0 commit comments

Comments
 (0)