Examples

This section provides examples of how to use the qlass package for various quantum computing tasks on photonic devices.

Basic Circuit Compilation

Compiling a quantum circuit from Qiskit to a Perceval processor:

from qiskit import QuantumCircuit
from qlass import compile

# Create a Qiskit circuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)

# Compile to Perceval processor
processor = compile(qc)

# Run the processor using Perceval's Sampler
from perceval.algorithm import Sampler
sampler = Sampler(processor)
results = sampler.samples(1000)

Resource-Aware Compilation

Analyzing quantum circuits against hardware configurations to estimate real-world performance:

from qiskit import QuantumCircuit
from qlass.compiler import ResourceAwareCompiler, HardwareConfig, generate_report

# Create a quantum circuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)

# Define hardware configuration for a photonic chip
chip_config = HardwareConfig(
    photon_loss_component_db=0.05,
    fusion_success_prob=0.11,
    hom_visibility=0.95
)

# Compile with resource analysis
compiler = ResourceAwareCompiler(config=chip_config)
processor = compiler.compile(qc)

# Access and display the analysis report
report = processor.analysis_report
generate_report(report)

The report provides insights into component counts, estimated photon loss, and overall success probability for running the circuit on the specified hardware.

Variational Quantum Eigensolver (VQE)

VQE with Sampling Executor

Standard VQE using sampling-based executor with Hartree-Fock ansatz:

import numpy as np
from qlass.vqe import VQE
from qlass.quantum_chemistry import LiH_hamiltonian, brute_force_minimize
from qlass.vqe.ansatz import hf_ansatz
from perceval.algorithm import Sampler
import warnings
warnings.filterwarnings('ignore')

# Create molecular Hamiltonian
ham = LiH_hamiltonian(num_electrons=2, num_orbitals=1)

# Define executor using Hartree-Fock ansatz
def executor(params, pauli_string):
    processors = hf_ansatz(1, 1, params, pauli_string, method="WFT", cost="VQE")
    samplers = Sampler(processors)
    samples = samplers.samples(10_000)
    return samples

# Initialize VQE solver
vqe = VQE(
    hamiltonian=ham,
    executor=executor,
    num_params=4,
)

# Run optimization
vqe_energy = vqe.run(max_iterations=50, verbose=True)

VQE with Sampling Executor using Piquasso

Standard VQE using sampling-based executor with `Piquasso https://piquasso.readthedocs.io/`_:

import numpy as np
import piquasso as pq
from piquasso.dual_rail_encoding import (
    dual_rail_encode_from_qiskit, get_bosonic_qubit_samples,
)
from qiskit import transpile
from qiskit.circuit.library import n_local

from qlass.quantum_chemistry import LiH_hamiltonian
from qlass.utils import rotate_qubits
from qlass.vqe import VQE

def le_ansatz_piquasso(lp, pauli_string):
    num_qubits = len(pauli_string)
    ansatz = n_local(num_qubits, "ry", "cx", reps=1, entanglement="linear")

    ansatz_assigned = ansatz.assign_parameters(lp)
    ansatz_transpiled = transpile(
        ansatz_assigned, basis_gates=["u3", "cx"], optimization_level=3,
    )

    ansatz_rot = rotate_qubits(pauli_string, ansatz_transpiled.copy())
    program = dual_rail_encode_from_qiskit(ansatz_rot)

    return program

def _sample_from_prob_dist(prob_dist, shots):
    states = np.asarray(list(prob_dist.keys()), dtype=int)
    probs = np.fromiter(prob_dist.values(), dtype=float)

    cdf = np.cumsum(probs)
    r = np.random.rand(shots)
    idx = np.searchsorted(cdf, r)

    return states[idx].tolist()

# Define an executor function that uses the linear entangled ansatz
def executor(params, pauli_string):
    program = le_ansatz_piquasso(params, pauli_string)
    simulator = pq.SamplingSimulator(config=pq.Config(cutoff=5))
    state = simulator.execute(program).state
    state.normalize()
    fock_probs = state.fock_probabilities_map
    samples = _sample_from_prob_dist(fock_probs, shots=10_000)
    qubit_samples = get_bosonic_qubit_samples(samples)
    return qubit_samples

# Create molecular Hamiltonian
hamiltonian = LiH_hamiltonian(num_electrons=2, num_orbitals=1)

# Initialize VQE solver
vqe = VQE(hamiltonian=hamiltonian, executor=executor, num_params=4)

# Run optimization
vqe_energy = vqe.run(max_iterations=10, verbose=True)

VQE with Qubit Unitary Executor

Using unitary matrices directly for VQE:

import numpy as np
from qlass.vqe import VQE
from qlass.quantum_chemistry import LiH_hamiltonian, brute_force_minimize
from scipy.linalg import expm

