Skip to content

Non-Gaussian states ​

qkd.fock holds one mode as a density matrix on levels 0…d−1, with the conventions and (len(ps), len(xs)) grid layout of qkd.gaussian: the layers compare with no conversion. Scope.

python
from qkd import fock

Constructors ​

cutoff is keyword-only, 1≤d≤512. The cutoff is part of the state: the only check on a derived figure is convergence under at_cutoff(d).

Coherent(11.0, 0.0) at the default cutoff 40
Norm discarded99.79 %
Reported Wigner negativity2.5072, above every resource state here, on a state whose true negativity is zero
physical(), purity(), Gaussian shadowTrue, 1.0, bona fide
Artefact versus discarded population1.4 to 6.7 orders of magnitude above it, so trunc_error() does not bound it
ParameterUnitDefaultDescription
Vacuum(*, cutoff)levels40Number(0).
Number(n, *, cutoff)—required|n⟩, 0≤n<d. Zero truncation error.
Coherent(x, p, *, cutoff)internal quadraturesrequiredβ=(x+ip)/2, as gaussian.Coherent(x, p). The Poissonian tail reaches every level, so discarded() is non-zero.
Squeezed(r, *, cutoff)nepersrequiredSqueezed vacuum, x squeezed for r>0, as gaussian.Squeezed(r). True negativity zero at every r: the grid's calibration state.
Thermal(nbar, *, cutoff)photonsrequiredn¯≥0. Mixed and Gaussian, exercising both halves of the non-Gaussianity measure.
Cat(x, p, odd=False, *, cutoff)internal quadraturesrequiredEven or odd superposition of the coherent states at ±(x,p). An odd cat at the origin raises: |β⟩−|−β⟩ vanishes.
Gkp(logical, delta, *, cutoff)— / vacuum unitsrequired, cutoff=NoneFinite-energy grid state, logical ∈{0,1}, δ∈[0.05,2]; squeezing −20log10⁡δ dB, so δ=0.3 is 10.5 dB. None derives the cutoff as gkp_levels(delta); a pinned value is honoured; a derived cutoff that cannot hold the state raises.
Density(rho)—requiredSquare complex density matrix; its dimension is the cutoff and discarded() is 0. Validated for shape, finiteness, Hermiticity to 10−9, unit trace and positive semidefiniteness — the number-basis bona fide condition.

Module constants ​

NameValueBounds
CUTOFF40Default cutoff for every constructor except Gkp. Not safe for a large displacement.
MAX_LEVELS512The native core's cap, so a derived cutoff clamps before the core refuses.
GKP_SCALE, GKP_TAIL8.0, 1e-6The numerator of gkp_levels, and the discarded norm a derived-cutoff grid state may reach before its constructor raises. A pinned cutoff is never refused.
gkp_levels(delta)ceil(GKP_SCALE / delta**2), capped at MAX_LEVELSLevels needed for a discarded population below GKP_TAIL track 7.1/δ2 over δ∈[0.15,0.6]: 66, 89, 128 and 200 at δ = 0.35, 0.3, 0.25, 0.2. δ≲0.12 is refused — no cutoff within the cap converges.
EDGE3Default levels for tail(), and the depth trunc_error() sums over.
STEP0.06Default phase-space sample spacing for window().

Truncation ​

MethodCatches
discarded()norm thrown away at construction — a state born too big for its cutoff
tail(levels=3)population in the top few levels — a state born inside its cutoff, then pushed against the ceiling by a displacement or a channel
trunc_error()the worse of the two, as one number to threshold on

A truncated matrix is renormalised, so it passes every physicality check. Neither instrument bounds the kernel: both measure population, and the displaced-parity kernel oscillates, so the truncated tail contributes to ∬|W| with alternating sign.

OperationTruncation cost
rotate(theta)none — diagonal in the number basis
loss(eta)none — the Kraus operators only move population down, so it is trace-preserving inside the cutoff and shrinks the tail
displace(x, p)yes — the truncated displacement is not unitary: it loses norm and pushes population up. The loss is folded into discarded(), not renormalised away

Phase space ​

W and Q as in the Gaussian layer, in dxdp. A general density matrix has no closed form, so W uses the Cahill–Glauber displaced-parity kernel on a rescaled three-term Laguerre recursion.

python
from qkd import fock

grid = [-1e-9, 0.0, 1e-9]
vac = fock.Vacuum(cutoff=24)

vac.wigner(grid, grid)[1, 1]   # 0.3183098861837907  == 1/pi
vac.husimi(grid, grid)[1, 1]   # 0.15915494309189535 == 1/(2 pi)

W|1⟩(0,0)=−1/π.

