Skip to the content.

GBS (Gaussian Boson Sampling) integration

qpubench models Gaussian Boson Sampling in src/qpubench/schemas/mirrors/mqsdk_photoq.py (the GBS section). This covers the Gaussian-state / hafnian-based formalism — distinct from the LOQC section of the same module, which uses the permanent-based / Fock-state formalism for linear-optics chips.

  LOQC (permanent) GBS (hafnian)
State representation Fock states Covariance matrix (Gaussian states)
Amplitude formula Permanent Hafnian
Hardware MZI chips, boson samplers Xanadu X8, Borealis TDM
Typical gate BeamsplitterSpec / MZISpec SqueezingGateSpec / S2GateSpec

Computing model: ComputingModel.GBS. Qubit modality: QubitModality.PHOTONIC


Direct GBS sampling

from qpubench.schemas.mirrors.mqsdk_photoq import (
    SqueezingGateSpec, S2GateSpec, RotationGateSpec,
    InterferometerSpec, GBSProgramSpec, GBSMeasurementType,
    GBSSamplingConfig, GBSSamplingResult, GBSSample,
)
from qpubench.schemas.backend import BackendSpec

# 4-mode GBS program
prog = GBSProgramSpec(
    num_modes=4,
    squeezing_params=[
        SqueezingGateSpec(mode_index=0, r=0.8, phi=0.0),
        SqueezingGateSpec(mode_index=1, r=0.8, phi=0.0),
        SqueezingGateSpec(mode_index=2, r=0.8, phi=0.0),
        SqueezingGateSpec(mode_index=3, r=0.8, phi=0.0),
    ],
    interferometer=InterferometerSpec(
        mode_indices=[0, 1, 2, 3],
        unitary_real=[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],  # 4×4 identity
        unitary_imag=[0.0] * 16,
        source="random_haar",
    ),
    measurement_type=GBSMeasurementType.FOCK,
)

config = GBSSamplingConfig(
    program=prog,
    num_samples=1000,
    backend_type="gaussian_simulator",
)

sample = GBSSample(photon_numbers=[2, 0, 1, 1])
print(sample.total_photons)   # 4
print(sample.num_clicks)      # 3

backend = BackendSpec.strawberry_fields_gaussian(num_modes=4)

Store results in QuantumResult.vendor_results["gbs_sampling"].


Hafnian computation

from qpubench.schemas.mirrors.mqsdk_photoq import (
    GaussianStateSpec, HafnianMatrixSpec,
    HafnianComputationSpec, HafnianResult,
    QuadratureOrdering,
)
from qpubench.schemas.primitives import ComplexNumber

# Covariance matrix (XP_BLOCKS ordering: [x1,x2,...,p1,p2,...])
state = GaussianStateSpec(
    num_modes=2,
    mean_vector=[0.0, 0.0, 0.0, 0.0],
    covariance_matrix=[1.5, 0.0, 0.0, 0.0,    # flattened 4×4
                       0.0, 1.5, 0.0, 0.0,
                       0.0, 0.0, 0.5, 0.0,
                       0.0, 0.0, 0.0, 0.5],
    quadrature_ordering=QuadratureOrdering.XP_BLOCKS,
)

haf_spec = HafnianComputationSpec(
    B_real=[0.0, 0.6, 0.6, 0.0],   # 2×2 flattened
    B_imag=[0.0, 0.0, 0.0, 0.0],
    output_pattern=[1, 1],           # detect 1 photon in each mode
)

haf_result = HafnianResult(
    hafnian=ComplexNumber(re=0.6, im=0.0),
    probability=0.0947,
    method="thewalrus",
)

Graph-based GBS (clique finding)

Encode a graph adjacency matrix into a GBS device via Takagi decomposition, then use the photon-number samples to find dense subgraphs (cliques).

from qpubench.schemas.mirrors.mqsdk_photoq import (
    GBSGraphConfig, GraphScalingMethod,
    TakagiDecompositionSpec, GBSCliqueFindingResult,
)

config = GBSGraphConfig(
    adjacency_matrix=[0.0, 1.0, 1.0, 0.0,   # 4-node graph, flattened
                      1.0, 0.0, 1.0, 1.0,
                      1.0, 1.0, 0.0, 1.0,
                      0.0, 1.0, 1.0, 0.0],
    num_nodes=4,
    num_photons=2,
    num_samples=10_000,
    scaling_method=GraphScalingMethod.DIVIDE_BY_MAX,
)

takagi = TakagiDecompositionSpec(
    num_modes=4,
    singular_values=[0.95, 0.87, 0.44, 0.12],
    unitary_real=[1, 0, 0, 0, 0, 1, 0, 0,   # 4×4 Takagi unitary
                  0, 0, 1, 0, 0, 0, 0, 1],
    unitary_imag=[0.0] * 16,
)

