Strawberry Fields Intermediate Free 7/7 in series 18 min read

Squeezed States and Quadrature Measurements in Strawberry Fields

Learn to create squeezed, displaced, and rotated states in Strawberry Fields, measure position and momentum quadratures via homodyne detection, and visualize the results with Wigner functions.

What you'll learn

  • Strawberry Fields
  • squeezed states
  • quadrature measurements
  • homodyne detection
  • continuous-variable
  • Wigner function

Prerequisites

  • Python proficiency
  • Beginner quantum computing concepts (superposition, entanglement)
  • Linear algebra basics

Squeezed states are among the most important resources in continuous-variable (CV) quantum computing. By reducing the uncertainty in one quadrature below the vacuum level, squeezed states enable precision measurement, entanglement generation, and quantum error correction in photonic systems. In this tutorial, you will create and manipulate squeezed states using Strawberry Fields, measure their quadratures with homodyne detection, and visualize the resulting quantum states in phase space using Wigner functions.

Prerequisites

Make sure Strawberry Fields is installed along with NumPy and Matplotlib for visualization:

pip install strawberryfields numpy matplotlib

Quadratures and the Heisenberg Uncertainty Principle

In CV quantum computing, each optical mode is described by two conjugate quadrature operators: position X^\hat{X} and momentum P^\hat{P}. These satisfy the canonical commutation relation [X^,P^]=i[\hat{X}, \hat{P}] = i, which gives rise to the Heisenberg uncertainty principle for quadratures:

ΔXΔP12\Delta X \cdot \Delta P \geq \frac{1}{2}

The vacuum state 0|0\rangle saturates this bound with equal uncertainty in both quadratures: ΔX=ΔP=12\Delta X = \Delta P = \frac{1}{\sqrt{2}}. Squeezing reduces the uncertainty in one quadrature at the expense of increasing it in the other, while still satisfying the uncertainty relation.

Creating a Squeezed Vacuum State

The squeezing gate Sgate(r, phi) transforms the vacuum into a squeezed state. The parameter r controls the squeezing strength, and phi controls the squeezing angle. When phi=0, position uncertainty is reduced by a factor of ere^{-r} while momentum uncertainty grows by ere^{r}.

import strawberryfields as sf
from strawberryfields.ops import Sgate
import numpy as np

# Create a single-mode squeezed vacuum state
prog = sf.Program(1)

with prog.context as q:
    Sgate(0.8, 0.0) | q[0]  # Squeeze with r=0.8, phi=0 (squeeze in X)

eng = sf.Engine("gaussian")
result = eng.run(prog)
state = result.state

# Check the quadrature variances
# For the Gaussian backend, the covariance matrix encodes all second moments
cov = state.cov()
print(f"Covariance matrix:\n{cov}")

# Extract variances (diagonal elements)
# Strawberry Fields uses the xp-ordering: [x0, x1, ..., p0, p1, ...]
var_x = cov[0, 0] / 2  # Variance of X quadrature
var_p = cov[1, 1] / 2  # Variance of P quadrature

print(f"\nVariance in X: {var_x:.4f}")
print(f"Variance in P: {var_p:.4f}")
print(f"Product ΔX·ΔP: {np.sqrt(var_x * var_p):.4f}")
print(f"Heisenberg bound: 0.5000")

The squeezed vacuum is a minimum uncertainty state, so the product ΔXΔP\Delta X \cdot \Delta P equals exactly 12\frac{1}{2}. The key point is that one quadrature has been traded for the other.

Comparing Vacuum, Coherent, and Squeezed States

To build intuition, let us compare three fundamental CV states side by side: the vacuum, a coherent state (created by displacement), and a squeezed state.

import strawberryfields as sf
from strawberryfields.ops import Sgate, Dgate
import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 3, figsize=(15, 5))

states_info = [
    ("Vacuum |0⟩", []),
    ("Coherent |α=1.5⟩", [("Dgate", (1.5, 0.0))]),
    ("Squeezed S(0.8)|0⟩", [("Sgate", (0.8, 0.0))]),
]

