Upload 4 files
Browse files- tests/test_app_flows.py +77 -8
- tests/test_eda_translator.py +9 -1
- tests/test_eda_view.py +12 -2
- tests/test_severity_mapper.py +100 -0
tests/test_app_flows.py
CHANGED
|
@@ -122,8 +122,53 @@ import app
|
|
| 122 |
from quread.engine import QuantumStateVector
|
| 123 |
from quread.def_translator import DEFGridConfig, build_def_blockages, to_def_blockages_fragment
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
class AppFlowsTest(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
def test_qubit_count_change_reinitializes_simulator(self):
|
| 128 |
qc, last_counts, selected_gate, _target, _control, _cnot_target, status = app._on_qubit_count_change(3)
|
| 129 |
|
|
@@ -237,6 +282,34 @@ class AppFlowsTest(unittest.TestCase):
|
|
| 237 |
self.assertTrue(hasattr(fig, "axes"))
|
| 238 |
self.assertGreaterEqual(len(fig.axes), 1)
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
def test_def_export_returns_fragment_file(self):
|
| 241 |
qc = QuantumStateVector(2)
|
| 242 |
qc.apply_single("H", target=0)
|
|
@@ -258,8 +331,7 @@ class AppFlowsTest(unittest.TestCase):
|
|
| 258 |
0.15,
|
| 259 |
0.45,
|
| 260 |
0.70,
|
| 261 |
-
|
| 262 |
-
"composite_risk",
|
| 263 |
"0",
|
| 264 |
"0",
|
| 265 |
"40",
|
|
@@ -298,8 +370,7 @@ class AppFlowsTest(unittest.TestCase):
|
|
| 298 |
0.15,
|
| 299 |
0.45,
|
| 300 |
0.70,
|
| 301 |
-
|
| 302 |
-
"composite_risk",
|
| 303 |
"0",
|
| 304 |
"0",
|
| 305 |
"40",
|
|
@@ -346,8 +417,7 @@ class AppFlowsTest(unittest.TestCase):
|
|
| 346 |
0.15,
|
| 347 |
0.45,
|
| 348 |
0.70,
|
| 349 |
-
|
| 350 |
-
"composite_risk",
|
| 351 |
"0",
|
| 352 |
"0",
|
| 353 |
"40",
|
|
@@ -381,8 +451,7 @@ class AppFlowsTest(unittest.TestCase):
|
|
| 381 |
0.15,
|
| 382 |
0.45,
|
| 383 |
0.70,
|
| 384 |
-
|
| 385 |
-
"composite_risk",
|
| 386 |
"0",
|
| 387 |
"0",
|
| 388 |
"-1",
|
|
|
|
| 122 |
from quread.engine import QuantumStateVector
|
| 123 |
from quread.def_translator import DEFGridConfig, build_def_blockages, to_def_blockages_fragment
|
| 124 |
|
| 125 |
+
DEFAULT_SEVERITY_ARGS = (
|
| 126 |
+
"linear",
|
| 127 |
+
"composite_risk",
|
| 128 |
+
0.45,
|
| 129 |
+
0.70,
|
| 130 |
+
67.0,
|
| 131 |
+
90.0,
|
| 132 |
+
0.25,
|
| 133 |
+
0.20,
|
| 134 |
+
0.15,
|
| 135 |
+
0.25,
|
| 136 |
+
0.15,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
|
| 140 |
class AppFlowsTest(unittest.TestCase):
|
| 141 |
+
def test_apply_selected_gate_recovers_from_missing_qc_state(self):
|
| 142 |
+
qc, last_counts, status = app.apply_selected_gate(None, None, "H", 0, 2)
|
| 143 |
+
|
| 144 |
+
self.assertIsNotNone(qc)
|
| 145 |
+
self.assertEqual(qc.n_qubits, 2)
|
| 146 |
+
self.assertEqual(len(qc.history), 1)
|
| 147 |
+
self.assertIsNone(last_counts)
|
| 148 |
+
self.assertIn("Applied H on q0", status)
|
| 149 |
+
|
| 150 |
+
def test_metrics_pipeline_recovers_from_missing_qc_state(self):
|
| 151 |
+
metrics, meta, qubit_coords, layout_meta = app._current_metrics_and_layout(
|
| 152 |
+
None,
|
| 153 |
+
2,
|
| 154 |
+
2,
|
| 155 |
+
2,
|
| 156 |
+
None,
|
| 157 |
+
"",
|
| 158 |
+
0.25,
|
| 159 |
+
0.20,
|
| 160 |
+
0.15,
|
| 161 |
+
0.25,
|
| 162 |
+
0.15,
|
| 163 |
+
0.45,
|
| 164 |
+
0.70,
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
self.assertIn("composite_risk", metrics)
|
| 168 |
+
self.assertEqual(len(metrics["composite_risk"]), 2)
|
| 169 |
+
self.assertEqual(qubit_coords[0], (0, 0))
|
| 170 |
+
self.assertEqual(layout_meta["source"], "default")
|
| 171 |
+
|
| 172 |
def test_qubit_count_change_reinitializes_simulator(self):
|
| 173 |
qc, last_counts, selected_gate, _target, _control, _cnot_target, status = app._on_qubit_count_change(3)
|
| 174 |
|
|
|
|
| 282 |
self.assertTrue(hasattr(fig, "axes"))
|
| 283 |
self.assertGreaterEqual(len(fig.axes), 1)
|
| 284 |
|
| 285 |
+
def test_severity_csv_export_returns_file(self):
|
| 286 |
+
qc = QuantumStateVector(2)
|
| 287 |
+
path = app._dl_severity_csv(
|
| 288 |
+
qc,
|
| 289 |
+
2,
|
| 290 |
+
2,
|
| 291 |
+
2,
|
| 292 |
+
None,
|
| 293 |
+
"",
|
| 294 |
+
0.25,
|
| 295 |
+
0.20,
|
| 296 |
+
0.15,
|
| 297 |
+
0.25,
|
| 298 |
+
0.15,
|
| 299 |
+
0.45,
|
| 300 |
+
0.70,
|
| 301 |
+
*DEFAULT_SEVERITY_ARGS,
|
| 302 |
+
)
|
| 303 |
+
try:
|
| 304 |
+
text = pathlib.Path(path).read_text(encoding="utf-8")
|
| 305 |
+
self.assertIn("severity_mode", text.splitlines()[0])
|
| 306 |
+
self.assertIn("pnr_cost", text.splitlines()[0])
|
| 307 |
+
finally:
|
| 308 |
+
try:
|
| 309 |
+
os.remove(path)
|
| 310 |
+
except FileNotFoundError:
|
| 311 |
+
pass
|
| 312 |
+
|
| 313 |
def test_def_export_returns_fragment_file(self):
|
| 314 |
qc = QuantumStateVector(2)
|
| 315 |
qc.apply_single("H", target=0)
|
|
|
|
| 331 |
0.15,
|
| 332 |
0.45,
|
| 333 |
0.70,
|
| 334 |
+
*DEFAULT_SEVERITY_ARGS,
|
|
|
|
| 335 |
"0",
|
| 336 |
"0",
|
| 337 |
"40",
|
|
|
|
| 370 |
0.15,
|
| 371 |
0.45,
|
| 372 |
0.70,
|
| 373 |
+
*DEFAULT_SEVERITY_ARGS,
|
|
|
|
| 374 |
"0",
|
| 375 |
"0",
|
| 376 |
"40",
|
|
|
|
| 417 |
0.15,
|
| 418 |
0.45,
|
| 419 |
0.70,
|
| 420 |
+
*DEFAULT_SEVERITY_ARGS,
|
|
|
|
| 421 |
"0",
|
| 422 |
"0",
|
| 423 |
"40",
|
|
|
|
| 451 |
0.15,
|
| 452 |
0.45,
|
| 453 |
0.70,
|
| 454 |
+
*DEFAULT_SEVERITY_ARGS,
|
|
|
|
| 455 |
"0",
|
| 456 |
"0",
|
| 457 |
"-1",
|
tests/test_eda_translator.py
CHANGED
|
@@ -9,6 +9,7 @@ class EDATranslatorTest(unittest.TestCase):
|
|
| 9 |
def test_mapping_sorted_by_risk_and_contains_fields(self):
|
| 10 |
metrics = {
|
| 11 |
"composite_risk": np.array([0.25, 0.82, 0.51], dtype=float),
|
|
|
|
| 12 |
"gate_error": np.array([0.01, 0.03, 0.02], dtype=float),
|
| 13 |
"readout_error": np.array([0.02, 0.04, 0.03], dtype=float),
|
| 14 |
"decoherence_risk": np.array([0.2, 0.7, 0.4], dtype=float),
|
|
@@ -20,6 +21,9 @@ class EDATranslatorTest(unittest.TestCase):
|
|
| 20 |
self.assertEqual(rows[0]["tier"], "CRITICAL")
|
| 21 |
self.assertIn("timing_derate", rows[0])
|
| 22 |
self.assertIn("guardband_mv", rows[0])
|
|
|
|
|
|
|
|
|
|
| 23 |
self.assertIsNone(rows[0]["layout_row"])
|
| 24 |
self.assertIsNone(rows[0]["layout_col"])
|
| 25 |
q2 = next(r for r in rows if int(r["qubit"]) == 2)
|
|
@@ -29,6 +33,7 @@ class EDATranslatorTest(unittest.TestCase):
|
|
| 29 |
def test_export_generates_nonempty_scripts(self):
|
| 30 |
metrics = {
|
| 31 |
"composite_risk": np.array([0.2, 0.6], dtype=float),
|
|
|
|
| 32 |
"gate_error": np.array([0.01, 0.02], dtype=float),
|
| 33 |
"readout_error": np.array([0.02, 0.03], dtype=float),
|
| 34 |
"decoherence_risk": np.array([0.2, 0.5], dtype=float),
|
|
@@ -40,11 +45,14 @@ class EDATranslatorTest(unittest.TestCase):
|
|
| 40 |
|
| 41 |
self.assertIn("Generated by Quread", tcl)
|
| 42 |
self.assertIn("quread_q1_risk", tcl)
|
|
|
|
|
|
|
| 43 |
self.assertIn("set quread_q1_row 0", tcl)
|
| 44 |
self.assertIn("set quread_q1_col 1", tcl)
|
| 45 |
self.assertIn("qureadRiskRows", skill)
|
| 46 |
self.assertIn('list("q1"', skill)
|
| 47 |
-
self.assertIn(' 0 1 "MEDIUM"
|
|
|
|
| 48 |
|
| 49 |
|
| 50 |
if __name__ == "__main__":
|
|
|
|
| 9 |
def test_mapping_sorted_by_risk_and_contains_fields(self):
|
| 10 |
metrics = {
|
| 11 |
"composite_risk": np.array([0.25, 0.82, 0.51], dtype=float),
|
| 12 |
+
"activity_norm": np.array([0.25, 0.82, 0.51], dtype=float),
|
| 13 |
"gate_error": np.array([0.01, 0.03, 0.02], dtype=float),
|
| 14 |
"readout_error": np.array([0.02, 0.04, 0.03], dtype=float),
|
| 15 |
"decoherence_risk": np.array([0.2, 0.7, 0.4], dtype=float),
|
|
|
|
| 21 |
self.assertEqual(rows[0]["tier"], "CRITICAL")
|
| 22 |
self.assertIn("timing_derate", rows[0])
|
| 23 |
self.assertIn("guardband_mv", rows[0])
|
| 24 |
+
self.assertIn("severity_score", rows[0])
|
| 25 |
+
self.assertIn("pnr_cost", rows[0])
|
| 26 |
+
self.assertIn("density_cap", rows[0])
|
| 27 |
self.assertIsNone(rows[0]["layout_row"])
|
| 28 |
self.assertIsNone(rows[0]["layout_col"])
|
| 29 |
q2 = next(r for r in rows if int(r["qubit"]) == 2)
|
|
|
|
| 33 |
def test_export_generates_nonempty_scripts(self):
|
| 34 |
metrics = {
|
| 35 |
"composite_risk": np.array([0.2, 0.6], dtype=float),
|
| 36 |
+
"activity_norm": np.array([0.2, 0.6], dtype=float),
|
| 37 |
"gate_error": np.array([0.01, 0.02], dtype=float),
|
| 38 |
"readout_error": np.array([0.02, 0.03], dtype=float),
|
| 39 |
"decoherence_risk": np.array([0.2, 0.5], dtype=float),
|
|
|
|
| 45 |
|
| 46 |
self.assertIn("Generated by Quread", tcl)
|
| 47 |
self.assertIn("quread_q1_risk", tcl)
|
| 48 |
+
self.assertIn("quread_q1_pnr_cost", tcl)
|
| 49 |
+
self.assertIn("quread_q1_density_cap", tcl)
|
| 50 |
self.assertIn("set quread_q1_row 0", tcl)
|
| 51 |
self.assertIn("set quread_q1_col 1", tcl)
|
| 52 |
self.assertIn("qureadRiskRows", skill)
|
| 53 |
self.assertIn('list("q1"', skill)
|
| 54 |
+
self.assertIn(' 0 1 "MEDIUM"', skill)
|
| 55 |
+
self.assertIn('"composite_risk" "linear"', skill)
|
| 56 |
|
| 57 |
|
| 58 |
if __name__ == "__main__":
|
tests/test_eda_view.py
CHANGED
|
@@ -15,6 +15,12 @@ class EDAViewParserTest(unittest.TestCase):
|
|
| 15 |
"set quread_q1_tier CRITICAL",
|
| 16 |
"set quread_q1_timing_derate 1.12",
|
| 17 |
"set quread_q1_guardband_mv 41.0",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
"set quread_q1_row 0",
|
| 19 |
"set quread_q1_col 1",
|
| 20 |
"",
|
|
@@ -34,12 +40,14 @@ class EDAViewParserTest(unittest.TestCase):
|
|
| 34 |
self.assertEqual(rows[0]["tier"], "CRITICAL")
|
| 35 |
self.assertEqual(rows[0]["layout_row"], 0)
|
| 36 |
self.assertEqual(rows[0]["layout_col"], 1)
|
|
|
|
|
|
|
| 37 |
|
| 38 |
def test_parse_cadence_skill_rows(self):
|
| 39 |
text = "\n".join(
|
| 40 |
[
|
| 41 |
-
'qureadRiskRows = cons(list("q0" 0.200000 "OK" 1.030000 14.000 1 2 "LOW") qureadRiskRows)',
|
| 42 |
-
'qureadRiskRows = cons(list("q2" 0.910000 "CRITICAL" 1.140000 55.000 2 1 "HIGH") qureadRiskRows)',
|
| 43 |
]
|
| 44 |
)
|
| 45 |
rows, meta = parse_eda_text(text, "cadence_skill")
|
|
@@ -49,6 +57,8 @@ class EDAViewParserTest(unittest.TestCase):
|
|
| 49 |
self.assertEqual(rows[0]["route_priority"], "HIGH")
|
| 50 |
self.assertEqual(rows[0]["layout_row"], 2)
|
| 51 |
self.assertEqual(rows[0]["layout_col"], 1)
|
|
|
|
|
|
|
| 52 |
|
| 53 |
def test_auto_detect_prefers_recognized_format(self):
|
| 54 |
syn_text = "set quread_q0_risk 0.5"
|
|
|
|
| 15 |
"set quread_q1_tier CRITICAL",
|
| 16 |
"set quread_q1_timing_derate 1.12",
|
| 17 |
"set quread_q1_guardband_mv 41.0",
|
| 18 |
+
"set quread_q1_severity_score 0.82",
|
| 19 |
+
"set quread_q1_severity_percentile 100.0",
|
| 20 |
+
"set quread_q1_pnr_cost 82.0",
|
| 21 |
+
"set quread_q1_density_cap 50.8",
|
| 22 |
+
"set quread_q1_source_metric composite_risk",
|
| 23 |
+
"set quread_q1_severity_mode linear",
|
| 24 |
"set quread_q1_row 0",
|
| 25 |
"set quread_q1_col 1",
|
| 26 |
"",
|
|
|
|
| 40 |
self.assertEqual(rows[0]["tier"], "CRITICAL")
|
| 41 |
self.assertEqual(rows[0]["layout_row"], 0)
|
| 42 |
self.assertEqual(rows[0]["layout_col"], 1)
|
| 43 |
+
self.assertAlmostEqual(rows[0]["severity_score"], 0.82, places=6)
|
| 44 |
+
self.assertAlmostEqual(rows[0]["pnr_cost"], 82.0, places=6)
|
| 45 |
|
| 46 |
def test_parse_cadence_skill_rows(self):
|
| 47 |
text = "\n".join(
|
| 48 |
[
|
| 49 |
+
'qureadRiskRows = cons(list("q0" 0.200000 "OK" 1.030000 14.000 1 2 "LOW" 0.200000 0.000000 20.000000 88.000000 "composite_risk" "linear") qureadRiskRows)',
|
| 50 |
+
'qureadRiskRows = cons(list("q2" 0.910000 "CRITICAL" 1.140000 55.000 2 1 "HIGH" 0.950000 100.000000 95.000000 43.000000 "weighted_blend" "pnr_cost") qureadRiskRows)',
|
| 51 |
]
|
| 52 |
)
|
| 53 |
rows, meta = parse_eda_text(text, "cadence_skill")
|
|
|
|
| 57 |
self.assertEqual(rows[0]["route_priority"], "HIGH")
|
| 58 |
self.assertEqual(rows[0]["layout_row"], 2)
|
| 59 |
self.assertEqual(rows[0]["layout_col"], 1)
|
| 60 |
+
self.assertAlmostEqual(rows[0]["pnr_cost"], 95.0, places=6)
|
| 61 |
+
self.assertEqual(rows[0]["severity_mode"], "pnr_cost")
|
| 62 |
|
| 63 |
def test_auto_detect_prefers_recognized_format(self):
|
| 64 |
syn_text = "set quread_q0_risk 0.5"
|
tests/test_severity_mapper.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import unittest
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from quread.severity_mapper import SeverityConfig, SeverityThresholds, SeverityWeights, compute_severity_rows, severity_rows_to_csv
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class SeverityMapperTest(unittest.TestCase):
|
| 9 |
+
def _metrics(self):
|
| 10 |
+
return {
|
| 11 |
+
"activity_count": np.array([1.0, 4.0, 2.0], dtype=float),
|
| 12 |
+
"activity_norm": np.array([0.25, 1.0, 0.5], dtype=float),
|
| 13 |
+
"gate_error": np.array([0.02, 0.75, 0.30], dtype=float),
|
| 14 |
+
"readout_error": np.array([0.03, 0.40, 0.08], dtype=float),
|
| 15 |
+
"coherence_health": np.array([0.95, 0.30, 0.70], dtype=float),
|
| 16 |
+
"decoherence_risk": np.array([0.10, 0.80, 0.35], dtype=float),
|
| 17 |
+
"fidelity": np.array([0.98, 0.50, 0.90], dtype=float),
|
| 18 |
+
"state_fidelity": np.array([0.99, 0.55, 0.92], dtype=float),
|
| 19 |
+
"process_fidelity": np.array([0.98, 0.60, 0.91], dtype=float),
|
| 20 |
+
"composite_risk": np.array([0.18, 0.86, 0.41], dtype=float),
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
def test_linear_mode_uses_selected_heatmap_metric(self):
|
| 24 |
+
rows = compute_severity_rows(
|
| 25 |
+
self._metrics(),
|
| 26 |
+
cfg=SeverityConfig(mode="linear", source_metric="gate_error"),
|
| 27 |
+
qubit_coords={0: (0, 0), 1: (0, 1), 2: (1, 0)},
|
| 28 |
+
)
|
| 29 |
+
self.assertEqual(rows[0]["qubit"], 1)
|
| 30 |
+
self.assertEqual(rows[0]["severity_band"], "CRITICAL")
|
| 31 |
+
self.assertAlmostEqual(rows[0]["severity_score"], 0.75, places=6)
|
| 32 |
+
self.assertEqual(rows[0]["source_metric"], "gate_error")
|
| 33 |
+
|
| 34 |
+
def test_bucket_mode_quantizes_to_three_levels(self):
|
| 35 |
+
rows = compute_severity_rows(
|
| 36 |
+
self._metrics(),
|
| 37 |
+
cfg=SeverityConfig(
|
| 38 |
+
mode="bucket",
|
| 39 |
+
source_metric="composite_risk",
|
| 40 |
+
thresholds=SeverityThresholds(warning=0.4, critical=0.7),
|
| 41 |
+
),
|
| 42 |
+
)
|
| 43 |
+
values = {int(r["qubit"]): float(r["severity_score"]) for r in rows}
|
| 44 |
+
self.assertEqual(values[0], 0.0)
|
| 45 |
+
self.assertEqual(values[1], 1.0)
|
| 46 |
+
self.assertEqual(values[2], 0.5)
|
| 47 |
+
|
| 48 |
+
def test_percentile_mode_uses_current_session_ranking(self):
|
| 49 |
+
rows = compute_severity_rows(
|
| 50 |
+
self._metrics(),
|
| 51 |
+
cfg=SeverityConfig(
|
| 52 |
+
mode="percentile",
|
| 53 |
+
source_metric="composite_risk",
|
| 54 |
+
thresholds=SeverityThresholds(percentile_warning=50.0, percentile_critical=90.0),
|
| 55 |
+
),
|
| 56 |
+
)
|
| 57 |
+
by_q = {int(r["qubit"]): r for r in rows}
|
| 58 |
+
self.assertEqual(by_q[1]["severity_band"], "CRITICAL")
|
| 59 |
+
self.assertEqual(by_q[1]["severity_rank"], 1)
|
| 60 |
+
self.assertGreater(by_q[1]["severity_percentile"], by_q[2]["severity_percentile"])
|
| 61 |
+
|
| 62 |
+
def test_weighted_mode_uses_separate_weight_model(self):
|
| 63 |
+
rows = compute_severity_rows(
|
| 64 |
+
self._metrics(),
|
| 65 |
+
cfg=SeverityConfig(
|
| 66 |
+
mode="weighted",
|
| 67 |
+
weights=SeverityWeights(activity=0.0, gate_error=1.0, readout_error=0.0, decoherence=0.0, fidelity=0.0),
|
| 68 |
+
),
|
| 69 |
+
)
|
| 70 |
+
self.assertEqual(rows[0]["qubit"], 1)
|
| 71 |
+
self.assertEqual(rows[0]["source_metric"], "weighted_blend")
|
| 72 |
+
self.assertAlmostEqual(rows[0]["severity_score"], 0.75, places=6)
|
| 73 |
+
|
| 74 |
+
def test_pnr_cost_mode_emits_richer_outputs(self):
|
| 75 |
+
rows = compute_severity_rows(
|
| 76 |
+
self._metrics(),
|
| 77 |
+
cfg=SeverityConfig(mode="pnr_cost"),
|
| 78 |
+
)
|
| 79 |
+
top = rows[0]
|
| 80 |
+
self.assertEqual(top["severity_band"], "CRITICAL")
|
| 81 |
+
self.assertGreater(top["pnr_cost"], 0.0)
|
| 82 |
+
self.assertGreater(top["timing_derate"], 1.0)
|
| 83 |
+
self.assertLess(top["density_cap"], 100.0)
|
| 84 |
+
self.assertIn(top["route_priority"], {"LOW", "MEDIUM", "HIGH"})
|
| 85 |
+
|
| 86 |
+
def test_severity_rows_to_csv_contains_headers(self):
|
| 87 |
+
csv_text = severity_rows_to_csv(
|
| 88 |
+
compute_severity_rows(
|
| 89 |
+
self._metrics(),
|
| 90 |
+
cfg=SeverityConfig(mode="linear", source_metric="composite_risk"),
|
| 91 |
+
qubit_coords={0: (0, 0), 1: (0, 1), 2: (1, 0)},
|
| 92 |
+
)
|
| 93 |
+
)
|
| 94 |
+
self.assertIn("severity_mode", csv_text.splitlines()[0])
|
| 95 |
+
self.assertIn("pnr_cost", csv_text.splitlines()[0])
|
| 96 |
+
self.assertIn("\n1,0,1,", csv_text)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
if __name__ == "__main__":
|
| 100 |
+
unittest.main()
|