Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# News

## v1.3.6 - 2026-03-19

- Allow `homodyne` and `rand(Homodyne, ...)` to measure single-mode Gaussian states while keeping `ptrace` on all modes unsupported.

## v1.3.5 - 2026-03-19

- `express(gaussian_state, ::QuantumOpticsRepr)` is now implemented, letting Gabs.jl pure Gaussian states to be converted to Kets from QuantumOptics.jl
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Gabs"
uuid = "0eb812ee-a11f-4f5e-b8d4-bb8a44f06f50"
authors = ["Andrew Kille"]
version = "1.3.5"
version = "1.3.6"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
10 changes: 5 additions & 5 deletions src/homodyne.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function homodyne(
basis = state.basis
nmodes = basis.nmodes
indlength = length(indices)
indlength < nmodes || throw(ArgumentError(Gabs.INDEX_ERROR))
indlength <= nmodes || throw(ArgumentError(Gabs.INDEX_ERROR))
indlength == length(angles) || throw(ArgumentError(Gabs.GENERALDYNE_ERROR))
# perform conditional mapping of Gaussian quantum state
result′, a, A = _homodyne_filter(rng, state, indices, angles; squeeze)
Expand Down Expand Up @@ -124,7 +124,7 @@ function homodyne(
basis = state.basis
nmodes = basis.nmodes
indlength = length(indices)
indlength < nmodes || throw(ArgumentError(Gabs.INDEX_ERROR))
indlength <= nmodes || throw(ArgumentError(Gabs.INDEX_ERROR))
indlength == length(angles) || throw(ArgumentError(Gabs.GENERALDYNE_ERROR))
# perform conditional mapping of Gaussian quantum state
result′, a, A = _homodyne_filter(rng, state, indices, angles; squeeze)
Expand Down Expand Up @@ -203,7 +203,7 @@ function Base.rand(
) where {Tm,Tc,R,G}
basis = state.basis
indlength = length(indices)
indlength < basis.nmodes || throw(ArgumentError(Gabs.INDEX_ERROR))
indlength <= basis.nmodes || throw(ArgumentError(Gabs.INDEX_ERROR))
indlength == length(angles) || throw(ArgumentError(Gabs.GENERALDYNE_ERROR))
nmodes′ = basis.nmodes - indlength
mean, covar = state.mean, state.covar
Expand Down Expand Up @@ -266,7 +266,7 @@ function Base.rand(
basis = state.basis
nmodes = basis.nmodes
indlength = length(indices)
indlength < nmodes || throw(ArgumentError(Gabs.INDEX_ERROR))
indlength <= nmodes || throw(ArgumentError(Gabs.INDEX_ERROR))
indlength == length(angles) || throw(ArgumentError(Gabs.GENERALDYNE_ERROR))
nmodes′ = nmodes - indlength
mean, covar = state.mean, state.covar
Expand Down Expand Up @@ -385,4 +385,4 @@ function _homodyne_filter(
# promote output array type to ensure it matches the input array type
result′ = Gabs._promote_output_vector(Tm, resultmean, 2*indlength)
return result′, a, A
end
end
2 changes: 1 addition & 1 deletion src/states.jl
Original file line number Diff line number Diff line change
Expand Up @@ -840,4 +840,4 @@ function _sympspectrum(M::AbstractMatrix{<:Number}, select::Function; pre::Union
M = isnothing(post) ? M : M * post
M = isnothing(invscale) ? imag.(eigvals(M)) : imag.(eigvals(M)) ./ invscale
return filter(x -> select(x), M)
end
end
20 changes: 18 additions & 2 deletions test/test_measurements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,22 @@
@test isapprox(state.mean[1:2], zeros(2), atol=1e-12)
@test isapprox(state.covar[1:2, 1:2], Matrix{Float64}(I,2,2), atol=1e-12)
end

for basis in (qpairbasis, qblockbasis)
seed = 1234
st = coherentstate(basis, 0.3 + 0.2im)
h_int = homodyne(MersenneTwister(seed), st, 1, [0.0]; squeeze)
h_vec = homodyne(MersenneTwister(seed), st, [1], [0.0]; squeeze)
@test h_int.result == h_vec.result
@test h_int.state == h_vec.state
@test isapprox(h_int.state, vacuumstate(basis), atol = 1e-12)
@test_throws ArgumentError ptrace(h_int.state, 1)

samples_int = rand(MersenneTwister(seed), Homodyne, st, 1, [0.0]; shots = 2, squeeze)
samples_vec = rand(MersenneTwister(seed), Homodyne, st, [1], [0.0]; shots = 2, squeeze)
Comment thread
Krastanov marked this conversation as resolved.
@test samples_int == samples_vec
@test size(samples_int) == (2, 2)
end

st = squeezedstate(QuadPairBasis(4), 0.5, π/2)
st_block = changebasis(QuadBlockBasis, st)
Expand Down Expand Up @@ -172,6 +188,6 @@

@test_throws ArgumentError rand(Homodyne, rs_qpair, collect(1:5), [0.0])
@test_throws ArgumentError rand(Homodyne, rs_qblock, collect(1:5), [π/2])
end
end
end
end
end
5 changes: 4 additions & 1 deletion test/test_states.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@
@test ptrace(state, [2, 3]) == s1
@test_throws ArgumentError ptrace(state, [1, 2, 3, 4])

single_mode = coherentstate(basis, alpha)
@test_throws ArgumentError ptrace(single_mode, 1)

sstatic = coherentstate(SVector{2}, SMatrix{2,2}, basis, alpha)
tpstatic = sstatic ⊗ sstatic ⊗ sstatic
@test ptrace(tpstatic, 1) == sstatic ⊗ sstatic
Expand Down Expand Up @@ -169,4 +172,4 @@
@test isapprox(det(s_qpair.covar), prod(abs2, spec_qpair), atol=1e-3)
@test isapprox(det(s_qblock.covar), prod(abs2, spec_qblock), atol=1e-3)
end
end
end
Loading