Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 93 additions & 5 deletions src/qc_compiler/cost_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,9 @@ def estimate_gate_error(

Computes 1 - Π_gates (1 - error_rate(gate, qubits)).

If no device is characterized, uses average error rates from
typical superconducting hardware:
- Single-qubit gate error: 0.05%
- Two-qubit gate error: 1.0%
When a layout is provided and device calibration data is available,
uses per-qubit and per-link error rates for each gate instruction.
Falls back to average error rates when per-qubit data is unavailable.

Args:
circuit: The transpiled quantum circuit.
Expand All @@ -201,6 +200,25 @@ def estimate_gate_error(
if not self.device.num_qubits:
return self._estimate_gate_error_default(circuit)

if layout is not None or self.device.single_qubit_gate_errors:
product = 1.0
for instr in circuit.data:
gate_name = instr.operation.name
qubits = tuple(
circuit.find_bit(q).index for q in instr.qubits
)
if len(qubits) == 2 and gate_name in TWO_QUBIT_GATES:
fidelity = self._get_gate_fidelity_for_pair(
gate_name, qubits, layout
)
else:
fidelity = self._get_gate_fidelity_for_qubit(
gate_name, qubits[0] if qubits else 0, layout
)
product *= fidelity

return 1.0 - product

product = 1.0
ops = circuit.count_ops()

Expand Down Expand Up @@ -237,9 +255,13 @@ def _get_gate_fidelity(
) -> float:
"""Get the fidelity for a single gate execution on the device.

When layout is provided, looks up the specific error rate for
the gate on the mapped physical qubit(s). Falls back to average
error rates when per-qubit data is unavailable.

Args:
gate_name: The gate name (e.g., 'sx', 'cx', 'ecr').
layout: Optional qubit mapping.
layout: Optional qubit mapping from virtual to physical qubits.

Returns:
Gate fidelity (0 to 1).
Expand All @@ -251,6 +273,72 @@ def _get_gate_fidelity(

return 1.0 - avg_error

def _get_gate_fidelity_for_qubit(
self, gate_name: str, qubit: int, layout: dict | None = None
) -> float:
"""Get the fidelity for a single-qubit gate on a specific qubit.

Uses per-qubit error rates from device calibration when available.

Args:
gate_name: The gate name (e.g., 'sx', 'rz', 'h').
qubit: The virtual qubit index the gate acts on.
layout: Optional mapping from virtual to physical qubits.

Returns:
Gate fidelity (0 to 1).
"""
if not self.device.single_qubit_gate_errors:
return self._get_gate_fidelity(gate_name)

physical_qubit = layout.get(qubit, qubit) if layout else qubit
key = (gate_name, physical_qubit)
if key in self.device.single_qubit_gate_errors:
return 1.0 - self.device.single_qubit_gate_errors[key]

for (g, q), error in self.device.single_qubit_gate_errors.items():
if q == physical_qubit:
return 1.0 - error

return self._get_gate_fidelity(gate_name)

def _get_gate_fidelity_for_pair(
self, gate_name: str, qubits: tuple[int, ...], layout: dict | None = None
) -> float:
"""Get the fidelity for a two-qubit gate on a specific qubit pair.

Uses per-link error rates from device calibration when available.

Args:
gate_name: The gate name (e.g., 'cx', 'ecr', 'swap').
qubits: The virtual qubit indices the gate acts on.
layout: Optional mapping from virtual to physical qubits.

Returns:
Gate fidelity (0 to 1).
"""
if not self.device.two_qubit_gate_errors:
return self._get_gate_fidelity(gate_name)

if layout:
physical_qubits = tuple(layout.get(q, q) for q in qubits)
else:
physical_qubits = qubits

key = (gate_name, physical_qubits)
if key in self.device.two_qubit_gate_errors:
return 1.0 - self.device.two_qubit_gate_errors[key]

reversed_key = (gate_name, physical_qubits[::-1])
if reversed_key in self.device.two_qubit_gate_errors:
return 1.0 - self.device.two_qubit_gate_errors[reversed_key]

for (g, pair), error in self.device.two_qubit_gate_errors.items():
if set(pair) == set(physical_qubits):
return 1.0 - error

return self._get_gate_fidelity(gate_name)

def _avg_single_qubit_error(self) -> float:
"""Compute average single-qubit gate error across all qubits."""
if not self.device.single_qubit_gate_errors:
Expand Down
77 changes: 76 additions & 1 deletion tests/test_cost_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,4 +293,79 @@ def test_default_values(self):
assert breakdown.gate_fidelity == 1.0
assert breakdown.decoherence_fidelity == 1.0
assert breakdown.measurement_fidelity == 1.0
assert breakdown.total_fidelity == 1.0
assert breakdown.total_fidelity == 1.0


class TestLayoutAwareFidelity:
"""Regression tests for layout-aware per-qubit error rates (issue #41)."""

def test_per_qubit_gate_error_with_layout(self):
device = DeviceCharacterization(
backend_name="test_device",
num_qubits=3,
single_qubit_gate_errors={
("sx", 0): 0.001,
("sx", 1): 0.005,
("sx", 2): 0.002,
},
two_qubit_gate_errors={
("cx", (0, 1)): 0.01,
("cx", (1, 2)): 0.03,
},
)
model = CostModel()
model.device = device

qc = QuantumCircuit(2)
qc.sx(0)
qc.sx(1)
qc.cx(0, 1)

error_no_layout = model.estimate_gate_error(qc)
error_with_layout = model.estimate_gate_error(qc, layout={0: 1, 1: 2})

assert error_no_layout > 0
assert error_with_layout > 0
assert error_no_layout != error_with_layout

def test_per_qubit_fidelity_uses_specific_qubit_rates(self):
device = DeviceCharacterization(
backend_name="test_device",
num_qubits=2,
single_qubit_gate_errors={
("sx", 0): 0.001,
("sx", 1): 0.01,
},
two_qubit_gate_errors={
("cx", (0, 1)): 0.02,
},
)
model = CostModel()
model.device = device

qc = QuantumCircuit(2)
qc.sx(0)

fidelity_q0 = model._get_gate_fidelity_for_qubit("sx", 0)
fidelity_q1 = model._get_gate_fidelity_for_qubit("sx", 1)

assert abs(fidelity_q0 - 0.999) < 1e-6
assert abs(fidelity_q1 - 0.990) < 1e-6

def test_per_pair_fidelity_uses_specific_link_rates(self):
device = DeviceCharacterization(
backend_name="test_device",
num_qubits=3,
two_qubit_gate_errors={
("cx", (0, 1)): 0.01,
("cx", (1, 2)): 0.05,
},
)
model = CostModel()
model.device = device

fidelity_01 = model._get_gate_fidelity_for_pair("cx", (0, 1))
fidelity_12 = model._get_gate_fidelity_for_pair("cx", (1, 2))

assert abs(fidelity_01 - 0.99) < 1e-6
assert abs(fidelity_12 - 0.95) < 1e-6
Loading