Wigner negativity as the witness ​

TheoremStatement
Spekkens 2008negativity of a quasi-probability representation and the failure of generalised non-contextuality are the same statement
Mari & Eisert 2012; Veitch et al. 2013Wigner-positive states, under Gaussian operations and homodyne readout, are efficiently classically simulable

The computed quantity is the Kenfack–Życzkowski indicator,

δ(ρ)=∬|W(x,p)|dxdp−1,

zero iff W≥0 everywhere. Its logarithm is the resource monotone of Albarelli et al. 2018; qkd reports the volume.

The anchor ​

δ(|1⟩)=4/e−2=0.4261226… in closed form.

python
from qkd import fock

fock.Number(1, cutoff=12).negativity().value   # 0.4262150
fock.Vacuum(cutoff=12).negativity().value      # -9.1e-07, i.e. zero

Four significant figures; the residual is the default grid's quadrature error — validation quotes the same state on a stated grid. A number state discards nothing at any d>n. The volume grows monotonically from |0⟩ to |4⟩.

The grid is part of the answer ​

negativity() returns a Volume, not a bare float: the integral runs over a window nobody chose explicitly.

MemberDescription
value∬|W|−1. float(vol) returns it.
trunc_errorThe state's truncation error.
cutoffThe cutoff the state was held at.
points(len(xs), len(ps)).
span(x_lo, x_hi, p_lo, p_hi).
step(dx, dp).
window(state, reach=None, step=0.06)
reach=Nonefive standard deviations of the state's second moments plus its displacement, on the wider quadrature, held to 2cutoff+5
Fivea Gaussian leaves 1.1×10−6 of its mass outside a 5σ square and 1.4×10−5 outside 4.5σ, the latter already the size of a truncated state's spurious volume
The capa state too big for its cutoff has the renormalised matrix's moments: Coherent(11, 0) at cutoff 40 asks for reach 25.65, an 8572 grid. Levels 0..d−1 cannot reach past the top Hermite function's turning point 2d−1 plus a Gaussian tail — the bound gkp() sizes its quadrature with. Capping moves volumes by at most 1.2×10−4
stepresolves the kink in |W| at the zero contour; too coarse a step reports a smaller volume
Statetrunc_errorVolume, true value 0
Squeezed(0.8), cutoff 252.2×10−54.5×10−3
the same at cutoff 40—2.7×10−4
the same at cutoff 60—4.1×10−6

Refining the grid does not move those; raising the cutoff does, monotonically. A volume is evidence of negativity when it stops changing as the cutoff grows. Volume over trunc_error is not a screen: a wholly spurious 2.505 scores 2.5, a wholly spurious 4.9×10−3 scores 1400, a mostly physical GKP volume 27.

Two witnesses ​

non_gaussianity() is the relative-entropy non-Gaussianity S(ρG)−S(ρ), ρG the Gaussian state with ρ's first and second moments (Genoni & Paris 2010) — shadow(). It returns a Divergence: value, trunc_error, cutoff, and float().

Truncation moves both halves, so a Gaussian state can score large and no cheap bound separates the columns:

At cutoff 40ArtefactResource
non_gaussianity(), bitsSqueezed(2.0) 2.284, Coherent(11.0, 0.0) 5.324odd cat 2.16, Number(10) 4.83

Negativity implies non-Gaussianity; the converse fails. An equal mixture of two coherent states is non-Gaussian with a strictly positive Wigner function: non_gaussianity() sees it, negativity() does not. Loss separates them at a calculable point:

python
from qkd import fock

cat = fock.Cat(3.0, 0.0, True, cutoff=40)

for eta in (1.0, 0.7, 0.51, 0.5, 0.49):
    st = cat.loss(eta)
    print(eta, st.negativity().value, float(st.non_gaussianity()))
# 1.00   0.600926   2.5555
# 0.70   0.024912   1.3117
# 0.51   0.000048   1.0931
# 0.50  -0.000000   1.0798
# 0.49  -0.000000   1.0662

Negativity reaches zero at η=12 and stays there; non-Gaussianity falls smoothly through it. The same η=12 kills |1⟩'s negativity, where the parity ⟨(−1)n⟩=1−2η crosses zero. Negativity is the resource, non-Gaussianity a symptom.

Parity ​

parity() returns ⟨(−1)n^⟩=πW(0,0) — no grid, and the quantity an experiment measures. A negative value certifies negativity at the origin: sufficient, not necessary. The odd cat sits at −1, the even cat at +1.

The Gaussian shadow ​

moments() returns (⟨x⟩,⟨p⟩) and the 2×2 covariance; shadow() is gaussian.Moments(mean, cov) over it.

python
from qkd import fock