# Define unitary executor using parameterized generators
def unitary_executor(params):
    num_qubits = 2
    dim = 2**num_qubits

    # Define Pauli matrices
    I = np.eye(2)
    X = np.array([[0, 1], [1, 0]])
    Y = np.array([[0, -1j], [1j, 0]])
    Z = np.array([[1, 0], [0, -1]])

    # Create 2-qubit generators
    generators = [
        np.kron(Y, I),
        np.kron(I, Y),
        np.kron(X, X),
        np.kron(Z, Z),
    ]

    # Build Hamiltonian for time evolution
    H = sum(params[i] * generators[i] for i in range(len(params)))

    # Compute unitary: U = exp(-iH)
    return expm(-1j * H)

hamiltonian = LiH_hamiltonian(num_electrons=2, num_orbitals=1)

vqe = VQE(
    hamiltonian=hamiltonian,
    executor=unitary_executor,
    num_params=4,
    executor_type="qubit_unitary"
)

vqe_energy = vqe.run(max_iterations=50, verbose=True)

VQE with Photonic Unitary Executor

Using photonic unitaries with dual-rail encoding and post-selection:

import numpy as np
from qlass.vqe import VQE
from qlass.quantum_chemistry import LiH_hamiltonian_tapered, brute_force_minimize
from qlass.utils import linear_circuit_to_unitary
from perceval.converters import QiskitConverter
from qiskit.circuit.library import n_local

qiskit_converter = QiskitConverter(backend_name="Naive", noise_model=None)

def executor(params):
    num_qubits = 4
    # Create variational ansatz circuit
    ansatz = n_local(num_qubits, 'ry', 'cx', reps=1, entanglement='linear')
    ansatz_assigned = ansatz.assign_parameters(params)

    # Convert to Perceval processor
    processor = qiskit_converter.convert(ansatz_assigned, use_postselection=True)
    linear = processor.linear_circuit()
    unitary = linear_circuit_to_unitary(linear)

    return unitary

hamiltonian = LiH_hamiltonian_tapered(R=0.1)

vqe = VQE(
    hamiltonian=hamiltonian,
    executor=executor,
    num_params=8,
    executor_type="photonic_unitary",
    ancillary_modes=list(range(8, 14)),  # 6 ancillary modes for 3 CNOTs
)

vqe_energy = vqe.run(max_iterations=100, verbose=True)

Ensemble-VQE (e-VQE)

Computing multiple excited states simultaneously using ensemble-VQE:

import numpy as np
from qlass.vqe import VQE
from qlass.quantum_chemistry import Hchain_KS_hamiltonian, hamiltonian_matrix
from qlass.vqe.ansatz import hf_ansatz
from perceval.algorithm import Sampler
import matplotlib.pyplot as plt

# Generate Kohn-Sham Hamiltonian for H4 chain
ham, scf_mo_energy, n_orbs = Hchain_KS_hamiltonian(4, 1.2)

def executor(params, pauli_string):
    processors = hf_ansatz(1, n_orbs, params, pauli_string, method="DFT", cost="e-VQE")
    samplers = [Sampler(p) for p in processors]
    samples = [sampler.samples(10_000) for sampler in samplers]
    return samples

vqe = VQE(
    hamiltonian=ham,
    executor=executor,
    num_params=4,
)

# Run e-VQE with weighted ensemble
vqe_energy = vqe.run(
    max_iterations=50,
    verbose=True,
    weight_option="weighted",  # Options: "weighted", "equi", "ground_state_only"
    cost="e-VQE"
)

VQE with Kohn-Sham Hamiltonian

Using DFT-based Kohn-Sham Hamiltonians:

from qlass.vqe import VQE
from qlass.quantum_chemistry import Hchain_KS_hamiltonian, brute_force_minimize
from qlass.vqe.ansatz import hf_ansatz
from perceval.algorithm import Sampler

# Generate Kohn-Sham Hamiltonian for H4 chain (4 atoms, 1.2 Å spacing)
ham, scf_mo_energy, n_orbs = Hchain_KS_hamiltonian(4, 1.2)

def executor(params, pauli_string):
    processors = hf_ansatz(1, n_orbs, params, pauli_string, method="DFT", cost="VQE")
    samplers = Sampler(processors)
    samples = samplers.samples(10_000)
    return samples

vqe = VQE(
    hamiltonian=ham,
    executor=executor,
    num_params=4,
)

vqe_energy = vqe.run(max_iterations=50, verbose=True)

Working with Molecular Hamiltonians

Generating and analyzing molecular Hamiltonians:

from qlass.quantum_chemistry import LiH_hamiltonian
from qlass.quantum_chemistry import hamiltonian_matrix, brute_force_minimize

# Generate a Hamiltonian for LiH with different parameters
hamiltonian = LiH_hamiltonian(
    R=1.5,  # Bond length in Angstroms
    charge=0,
    spin=0,
    num_electrons=2,
    num_orbitals=1
)

# Print the Hamiltonian terms
print("Hamiltonian terms:")
for pauli_string, coefficient in hamiltonian.items():
    print(f"  {pauli_string}: {coefficient:.6f}")

# Convert to matrix form
H_matrix = hamiltonian_matrix(hamiltonian)
print(f"Hamiltonian matrix shape: {H_matrix.shape}")

# Calculate the ground state energy
energy = brute_force_minimize(hamiltonian)
print(f"Ground state energy: {energy:.6f}")