Skip to content
Open
Show file tree
Hide file tree
Changes from 99 commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
129b60f
Update predictor(adding callbacks)
Mar 29, 2025
08889bd
Update
Apr 10, 2025
e2ff3fe
Restore helper.py and predictor.py to match upstream
Jul 29, 2025
1c32d15
Merge remote-tracking branch 'upstream/main'
Jul 29, 2025
78dc1aa
Implement new mapping actions
Jul 29, 2025
a3ba836
Fix: resolve pre-commit issues and add missing annotations
Jul 29, 2025
5935e6f
Fix: resolve pre-commit issues and add missing annotations
Jul 29, 2025
f71fb29
Fix: resolve pre-commit issues and add missing annotations
Jul 30, 2025
3c7592b
Fix: resolve pre-commit issues and add missing annotations
Jul 30, 2025
6db5c27
Fix mypy errors
Aug 1, 2025
47841c5
Fix mypy errors
Aug 1, 2025
b1ac8ce
Fix dependencies issues
Aug 3, 2025
5f8473c
Fix dependency issues
Aug 3, 2025
7491ec0
Add missing zip file
Aug 3, 2025
3346842
Fix issue with Python 3.13
Aug 3, 2025
6f7a73c
Merge branch 'main' into hybrid-mapping
Shaobo-Zhou Aug 3, 2025
6c67349
Remove Python 3.13 from noxfile.py due to compatibility issue
Aug 3, 2025
2692b96
Skip minimums session on Windows due to CI slowness
Aug 4, 2025
f4874e6
Fix bugs
Aug 5, 2025
54eec91
Fix bugs
Aug 5, 2025
845f7de
Use default Qiskit settings for VF2Layout and add assertion for nativ…
Shaobo-Zhou Aug 7, 2025
3418936
Debug
Shaobo-Zhou Aug 7, 2025
ae870cc
Fix missing argument
Shaobo-Zhou Aug 7, 2025
861bc62
Fix warning issues
Shaobo-Zhou Aug 7, 2025
fa989b6
Fix window runtime warning problem
Shaobo-Zhou Aug 7, 2025
405bd39
Fix window runtime warning problem
Shaobo-Zhou Aug 7, 2025
7b2f321
Add time limit for VF2PostLayout
Shaobo-Zhou Aug 7, 2025
b67d0a6
Fix windows runtime warning problem
Shaobo-Zhou Aug 7, 2025
bf7c9ee
Add new actions
Shaobo-Zhou Aug 29, 2025
6d2733f
Add new actions
Shaobo-Zhou Aug 29, 2025
878185a
Add evaluation code for baseline model
Shaobo-Zhou Sep 3, 2025
eae11a2
Set up code for testing new model
Shaobo-Zhou Sep 5, 2025
68306ec
Reset
Shaobo-Zhou Sep 5, 2025
fcba8fa
Add new actions
Shaobo-Zhou Sep 6, 2025
e5b7518
Fix dependencies
Shaobo-Zhou Sep 8, 2025
907ce2b
Fix dependencies
Shaobo-Zhou Sep 8, 2025
70dcd7d
Fix dependencies
Shaobo-Zhou Sep 8, 2025
e0364b1
Merge branch 'main' into new_structure
Shaobo-Zhou Sep 8, 2025
eb27098
🎨 pre-commit fixes
pre-commit-ci[bot] Sep 8, 2025
2688c0e
Fix dependencies
Shaobo-Zhou Sep 8, 2025
840d23d
Fix multiprocessing on Python 3.13
Shaobo-Zhou Sep 9, 2025
ac406ac
Fix timeout watcher
Shaobo-Zhou Sep 9, 2025
97c81bb
Update comments and restructure
Shaobo-Zhou Sep 15, 2025
470365d
Adjust test circuits from ALG to INDEP
Shaobo-Zhou Sep 15, 2025
caaf224
Update max synthesis size for bqskit
Shaobo-Zhou Sep 15, 2025
468da2e
Add tests for more coverage
Shaobo-Zhou Sep 17, 2025
737a9f2
Update tests
Shaobo-Zhou Sep 18, 2025
94c25ab
Update override
Shaobo-Zhou Sep 18, 2025
3ff922e
Update comments
Shaobo-Zhou Sep 18, 2025
782caf8
Update noxfile.py and CHANGELOG.md
Shaobo-Zhou Sep 22, 2025
44e0e40
Clean up venv after each session to free up space
Shaobo-Zhou Sep 23, 2025
350bae5
Clean up venv after each session to free up space
Shaobo-Zhou Sep 23, 2025
91208d1
Clean up venv after each session to free up space
Shaobo-Zhou Sep 23, 2025
622d409
Clean up venv after each session to free up space
Shaobo-Zhou Sep 23, 2025
fa008a6
Update noxfile
Shaobo-Zhou Sep 25, 2025
0c98e56
Update ibm runtime dependency
Shaobo-Zhou Sep 25, 2025
34a1c7c
Update noxfile
Shaobo-Zhou Sep 25, 2025
a8d069a
Update noxfile
Shaobo-Zhou Sep 25, 2025
114b79b
Update noxfile
Shaobo-Zhou Sep 25, 2025
aaf14b1
Fetch update from main
Shaobo-Zhou Sep 26, 2025
e39cd7e
Merge remote-tracking branch 'upstream/main' into new_structure
Shaobo-Zhou Nov 20, 2025
e7b4174
Fix ruff checks
Shaobo-Zhou Nov 20, 2025
fcab4fa
Update action space and add normalized gate counts as RL features
Shaobo-Zhou Nov 20, 2025
cb4d0fb
Fix FOM comparison logic
Shaobo-Zhou Nov 20, 2025
988a8f7
Remove 3-qubit gates from dict
Shaobo-Zhou Nov 21, 2025
9a40b3e
Add reward shaping
Shaobo-Zhou Nov 26, 2025
cee79cd
Add fallback for unsupported reward function
Shaobo-Zhou Nov 27, 2025
756d777
Minor Fixes
Shaobo-Zhou Nov 27, 2025
d60d17e
Minor Fixes
Shaobo-Zhou Nov 27, 2025
a05e37a
Merge branch 'main' into new_RL
Shaobo-Zhou Nov 27, 2025
85d4d57
Minor Fixes
Shaobo-Zhou Nov 27, 2025
a1370de
Fix predictorenv.py
Shaobo-Zhou Nov 27, 2025
e456f43
Update changelog and improve test coverage
Shaobo-Zhou Dec 3, 2025
de7c498
Update cost model
Shaobo-Zhou Dec 3, 2025
9a68c59
Merge branch 'main' into new_RL
Shaobo-Zhou Dec 3, 2025
d0a07a2
Update cost model
Shaobo-Zhou Dec 3, 2025
18cf31f
Fix CI issue
Shaobo-Zhou Dec 3, 2025
0c42b41
Improve coverage
Shaobo-Zhou Dec 3, 2025
a451710
Update src/mqt/predictor/reward.py
Shaobo-Zhou Dec 3, 2025
ac7dc28
Update src/mqt/predictor/rl/cost_model.py
Shaobo-Zhou Dec 3, 2025
67dc402
Update tests/hellinger_distance/test_estimated_hellinger_distance.py
Shaobo-Zhou Dec 3, 2025
8259b4d
Code improvements suggested by CodeRabbit
Shaobo-Zhou Dec 3, 2025
4b5bf6f
Fix warnings
Shaobo-Zhou Dec 3, 2025
14aa3ac
Improve coverage
Shaobo-Zhou Dec 4, 2025
04fde70
Improve coverage
Shaobo-Zhou Dec 4, 2025
1df8071
Update src/mqt/predictor/rl/predictorenv.py
Shaobo-Zhou Dec 4, 2025
6a104b8
Fixes
Shaobo-Zhou Dec 4, 2025
ae43600
Merge branch 'main' into new_RL
Shaobo-Zhou Dec 17, 2025
b91f4f4
Fixes
Shaobo-Zhou Dec 17, 2025
b2197b3
Fix format
Shaobo-Zhou Dec 17, 2025
331972a
Fixes
Shaobo-Zhou Dec 17, 2025
b8a80d2
Fixes
Shaobo-Zhou Dec 17, 2025
7810d4d
Update comments
Shaobo-Zhou Dec 18, 2025
8b3ecaf
Merge branch 'main' into new_RL
burgholzer Dec 25, 2025
bc49c63
✏️ curate changelog
burgholzer Dec 25, 2025
5cd1bf7
✏️ minimize unnecessary whitespace changes
burgholzer Dec 25, 2025
5a895d0
⏪ revert Windows workaround
burgholzer Dec 25, 2025
1ecb3e2
🏷️ Various typing fixes
burgholzer Dec 25, 2025
dde6f89
⏪ avoid CUDA out of memory error
burgholzer Dec 25, 2025
2e3d1c7
Update cost_model.py to QCEC style
Shaobo-Zhou Feb 4, 2026
c8b7201
Merge branch 'main' into new_RL
Shaobo-Zhou Feb 4, 2026
da3c95f
🎨 pre-commit fixes
pre-commit-ci[bot] Feb 4, 2026
56573a9
Adjusted implementation of reward approximation
Shaobo-Zhou Feb 8, 2026
e0cec3c
Coderabbit suggestions
Shaobo-Zhou Feb 8, 2026
a41b323
Resolve problem with estimated Hellinger distance
Shaobo-Zhou Feb 8, 2026
37c55a8
Merge branch 'main' into new_RL
Shaobo-Zhou Feb 8, 2026
734df4e
Add test coverage
Shaobo-Zhou Feb 8, 2026
d376268
Apply coderabbit suggestions
Shaobo-Zhou Feb 8, 2026
adaefa9
Apply coderabbit suggestions
Shaobo-Zhou Feb 8, 2026
7a0c40b
Apply coderabbit suggestions
Shaobo-Zhou Feb 8, 2026
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
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ This project adheres to [Semantic Versioning], with the exception that minor rel

