from typing import Any
import perceval as pcvl
from perceval.components.unitary_components import BS, PS
from perceval.converters import QiskitConverter
from perceval.utils import NoiseModel
from qiskit import QuantumCircuit
from qlass.compiler.hardware_config import HardwareConfig
[docs]
def compile(
circuit: QuantumCircuit,
backend_name: str = "Naive",
use_postselection: bool = True,
input_state: pcvl.StateVector | pcvl.BasicState | None = None,
noise_model: NoiseModel = None,
) -> pcvl.Processor:
"""
Convert a Qiskit quantum circuit to a Perceval processor.
Args:
circuit (QuantumCircuit): The Qiskit quantum circuit to convert
backend_name (str): The backend to use for the Perceval processor
Options are: "Naive", "SLOS"
use_postselection (bool): Whether to use postselection for the processor
input_state (Optional[Union[pcvl.StateVector, pcvl.BasicState]]):
The input state for the processor. If None, the |0...0> state is used.
noise_model (NoiseModel): A perceval NoiseModel object representing the noise model
for the processor.
Returns:
pcvl.Processor: The quantum circuit as a Perceval processor
"""
# Initialize the Qiskit converter
qiskit_converter = QiskitConverter(backend_name=backend_name, noise_model=noise_model)
# Convert the circuit to a Perceval processor
processor = qiskit_converter.convert(circuit, use_postselection=use_postselection)
# Set the input state if provided, otherwise use the |0...0> state
if input_state is None:
processor.with_input(pcvl.LogicalState([0] * circuit.num_qubits))
else:
processor.with_input(input_state)
return processor
[docs]
class ResourceAwareCompiler:
"""
A compiler that analyzes a quantum circuit against a hardware configuration
to estimate its real-world performance and resource requirements.
"""
def __init__(self, config: HardwareConfig):
self.config = config
self.noise_model = NoiseModel(
brightness=self.config.brightness,
indistinguishability=self.config.indistinguishability,
g2=self.config.g2,
g2_distinguishable=self.config.g2_distinguishable,
transmittance=self.config.transmittance,
phase_imprecision=self.config.phase_imprecision,
phase_error=self.config.phase_error,
)
self.qiskit_converter = QiskitConverter(backend_name="Naive", noise_model=self.noise_model)
self.analysis_report: dict[str, Any] = {}
def _analyze(self, processor: pcvl.Processor, num_cnots: int) -> None:
"""Inspects the compiled Perceval processor and populates the analysis report."""
# Get the underlying circuit
circuit = processor.linear_circuit()
# 1. Count components by iterating through the circuit
num_ps = sum(1 for _, component in circuit if isinstance(component, PS))
num_bs = sum(1 for _, component in circuit if isinstance(component, BS))
num_components = num_ps + num_bs # Simplified count
num_modes = processor.m # Total number of modes in the circuit
# 2. Estimate Photon Loss
component_loss_db = num_components * self.config.photon_loss_component_db
path_loss_db = (
num_components
* self.config.avg_path_length_per_component_cm
* self.config.photon_loss_waveguide_db_per_cm
)
total_loss_db = component_loss_db + path_loss_db
# Convert dB loss to survival probability
# T = 10^(-dB/10)
photon_survival_prob = 10 ** (-total_loss_db / 10)
effective_fusion_prob_per_gate = (
self.config.fusion_success_prob * self.config.hom_visibility
)
# 3. Estimate Overall Success Probability
# This is a chain of probabilities
source_prob = self.config.source_efficiency**num_modes
fusion_prob = (effective_fusion_prob_per_gate**num_cnots) if num_cnots > 0 else 1.0
detector_prob = self.config.detector_efficiency**num_modes
overall_success_prob = source_prob * photon_survival_prob * fusion_prob * detector_prob
# 4. Populate the report
self.analysis_report = {
"num_modes": num_modes,
"component_count": {
"PhaseShifter": num_ps,
"BeamSplitter": num_bs,
"Total": num_components,
},
"loss_estimation_db": {
"component_loss": component_loss_db,
"waveguide_loss": path_loss_db,
"total_circuit_loss": total_loss_db,
},
"probability_estimation": {
"photon_survival_prob": photon_survival_prob,
"overall_success_prob": overall_success_prob,
"details": {
"source_success": source_prob,
"fusion_success": fusion_prob,
"detector_success": detector_prob,
},
},
}
[docs]
def compile(self, circuit: QuantumCircuit) -> pcvl.Processor:
"""Compiles a Qiskit circuit and runs a resource analysis."""
processor = self.qiskit_converter.convert(circuit, use_postselection=True)
processor.with_input(pcvl.LogicalState([0] * circuit.num_qubits))
num_cnots = circuit.count_ops().get("cx", 0)
self._analyze(processor, num_cnots)
# self.generate_report() # Automatically print the report after compilation
# Attach the report to the processor object for programmatic access
processor.analysis_report = self.analysis_report
return processor
[docs]
def generate_report(analysis_report: dict[str, Any]) -> None:
"""Prints the analysis report in a human-readable format."""
print("\n--- qlass Resource-Aware Compiler Report ---")
report = analysis_report
print(f" Circuit modes: {report['num_modes']}")
print(
f" Component Count: {report['component_count']['Total']} "
f"(PS: {report['component_count']['PhaseShifter']}, BS: {report['component_count']['BeamSplitter']})"
)
print("\n[Performance Estimation]")
loss_db = report["loss_estimation_db"]["total_circuit_loss"]
survival_prob = report["probability_estimation"]["photon_survival_prob"]
print(f" Estimated Circuit Loss: {loss_db:.2f} dB")
print(f" Photon Survival Probability (due to loss): {survival_prob:.2%}")
overall_prob = report["probability_estimation"]["overall_success_prob"]
print(f" >> Estimated Overall Success Probability: {overall_prob:.4%}")
print(
" (This is the probability of getting a valid, post-selected result from a single shot)"
)
print("-------------------------------------------\n")