for idx, (title, ops_list) in enumerate(states_info):
    prog = sf.Program(1)
    with prog.context as q:
        for op_name, params in ops_list:
            if op_name == "Dgate":
                Dgate(*params) | q[0]
            elif op_name == "Sgate":
                Sgate(*params) | q[0]

    eng = sf.Engine("gaussian")
    result = eng.run(prog)
    state = result.state

    # Compute Wigner function over a grid
    xvec = np.arange(-4, 4, 0.1)
    pvec = np.arange(-4, 4, 0.1)
    W = state.wigner(0, xvec, pvec)

    X, P = np.meshgrid(xvec, pvec)
    axes[idx].contourf(X, P, W, levels=50, cmap="RdBu_r")
    axes[idx].set_title(title)
    axes[idx].set_xlabel("X quadrature")
    axes[idx].set_ylabel("P quadrature")
    axes[idx].set_aspect("equal")

plt.tight_layout()
plt.savefig("quadrature_comparison.png", dpi=150)
plt.show()

In this visualization, the vacuum appears as a symmetric circle centered at the origin. The coherent state is the same circle but displaced to X=1.5X = 1.5. The squeezed state is an ellipse, compressed along the XX axis and stretched along PP. All three are Gaussian states with positive Wigner functions everywhere.

Displacement and Rotation of Squeezed States

Combining squeezing with displacement (Dgate) and rotation (Rgate) allows you to position and orient the squeezed ellipse anywhere in phase space. This is the foundation of many CV quantum protocols.

import strawberryfields as sf
from strawberryfields.ops import Sgate, Dgate, Rgate
import numpy as np
import matplotlib.pyplot as plt

prog = sf.Program(1)

with prog.context as q:
    Sgate(1.0, 0.0) | q[0]       # Squeeze in X
    Rgate(np.pi / 4) | q[0]      # Rotate by 45 degrees in phase space
    Dgate(2.0, np.pi / 6) | q[0] # Displace to (2.0, pi/6) in polar coordinates

eng = sf.Engine("gaussian")
result = eng.run(prog)
state = result.state

# Compute and plot the Wigner function
xvec = np.arange(-5, 5, 0.1)
pvec = np.arange(-5, 5, 0.1)
W = state.wigner(0, xvec, pvec)

X, P = np.meshgrid(xvec, pvec)
plt.figure(figsize=(7, 6))
plt.contourf(X, P, W, levels=50, cmap="RdBu_r")
plt.colorbar(label="W(x, p)")
plt.title("Displaced and Rotated Squeezed State")
plt.xlabel("X quadrature")
plt.ylabel("P quadrature")
plt.axis("equal")
plt.tight_layout()
plt.savefig("displaced_rotated_squeezed.png", dpi=150)
plt.show()

# Print state properties
means = state.means()
print(f"Mean X: {means[0]:.4f}")
print(f"Mean P: {means[1]:.4f}")
print(f"Mean photon number: {state.mean_photon(0)[0]:.4f}")

The Rgate(theta) rotates the squeezing ellipse by angle theta in phase space. After rotation, the reduced uncertainty axis is no longer aligned with XX or PP but lies along a rotated direction. This is useful for protocols that require squeezing along a specific measurement basis.

Homodyne Detection: Measuring Quadratures

Homodyne detection measures a single quadrature of the optical mode. In Strawberry Fields, MeasureHomodyne(phi) measures the quadrature at angle phi, where phi=0 corresponds to the XX quadrature and phi=pi/2 corresponds to PP. The shorthand operators MeasureX and MeasureP measure XX and PP directly.

import strawberryfields as sf
from strawberryfields.ops import Sgate, Dgate, MeasureHomodyne
import numpy as np

# Measure X quadrature of a displaced squeezed state
prog_x = sf.Program(1)
with prog_x.context as q:
    Sgate(1.0, 0.0) | q[0]
    Dgate(2.0, 0.0) | q[0]
    MeasureHomodyne(0) | q[0]  # Measure X quadrature (phi=0)

eng = sf.Engine("gaussian")
result_x = eng.run(prog_x, shots=1000)

x_samples = result_x.samples[:, 0]
print(f"X quadrature: mean = {np.mean(x_samples):.4f}, std = {np.std(x_samples):.4f}")

# Measure P quadrature of the same state
prog_p = sf.Program(1)
with prog_p.context as q:
    Sgate(1.0, 0.0) | q[0]
    Dgate(2.0, 0.0) | q[0]
    MeasureHomodyne(np.pi / 2) | q[0]  # Measure P quadrature (phi=pi/2)