fock.Coherent(1.0, 0.0).shadow().cov
# array([[0.5, 0. ],
#        [0. , 0.5]])
What the shadow is used for
Hudson as a live testa coherent, squeezed or thermal state built in both layers must give pointwise-equal Wigner grids
Non-Gaussian statesthe shadow stays non-negative where the state it shadows does not
S(ρG)≥S(ρ)a Gaussian maximises entropy at fixed second moments, so a negative non_gaussianity() is numerical noise

Scope ​

No protocol consumes this layer: Gaussian-modulation CV-QKD is Gaussian end to end, the discrete-modulation analysis works from a covariance matrix, and the click family is written in photons.

Limit
Single modeno two-mode gates, no tensor network, no MPS — Architecture
GKP is the finite-energy approximationa Gaussian-enveloped comb of Gaussian peaks in the position wavefunction, at xs=2sπ for |0L⟩ and (2s+1)π for |1L⟩; not the ideal grid state
Operationspure loss, rotation and displacement only. No thermal-loss channel, no cubic-phase gate, no Kerr evolution, no photon-number-resolving measurement

Read surface ​

python
from qkd import fock

odd = fock.Cat(3.0, 0.0, True, cutoff=30)
odd.parity()                 # -1.0 exactly
odd.negativity().value       # 0.600926  at cutoff 30
odd.trunc_error()            # 9.1e-13 -- how much the cutoff cost

gkp = fock.Gkp(0, 0.3)       # cutoff derived: gkp_levels(0.3) == 89
gkp.cutoff                   # 89
gkp.negativity().value       # 0.926206  at cutoff 89
gkp.trunc_error()            # 9.4e-08

thermal = fock.Thermal(0.5, cutoff=30)
thermal.negativity().value   # -9.8e-07 -- Gaussian, so Hudson applies
float(thermal.non_gaussianity())   # 4.6e-13  -- and it is Gaussian too

The cat reads 0.600926 at cutoff 30 and at at_cutoff(45). The GKP state:

Cutoffnegativity().valuetrunc_error()
60 (the former fixed default)0.9332832.6×10−5
800.9267814.2×10−7
89 (gkp_levels(0.3), the derived default)0.9262069.4×10−8
1100.9258782.2×10−9

trunc_error improves four orders of magnitude while the volume moves in the third decimal: at 60 the volume's error was ≈7×10−3, about 280× the trunc_error. The derived 89 cuts the overstatement from 0.8 % to 0.04 %.

MethodReturns
cutoffLevels this state is held on. A property
at_cutoff(d)The same state on d levels — the convergence check, and the only guard on a Volume
matrix()The density matrix
populations()P(n)=ρnn
eigenvalues()Spectrum, ascending
photons()Mean photon number
purity()A Purity: value is Tr(ρ2) of the truncated state, with discarded and the certified lower bound least
entropy()Von Neumann entropy in bits
parity()⟨(−1)n^⟩=πW(0,0)
physical(tol=1e-9)Hermitian, unit trace and positive semidefinite
moments(), shadow()The Gaussian shadow, as a (mean, cov) pair of arrays or as a gaussian.State
wigner(xs, ps), husimi(xs, ps)Phase-space grids
negativity(xs=None, ps=None)A Volume; both axes default to window(self)
non_gaussianity()A Divergence: S(ρG)−S(ρ) in bits, with its truncation error and cutoff
discarded(), tail(levels=3), trunc_error()The truncation instrumentation

Array dtypes, shapes and aliasing: Bulk returns are numpy arrays. Volume, Divergence and Purity are frozen records carrying __float__.

purity() is the truncated state's purity
Mechanismthe constructors renormalise whatever fits under the cutoff, rescaling every retained element by 1/kept and Tr(ρ2) by 1/kept2
Thermal(200) at cutoff 40keeps 18 % of its norm and reports 0.025083 against the true 1/(1+2×200)=0.002494 — ten times high, on a state that passes every physical check
Signnot one-signed: a state whose discarded tail is purer than its retained head reads too low
leastthe one certified statement, Tr(PρP)2≤Tr(ρ2) for the truncating projector P, and Tr(PρP)2=kept2×value
Abovenothing bounds it usefully — the discarded tail could be pure. entropy() and eigenvalues() carry the same distortion with no bound at all

References ​

  • Spekkens 2008 — PRL 101, 020401 (2008)
  • Genoni and Paris 2010 — PRA 82, 052341 (2010)
  • Mari & Eisert 2012 — PRL 109, 230503 (2012)
  • Veitch et al. 2013 — NJP 15, 013037 (2013)
  • Albarelli et al. 2018 — PRA 98, 052350 (2018)