### Changed

- ✨ Improve RL reward design by adding intermediate rewards ([#526]) ([**@Shaobo-Zhou**])
- 🔧 Change test circuit level for RL predictor from ALG to INDEP ([#449]) ([**@Shaobo-Zhou**])

### Fixed

- 🐛 Fix instruction duration unit in estimated success probability calculation ([#445]) ([**@Shaobo-Zhou**])

### Removed

- ✨ Remove support for custom names of trained models ([#489]) ([**@bachase**])
- 🔥 Drop support for x86 macOS systems ([#421]) ([**@denialhaag**])

Expand Down Expand Up @@ -45,8 +53,10 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool

<!-- PR links -->

[#445]: https://github.com/munich-quantum-toolkit/predictor/pull/445
[#526]: https://github.com/munich-quantum-toolkit/predictor/pull/526
[#489]: https://github.com/munich-quantum-toolkit/predictor/pull/489
[#449]: https://github.com/munich-quantum-toolkit/predictor/pull/449
[#445]: https://github.com/munich-quantum-toolkit/predictor/pull/445
[#421]: https://github.com/munich-quantum-toolkit/predictor/pull/421
[#406]: https://github.com/munich-quantum-toolkit/predictor/pull/406
[#405]: https://github.com/munich-quantum-toolkit/predictor/pull/405
Expand Down
2 changes: 1 addition & 1 deletion docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ After setup, any quantum circuit can be compiled for the most suitable device wi
from mqt.predictor import qcompile
from mqt.bench import get_benchmark, BenchmarkLevel

uncompiled_qc = get_benchmark("ghz", level=BenchmarkLevel.ALG, circuit_size=5)
uncompiled_qc = get_benchmark("ghz", level=BenchmarkLevel.INDEP, circuit_size=5)
compiled_qc, compilation_info, selected_device = qcompile(
uncompiled_qc, figure_of_merit="expected_fidelity"
)
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ filterwarnings = [
'ignore:.*qiskit.providers.models is deprecated since Qiskit 1.2*:DeprecationWarning:',
'ignore:.*The class ``qiskit.qobj.*`` is deprecated as of Qiskit 1.3.*:DeprecationWarning:',
'ignore:.*The property ``qiskit.circuit.instruction.Instruction.*`` is deprecated as of qiskit 1.3.0.*:DeprecationWarning:',
"ignore:.*No canonical cost table defined for device.*:UserWarning:",
]


Expand Down
1 change: 0 additions & 1 deletion src/mqt/predictor/ml/predictor.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,6 @@ def train_random_forest_model(
training_data = self._get_prepared_training_data()
num_cv = min(len(training_data.y_train), 5)
mdl = GridSearchCV(mdl, tree_param, cv=num_cv, n_jobs=8).fit(training_data.X_train, training_data.y_train)

joblib_dump(mdl, save_mdl_path)
logger.info("Random Forest model is trained and saved.")

Expand Down
2 changes: 1 addition & 1 deletion src/mqt/predictor/reward.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision:
if first_qubit_idx not in active_qubits:
continue

dt = device.dt # instruction durations are stored in unit dt
dt = device.dt or 1.0 # discrete time unit; fallback to 1.0 if unavailable
res *= np.exp(
-instruction.duration
* dt
Expand Down
3 changes: 2 additions & 1 deletion src/mqt/predictor/rl/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@

from bqskit import Circuit
from pytket._tket.passes import BasePass as tket_BasePass
from qiskit.passmanager import PropertySet
from qiskit.transpiler.basepasses import BasePass as qiskit_BasePass


Expand Down Expand Up @@ -143,7 +144,7 @@ class DeviceDependentAction(Action):
Callable[..., tuple[Any, ...] | Circuit],
]
)
do_while: Callable[[dict[str, Circuit]], bool] | None = None
do_while: Callable[[PropertySet], bool] | None = None


# Registry of actions
Expand Down
308 changes: 308 additions & 0 deletions src/mqt/predictor/rl/cost_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
# Copyright (c) 2025 Munich Quantum Software Company GmbH
# All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Licensed under the MIT License

"""Helper functions for approximating transformations to device-native gates.

This module provides a simple canonical gate cost model and approximate
fidelity/ESP estimates based on averaged 1q/2q error rates.

It ships hand-crafted canonical cost tables for a small set of backends
(currently: ``ibm_torino``, ``ankaa_3``, ``emerald``).

Support for additional devices is not automatic: for each new backend,
add a corresponding canonical cost table (and, if needed, device-specific
approximations) to ``DEVICE_CANONICAL_COSTS``.
"""

from __future__ import annotations

import logging
import warnings
from typing import TYPE_CHECKING

import numpy as np

if TYPE_CHECKING:
from qiskit import QuantumCircuit

logger = logging.getLogger(__name__)

CanonicalCostTable = dict[str, tuple[int, int]]

# ---------------------------------------------------------------------------
# Canonical cost tables
# ---------------------------------------------------------------------------

TORINO_CANONICAL_COSTS: CanonicalCostTable = {
# native 1q
"rz": (1, 0),
"rx": (1, 0),
"sx": (1, 0),
"x": (1, 0),
"id": (0, 0), # treat as no-op for fidelity; timing can be handled elsewhere
# native 2q
"cz": (0, 1),
"rzz": (0, 1),
# ------------------------------------------------------------------
# Common 1q non-natives decomposed into {rz, rx, sx, x}
# ------------------------------------------------------------------
"u": (3, 0), # generic U(θ, φ, λ) ~ 3 Euler angles
"u3": (3, 0),
"u2": (2, 0),
"h": (3, 0), # H ≈ Rz(π) • SX • Rz(π) up to phase
"ry": (3, 0), # Ry ≈ Rz(-π/2) • Rx(θ) • Rz(π/2)
"s": (1, 0), # S = Rz(π/2)
"sdg": (1, 0),
"t": (1, 0), # T = Rz(π/4)
"tdg": (1, 0),
# ------------------------------------------------------------------
# Common 2q gates expressed in a CZ + 1q basis (approximate)
# ------------------------------------------------------------------
"rxx": (4, 1), # ~4 single-qubit rotations + 1 entangler (rzz/cz)
# Controlled-1q rotations / phases:
# roughly: 1 CZ + a few single-qubit rotations on control/target.
"crx": (4, 1),
"cry": (4, 1),
"crz": (4, 1),
"cp": (4, 1),
"cu1": (4, 1),
"cu3": (6, 1),
"cu": (6, 1),
"ch": (6, 1),
"cy": (6, 1),
"csx": (4, 1),
"cx": (6, 1), # CX = H(t) • CZ • H(t) -> ~2*H + 1*CZ => ~6 singles + 1 two-qubit
"czx": (6, 1),
"swap": (12, 3), # SWAP ≈ 3 CX; in a CZ basis still ~3 two-qubit gates
}

ANKAA3_CANONICAL_COSTS: CanonicalCostTable = {
"rx": (1, 0),
"rz": (1, 0),
"iswap": (0, 1),
"u": (3, 0),
"u3": (3, 0),
"u2": (2, 0),
"h": (3, 0),
"ry": (3, 0),
"s": (1, 0),
"sdg": (1, 0),
"t": (1, 0),
"tdg": (1, 0),
"rzz": (4, 2), # ~2 iSWAP + ~4 1q rotations
"rxx": (4, 2),
# controlled gates: ~ 2 iSWAP + some 1q each
"crx": (6, 2),
"cry": (6, 2),
"crz": (6, 2),
"cp": (6, 2),
"cu1": (6, 2),
"cu3": (8, 2),
"cu": (8, 2),
"ch": (8, 2),
"cy": (8, 2),
"csx": (6, 2),
"swap": (12, 3),
}

EMERALD_CANONICAL_COSTS: CanonicalCostTable = {
# native
"rz": (1, 0),
"rx": (1, 0),
"r": (1, 0),
"cz": (0, 1),
"u": (1, 0),
"u3": (1, 0),
"u2": (1, 0),
"h": (1, 0),
"ry": (1, 0),
"s": (1, 0),
"sdg": (1, 0),
"t": (1, 0),
"tdg": (1, 0),
"rzz": (4, 2),
"rxx": (4, 2),
"crx": (4, 1),
"cry": (4, 1),
"crz": (4, 1),
"cp": (4, 1),
"cu1": (4, 1),
"cu3": (6, 1),
"cu": (6, 1),
"ch": (6, 1),
"cy": (6, 1),
"csx": (4, 1),
"swap": (12, 3),
}

DEVICE_CANONICAL_COSTS: dict[str, CanonicalCostTable] = {
"ibm_torino": TORINO_CANONICAL_COSTS,
"ankaa_3": ANKAA3_CANONICAL_COSTS,
"emerald": EMERALD_CANONICAL_COSTS,
}


def get_cost_table(device_id: str) -> CanonicalCostTable:
"""Return the canonical cost table for ``device_id``, with a safe fallback.

If the device is unknown, a warning is emitted and the ``ibm_torino`` table
is used as a generic fallback. This keeps the code running, but the
approximation should be treated with care.
"""
table = DEVICE_CANONICAL_COSTS.get(device_id)
if table is None:
msg = (
f"No canonical cost table defined for device '{device_id}'. "
"Falling back to 'ibm_torino' table; approximate metrics may "
"be inaccurate. Consider adding a dedicated entry to "
"DEVICE_CANONICAL_COSTS."
)
warnings.warn(msg, UserWarning, stacklevel=3)
logger.warning(msg)
table = TORINO_CANONICAL_COSTS
return table


def canonical_cost(
gate_name: str,
*,
device_id: str = "ibm_torino",
) -> tuple[int, int]:
"""Return (n_1q, n_2q) cost for ``gate_name`` on the given device.

Note:
Hand-crafted tables are available for a small set of backends
(see ``DEVICE_CANONICAL_COSTS``). For additional devices, extend
``DEVICE_CANONICAL_COSTS`` accordingly.
"""
table = get_cost_table(device_id)
return table.get(gate_name, (0, 0))


def estimate_counts(
qc: QuantumCircuit,
*,
cost_table: CanonicalCostTable,
) -> tuple[int, int]:
"""Estimate canonical (n_1q, n_2q) counts for a circuit.

Uses the provided ``cost_table`` where available and a simple, conservative
fallback otherwise (3*1q for unknown 1q gates, 1*2q + 4*1q for unknown 2q gates).
"""
n_1q = 0
n_2q = 0

for circuit_instr in qc.data:
name = circuit_instr.operation.name
qargs = circuit_instr.qubits

# Ignore non-unitary / timing-only ops for this count
if name in ("barrier", "delay", "measure"):
continue

cost = cost_table.get(name)
if cost is None:
# Conservative fallback by arity (only used for gates missing in the table)
if len(qargs) == 1:
n_1q += 3
elif len(qargs) == 2:
n_2q += 1
n_1q += 4
else:
n_1q += cost[0]
n_2q += cost[1]
return n_1q, n_2q
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider logging unknown gates.

The conservative fallback for gates not in the cost table is reasonable, but silently falling back (lines 210-216) could hide unexpected gates or make debugging difficult. Consider adding a debug or warning log when the fallback is triggered.

🔎 Suggested addition:
         cost = cost_table.get(name)
         if cost is None:
+            logger.debug(
+                f"Gate '{name}' not in cost table; using conservative fallback: "
+                f"{'(3, 0)' if len(qargs) == 1 else '(4, 1)'}"
+            )
             # Conservative fallback by arity (only used for gates missing in the table)
             if len(qargs) == 1:
                 n_1q += 3
🤖 Prompt for AI Agents
In src/mqt/predictor/rl/cost_model.py around lines 188 to 220, the fallback path
for gates missing from the cost_table silently applies conservative counts;
modify this to emit a log message when the fallback is triggered that includes
the gate name and its arity (len(qargs)). Add a module logger
(logging.getLogger(__name__)) if not present, and call logger.debug or
logger.warning inside the if cost is None branch (before incrementing n_1q/n_2q)
so developers can see which unknown gates caused the fallback; keep message
concise and avoid sensitive data.



def approx_expected_fidelity(
qc: QuantumCircuit,
p1_avg: float,
p2_avg: float,
*,
device_id: str = "ibm_torino",
) -> float:
"""Approximate expected fidelity from canonical gate counts.

Args:
qc: Circuit for which to estimate fidelity.
p1_avg: Average single-qubit error probability across the device.
p2_avg: Average two-qubit error probability across the device.
device_id: Identifier of the backend (used to select the cost table).

Returns:
Approximate expected fidelity in [0, 1].
"""
cost_table = get_cost_table(device_id)
n_1q, n_2q = estimate_counts(qc, cost_table=cost_table)

f_1q = (1.0 - p1_avg) ** max(n_1q, 0)
f_2q = (1.0 - p2_avg) ** max(n_2q, 0)
f = f_1q * f_2q

# Clamp to [0, 1] for numerical robustness
return float(max(min(f, 1.0), 0.0))


def approx_estimated_success_probability(
qc: QuantumCircuit,
p1_avg: float,
p2_avg: float,
tau1_avg: float,
tau2_avg: float,
tbar: float | None,
par_feature: float,
liv_feature: float,
n_qubits: int,
*,
device_id: str = "ibm_torino",
) -> float:
"""Approximate ESP using canonical counts and simple idle-time modeling.

The ESP is modeled as:

ESP ≈ F_gates * exp(- T_idle / T̄)

where F_gates is approximated from canonical 1q/2q counts and mean error
rates, and T_idle is estimated from a crude duration model modulated by
a parallelism and liveness feature.

Args:
qc: Circuit for which to estimate ESP.
p1_avg: Average single-qubit error probability.
p2_avg: Average two-qubit error probability.
tau1_avg: Average single-qubit gate duration.
tau2_avg: Average two-qubit gate duration.
tbar: Effective characteristic decoherence time (e.g. derived from T1/T2).
par_feature: Parallelism feature in [0, 1] (e.g. Supermarq parallelism).
liv_feature: Liveness feature in [0, 1], where 1 ≈ always active.
n_qubits: Number of qubits in the circuit / device.
device_id: Identifier of the backend (used to select the cost table).

Returns:
Approximate ESP in [0, 1].
"""
cost_table = get_cost_table(device_id)

# Fidelity part from gate errors
n_1q, n_2q = estimate_counts(qc, cost_table=cost_table)
f_1q = (1.0 - p1_avg) ** max(n_1q, 0)
f_2q = (1.0 - p2_avg) ** max(n_2q, 0)
f = f_1q * f_2q

# Effective duration via parallelism (par_feature ∈ [0, 1])
n_q = max(n_qubits, 1)
k_eff = 1.0 + (n_q - 1.0) * float(par_feature) # ∈ [1, n_qubits]

t_hat = (n_1q * tau1_avg + n_2q * tau2_avg) / k_eff

# Idle-time penalty via (1 - liveness)
idle_frac = max(0.0, 1.0 - float(liv_feature))
idle_factor = 1.0 if tbar is None or tbar <= 0.0 else float(np.exp(-(t_hat * idle_frac) / tbar))

esp = f * idle_factor
return float(max(min(esp, 1.0), 0.0))
Loading
Loading