eng_p = sf.Engine("gaussian")
result_p = eng_p.run(prog_p, shots=1000)

p_samples = result_p.samples[:, 0]
print(f"P quadrature: mean = {np.mean(p_samples):.4f}, std = {np.std(p_samples):.4f}")

print(f"\nExpected: X mean ~ 2*sqrt(2*hbar) due to displacement")
print(f"Expected: X std < P std due to squeezing in X")

The key observation is that the standard deviation of the XX measurements is significantly smaller than the standard deviation of the PP measurements. This is the direct signature of squeezing: we have reduced noise in XX at the cost of increased noise in PP.

Visualizing the Wigner Function in Detail

The Wigner function W(x,p)W(x, p) is a quasi-probability distribution in phase space. For Gaussian states, it is always non-negative and takes the shape of a 2D Gaussian. The contour plot of the Wigner function directly reveals the state’s mean position, orientation, and squeezing parameters.

import strawberryfields as sf
from strawberryfields.ops import Sgate, Dgate, Rgate
import numpy as np
import matplotlib.pyplot as plt

# Create four states to compare Wigner functions
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

configs = [
    ("Vacuum", [], (0, 0)),
    ("Squeezed r=0.5", [("S", 0.5, 0.0)], (0, 1)),
    ("Squeezed r=1.0", [("S", 1.0, 0.0)], (1, 0)),
    ("Squeezed r=1.5", [("S", 1.5, 0.0)], (1, 1)),
]

xvec = np.arange(-5, 5, 0.05)
pvec = np.arange(-5, 5, 0.05)

for title, ops, (row, col) in configs:
    prog = sf.Program(1)
    with prog.context as q:
        for op in ops:
            if op[0] == "S":
                Sgate(op[1], op[2]) | q[0]

    eng = sf.Engine("gaussian")
    result = eng.run(prog)
    state = result.state

    W = state.wigner(0, xvec, pvec)
    X, P = np.meshgrid(xvec, pvec)

    ax = axes[row][col]
    im = ax.contourf(X, P, W, levels=50, cmap="RdBu_r")
    ax.set_title(title)
    ax.set_xlabel("X")
    ax.set_ylabel("P")
    ax.set_aspect("equal")
    plt.colorbar(im, ax=ax)

plt.suptitle("Wigner Functions: Increasing Squeezing", fontsize=14)
plt.tight_layout()
plt.savefig("wigner_squeezing_comparison.png", dpi=150)
plt.show()

As the squeezing parameter rr increases, the ellipse becomes progressively narrower in XX and wider in PP. At r=1.5r = 1.5 (about 13 dB of squeezing), the position uncertainty is reduced to e1.50.22e^{-1.5} \approx 0.22 times the vacuum level.

Squeezing in Decibels

Experimentally, squeezing is often reported in decibels. The conversion is:

squeezing (dB)=10log10(e2r)=20rlog10(e)8.686r\text{squeezing (dB)} = -10 \log_{10}(e^{-2r}) = 20r \cdot \log_{10}(e) \approx 8.686 \cdot r

Current state-of-the-art experiments achieve around 15 dB of squeezing (corresponding to r1.73r \approx 1.73). Xanadu’s photonic processors typically operate with a few dB of squeezing per mode.

import numpy as np

# Convert between squeezing parameter r and dB
r_values = [0.1, 0.5, 1.0, 1.5, 2.0]
for r in r_values:
    db = 20 * r * np.log10(np.e)
    print(f"r = {r:.1f}{db:.1f} dB squeezing")

Summary and Next Steps

In this tutorial, you learned how to create squeezed states using the Sgate operator, position them in phase space with Dgate and Rgate, and measure their quadratures using homodyne detection. The Wigner function visualization provides a powerful tool for understanding the shape and orientation of CV quantum states.

Squeezed states are foundational to many CV quantum protocols. Two-mode squeezing generates entanglement (EPR states), which is essential for CV quantum teleportation and cluster state computation. In measurement-based quantum computing, large entangled cluster states built from squeezed modes provide the substrate for universal CV quantum computation.

For further exploration, consider investigating two-mode squeezed states and their entanglement properties, or look into the sf.apps module for practical applications of squeezing in Gaussian boson sampling.

Was this tutorial helpful?