Skip to main content

Circuit Optimization

QCOS includes a powerful circuit optimization engine that reduces circuit depth, minimizes gate count, and improves execution fidelity. This guide covers optimization techniques and best practices.

Why Optimize?​

Quantum computers are noisy. Every gate introduces errors:

Error SourceTypical Rate
Single-qubit gate0.01-0.1%
Two-qubit gate0.5-2%
Readout1-5%
DecoherenceContinuous

Optimization reduces errors by:

  • Reducing circuit depth (less time for decoherence)
  • Minimizing gate count (fewer error opportunities)
  • Using native gates (no additional decomposition errors)
  • Optimal qubit mapping (shorter paths, better qubits)

Optimization Levels​

QCOS provides four optimization levels:

Level 0: No Optimization​

job = client.run(circuit, backend="ibm_brisbane", optimization_level=0)
  • Basic validation only
  • Circuit submitted as-is
  • Use for debugging or pre-optimized circuits

Level 1: Light Optimization​

job = client.run(circuit, backend="ibm_brisbane", optimization_level=1)
  • Gate cancellation (adjacent inverses)
  • Basic gate fusion
  • Virtual Z optimization
  • Fast compilation

Level 2: Standard Optimization (Default)​

job = client.run(circuit, backend="ibm_brisbane", optimization_level=2)
  • All Level 1 optimizations
  • Qubit routing and mapping
  • Gate decomposition to native gates
  • Layout optimization for connectivity

Level 3: Heavy Optimization​

job = client.run(circuit, backend="ibm_brisbane", optimization_level=3)
  • All Level 2 optimizations
  • Multiple routing passes
  • Advanced gate synthesis
  • Noise-aware compilation
  • Slowest but best results

Optimization Pipeline​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Input β”‚ β†’ β”‚ Stage 1 β”‚ β†’ β”‚ Stage 2 β”‚
β”‚ Circuit β”‚ β”‚ Synthesis β”‚ β”‚ Mapping β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓ ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Output β”‚ ← β”‚ Stage 4 β”‚ ← β”‚ Stage 3 β”‚
β”‚ Circuit β”‚ β”‚ Scheduling β”‚ β”‚ Routing β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Stage 1: Synthesis​

Converts abstract gates to native gate set:

TargetNative Gates
IBMCX, ID, RZ, SX, X
IBM HeronCZ, RZ, SX, X
IonQGPI, GPI2, MS
RigettiCZ, RX, RZ

Stage 2: Mapping​

Assigns logical qubits to physical qubits:

# View qubit mapping
job = client.run(circuit, backend="ibm_brisbane")
print(f"Mapping: {job.metrics.qubit_mapping}")
# Output: {0: 12, 1: 15, 2: 18}

Stage 3: Routing​

Inserts SWAP gates for non-adjacent interactions:

# Original: CX(0, 5) - qubits not connected
# After routing: SWAP + CX or CX chain
print(f"Inserted SWAPs: {job.metrics.swap_count}")

Stage 4: Scheduling​

Parallelizes gates and adds timing:

# Dynamic decoupling for idle qubits
job = client.run(
circuit,
backend="ibm_brisbane",
enable_dynamic_decoupling=True
)

Optimization Metrics​

Get detailed metrics after optimization:

job = client.run(circuit, backend="ibm_brisbane", optimization_level=3)

metrics = job.metrics
print(f"Original depth: {metrics.original_depth}")
print(f"Optimized depth: {metrics.optimized_depth}")
print(f"Depth reduction: {metrics.depth_reduction_percent}%")
print(f"Original gates: {metrics.original_gate_count}")
print(f"Optimized gates: {metrics.optimized_gate_count}")
print(f"SWAP insertions: {metrics.swap_count}")
print(f"Two-qubit gates: {metrics.two_qubit_gate_count}")

Example Output:

Original depth: 120
Optimized depth: 72
Depth reduction: 40.0%
Original gates: 450
Optimized gates: 285
SWAP insertions: 8
Two-qubit gates: 45

Standalone Optimization​

Optimize circuits without execution:

result = client.optimize(
circuit,
target_backend="ibm_brisbane",
optimization_level=3
)

# Get optimized circuit
optimized = result.circuit

# View metrics
print(f"Reduction: {result.depth_reduction_percent}%")

# Export for later use
qasm = optimized.to_qasm()

Save Optimized Circuit​

# Optimize once, run many times
result = client.optimize(circuit, target_backend="ibm_brisbane")
optimized = result.circuit