result = GBSCliqueFindingResult(
    config=config,
    takagi=takagi,
    num_samples_completed=10_000,
    shrunk_cliques=[[0, 1, 2], [1, 2, 3]],
    searched_cliques=[[0, 1, 2], [0, 2, 3], [1, 2, 3]],
    mean_density=0.82,
    mean_clique_size=3.0,
    max_clique_size=3,
    min_clique_size=3,
)

Store in QuantumResult.gbs_clique_finding.


Borealis TDM GBS

Xanadu Borealis uses a time-domain multiplexed (TDM) architecture with three fibre-loop delays [1, 6, 36] to realise 216 effective modes:

from qpubench.schemas.mirrors.mqsdk_photoq import (
    TDMDelaySpec, TDMGBSConfig, TDMGBSResult, TDMSqueezingLevel,
)

delays = TDMDelaySpec(delays=[1, 6, 36], effective_modes=216)

tdm_config = TDMGBSConfig(
    delays=delays,
    squeezing_level=TDMSqueezingLevel.HIGH,
    num_shots=50_000,
    crop=True,
    num_modes_requested=216,
    device_arn="arn:aws:braket:us-east-1::device/qpu/xanadu/Borealis",
)

tdm_result = TDMGBSResult(
    config=tdm_config,
    num_modes_effective=216,
    num_shots_completed=50_000,
    mean_photon_per_mode=1.21,
    sampling_time_s=47.3,
)

Store in QuantumResult.tdm_gbs.

backend = BackendSpec.xanadu_borealis(via_braket=True)

CV cluster states

from qpubench.schemas.mirrors.mqsdk_photoq import ClusterStateSpec, GaussianStateType
import math

cluster = ClusterStateSpec(
    state_type=GaussianStateType.CLUSTER_1D,
    num_nodes=5,
    squeezing_r=0.5,
    measurement_angles=[0.0, math.pi / 4, math.pi / 2, 0.0, math.pi / 4],
    boundary_condition="open",
)

Pseudo-PNRD (click-counting) detectors & the four simulation methods

The photoq paper “Classical simulation of Gaussian boson sampling with click-counting detectors” studies GBS devices read out by pseudo photon-number-resolving detectors (pPNRD): a mode is demultiplexed across N on/off (click) detectors, and the detector reports the number k ∈ {0, …, N} of branches that clicked. This is the GBSMeasurementType.PSEUDO_PNR detector model.

from qpubench.schemas.mirrors.mqsdk_photoq import (
    PseudoPNRDSpec, SimulationMethod, ClickPatternProbabilityResult,
    KensingtonianResult, MethodComparison, MPSSimulationConfig,
)
from qpubench.schemas.primitives import ComplexNumber

# One mode demultiplexed across N=4 on/off detectors.
det = PseudoPNRDSpec(num_branches=4, multiplexing="spatial")
det.collision_error(2)   # 0.4 — P(two of 2 photons share one of 4 branches)

# The full click-pattern distribution P(k), computed by method i.
dist = ClickPatternProbabilityResult(
    num_modes=2,
    num_branches=4,
    method=SimulationMethod.KENSINGTONIAN_FORMULA,
    click_patterns=[[0, 0], [1, 0], [0, 1], [1, 1]],
    probabilities=[0.61, 0.14, 0.14, 0.11],
    total_probability=1.0,
    computation_time_s=0.002,
)

# Method i also exposes the raw matrix-function value per pattern.
ken = KensingtonianResult(
    click_pattern=[1, 1], num_branches=4,
    value=ComplexNumber(re=0.11, im=0.0), probability=0.11,
)

The SimulationMethod enum names the paper’s four methods (and one variant):

SimulationMethod Paper Idea photoq code
KENSINGTONIAN_FORMULA i Kensingtonian matrix function — the click-counting analogue of the hafnian (Eq. 26, arXiv:2305.00853) methods/kenform/
HAFNIAN_MODIFIED ii Fock/hafnian probabilities modified by the pPNRD model P_{k,n}(N) methods/kenhaf/
TENSOR_NETWORK_MPS iii Matrix-product-state simulation with a truncation-fidelity cutoff f_t methods/mps/, mps_fast/
BRUTE_FORCE_POVM iv Explicit POVM trace (demultiplex + vacuum projection); THERMAL_POVM is the thermal-Gaussian variant methods/utility/ppnrd.py

Store a distribution in QuantumResult.vendor_results["click_pattern_probability"].

Method comparison

The paper’s Figs. 5–11 compare the methods on one circuit (timing, TVD, KL, fidelity vs a reference method):

mc = MethodComparison(
    num_modes=4, num_branches=4,
    reference_method=SimulationMethod.BRUTE_FORCE_POVM,
    methods=[SimulationMethod.KENSINGTONIAN_FORMULA, SimulationMethod.TENSOR_NETWORK_MPS],
    computation_time_s={"kensingtonian_formula": 0.01, "tensor_network_mps": 1.2},
    total_variation_distance={"tensor_network_mps": 3e-4},
    fidelity={"tensor_network_mps": 0.9997},
    mps_truncation_fidelity=0.999, mps_bond_dimension=100,
    circuit_label="clements_4mode",
)

The MPS parameters double as the DTU QCloud tn-sampling job knobs (see below):

mps = MPSSimulationConfig(
    num_modes=8, physical_dimension=8, bond_dimension=100,
    truncation_fidelity=0.999, num_branches=4,
)

Store a comparison in QuantumResult.vendor_results["method_comparison"].


ORCA PT Series (time-bin interferometer)

ORCA’s PT-1/PT-2 are time-bin interferometers: one physical beamsplitter plus one or more fibre delay loops, applied across num_modes time bins. Each loop couples every adjacent pair of bins, so a loop needs num_modes - 1 beamsplitter angles.

from qpubench.schemas.mirrors.mqsdk_photoq import (
    TimeBinInterferometerSpec, PTSeriesSamplingConfig,
    PTSeriesSamplingResult, PTSeriesInputType,
)
from qpubench.schemas.backend import BackendSpec

tbi = TimeBinInterferometerSpec(
    num_modes=8, num_loops=1,
    input_type=PTSeriesInputType.GBS,     # or FOCK / DISTINGUISHABLE (classical control)
    squeezing=[0.5] * 8,
    beamsplitter_angles=[0.4] * 7,        # length num_loops * (num_modes - 1)
)
tbi.num_angles_expected                   # 7

cfg = PTSeriesSamplingConfig(interferometer=tbi, num_samples=1000, device="PT-2")
res = PTSeriesSamplingResult(
    config=cfg, samples=[[0, 1, 0, 2, 1, 0, 0, 1]], mean_photon_number=1.2,
)

backend = BackendSpec.orca_pt_series(num_modes=8, num_loops=1, device="PT-2")

Store in QuantumResult.vendor_results["pt_series_sampling"].


DTU QCloud (REST API v1)

qcloud.dtu.dk exposes a Bearer-token REST API with two GBS job types: tn-covariance (build a covariance matrix server-side) and tn-sampling (tensor-network GBS sampling — the successor of the DASQ Kensingtonian sampler, aligned with the paper’s MPS method).

from qpubench.schemas.mirrors.mqsdk_photoq import (
    QCloudJobType, QCloudJobSpec, QCloudJobResult,
    TNCovarianceParams, TNSamplingParams,
)
from qpubench.schemas.backend import BackendSpec

cov = TNCovarianceParams(nmodes=64, r_db=8.0, loss=0.5, basis="pi4")
cov_job = QCloudJobSpec(
    job_type=QCloudJobType.TN_COVARIANCE, params=cov.model_dump(), worker="catlab",
)

tn = TNSamplingParams(cov_matrix=[1.0, 0.0, 0.0, 1.0], d=8, chi=100, dd=1, N=10_000, n=1)
sampling_job = QCloudJobSpec(
    job_type=QCloudJobType.TN_SAMPLING, params=tn.model_dump(), worker="tn-sampling",
)

result = QCloudJobResult(spec=sampling_job, status="succeeded", samples=[[0, 1, 2]])

backend = BackendSpec.dtu_qcloud(job_type="tn-sampling")

Store in QuantumResult.vendor_results["qcloud_job"].


Xanadu Aurora dataset

Aurora is the modular photonic quantum computer of “Scaling and networking a modular photonic quantum computer” (Nature 638, 2025): 35 chips, 84 squeezers, 36 PNRDs, 12 qubit modes per clock cycle. It is a published dataset (public S3 bucket xanadu-aurora-data), not a programmable device — two experiment sets: cluster-state acquisition and the decoder demo.

from qpubench.schemas.mirrors.mqsdk_photoq import AuroraDatasetSpec, AuroraExperiment
from qpubench.schemas.backend import BackendSpec

spec = AuroraDatasetSpec(
    experiment=AuroraExperiment.DECODER_DEMO,
    condition="signal",            # signal | random | vacuum (decoder demo)
    batch_index=3,
    s3_key="decoder_demo/signal/batch_3/quadratures.npy",
)

backend = BackendSpec.xanadu_aurora(experiment="decoder_demo")

Backends

from qpubench.schemas.backend import BackendSpec

BackendSpec.strawberry_fields_gaussian(num_modes=8)   # local Gaussian simulator
BackendSpec.xanadu_x8(num_modes=8)                    # Xanadu X8 hardware (PNR)
BackendSpec.xanadu_borealis(via_braket=False)         # native SF RemoteEngine
BackendSpec.xanadu_borealis(via_braket=True)          # via AWS Braket BraketEngine
BackendSpec.orca_pt_series(num_modes=8, device="PT-2")  # ORCA PT Series TBI (simulated/hardware)
BackendSpec.dtu_qcloud(job_type="tn-sampling")        # DTU QCloud REST API v1
BackendSpec.xanadu_aurora(experiment="cluster_state") # Xanadu Aurora dataset