# Run multiple times with level 0 (already optimized)
for params in parameter_values:
optimized.bind_parameters(params)
job = client.run(
optimized,
backend="ibm_brisbane",
optimization_level=0 # Skip re-optimization
)

Advanced Optimization​

Preserve Layout​

Keep your qubit assignment:

result = client.optimize(
circuit,
target_backend="ibm_brisbane",
preserve_layout=True,
initial_layout={0: 0, 1: 1, 2: 2}
)

Custom Basis Gates​

Force specific gate decomposition:

result = client.optimize(
circuit,
basis_gates=["cx", "rz", "x", "sx"],
optimization_level=3
)

Noise-Aware Optimization​

Use calibration data for better mapping:

result = client.optimize(
circuit,
target_backend="ibm_brisbane",
use_calibration_data=True # Use latest calibration
)

Optimization Strategies​

Strategy 1: Reduce Two-Qubit Gates​

Two-qubit gates have 10-100x more error:

# Before: Multiple CX gates
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)

# Use optimization to find better decomposition
result = client.optimize(
circuit,
target_backend="ibm_brisbane",
optimization_goal="two_qubit_gates"
)

Strategy 2: Minimize Depth​

For algorithms limited by decoherence:

result = client.optimize(
circuit,
target_backend="ibm_brisbane",
optimization_goal="depth"
)

Strategy 3: Use High-Fidelity Qubits​

Select best qubits based on calibration:

# Get backend calibration
backend = client.backend("ibm_brisbane")
calibration = backend.calibration

# Find best qubits
best_qubits = sorted(
calibration.qubits,
key=lambda q: q.readout_error + q.cx_error
)[:circuit.num_qubits]

# Use as initial layout
result = client.optimize(
circuit,
target_backend="ibm_brisbane",
initial_layout={i: q.index for i, q in enumerate(best_qubits)}
)

Parameterized Circuits​

For variational algorithms, optimize the template once:

from softqcos.circuits import QuantumCircuit, Parameter

# Create parameterized circuit
theta = [Parameter(f"ΞΈ{i}") for i in range(4)]
circuit = QuantumCircuit(4, 4)
for i, t in enumerate(theta):
circuit.ry(t, i)
circuit.cx(0, 1)
circuit.cx(2, 3)
circuit.cx(1, 2)
circuit.measure_all()

# Optimize template
result = client.optimize(circuit, target_backend="ibm_brisbane")
template = result.circuit

# Use template with different parameters
for values in optimization_loop:
bound = template.bind_parameters(dict(zip(theta, values)))
job = client.run(bound, backend="ibm_brisbane", optimization_level=0)

Benchmarking​

Compare optimization levels:

results = []
for level in range(4):
job = client.run(
circuit,
backend="ibm_brisbane",
optimization_level=level
)
result = job.result()

results.append({
"level": level,
"depth": job.metrics.optimized_depth,
"gates": job.metrics.optimized_gate_count,
"fidelity": result.quality_metrics.fidelity_estimate,
"time": job.metrics.optimization_time_ms
})

# Compare
for r in results:
print(f"Level {r['level']}: depth={r['depth']}, "
f"fidelity={r['fidelity']:.3f}, time={r['time']}ms")

Example Output:

Level 0: depth=120, fidelity=0.72, time=10ms
Level 1: depth=98, fidelity=0.78, time=45ms
Level 2: depth=85, fidelity=0.84, time=120ms
Level 3: depth=72, fidelity=0.89, time=350ms

Best Practices​

Do​

βœ… Use Level 2-3 for production workloads βœ… Optimize parameterized templates once βœ… Check optimization metrics before running βœ… Use noise-aware compilation for critical circuits βœ… Consider backend topology when designing circuits

Don't​

❌ Use Level 0 unless debugging ❌ Re-optimize already optimized circuits ❌ Ignore swap insertions (they add significant error) ❌ Assume more gates = more optimization needed


Credits​

Optimization consumes credits based on circuit size:

Circuit SizeCredit Cost
< 100 gatesFree
100-500 gates, Level 1-21 credit
100-500 gates, Level 32 credits
500+ gates3+ credits

Free tier: 5 optimizations/day Pro tier: 1000 credits/month (first 100 free)


Next Steps​

  • πŸ“– Backends - Hardware-specific optimization
  • 🧠 SynapseX - AI-assisted optimization
  • πŸ’° Pricing - Optimization credit pricing