diff --git a/src/qc_compiler/mitigation.py b/src/qc_compiler/mitigation.py index 33e46cc..feb3de5 100644 --- a/src/qc_compiler/mitigation.py +++ b/src/qc_compiler/mitigation.py @@ -63,7 +63,7 @@ class MitigationPlan: subcircuit_sensitivity: Mapping from segment index to sensitivity score. total_shots: Total number of shots across all scales. - method: Mitigation method ('zne', 'pec', or 'cdr'). + method: Mitigation method ('zne', 'adaptive', 'pec', or 'cdr'). segments: Number of circuit segments identified. shots_per_segment: Mapping from segment index to shot count. scales_per_segment: Mapping from segment index to list of @@ -175,19 +175,21 @@ def create_plan( variational sensitivity. If None, uses two-qubit gate count as a proxy for sensitivity. total_shots: Total shot budget. - method: Mitigation method ('zne', 'pec', or 'cdr'). + method: Mitigation method ('zne', 'adaptive', 'pec', or 'cdr'). num_segments: Number of circuit segments. If None, segments are determined by two-qubit gate boundaries. Returns: A MitigationPlan with allocation details. """ - if method not in ("zne", "pec", "cdr"): + if method not in ("zne", "pec", "cdr", "adaptive"): raise ValueError( f"Unknown mitigation method '{method}'. " - "Use 'zne', 'pec', or 'cdr'." + "Use 'zne', 'pec', 'cdr', or 'adaptive'." ) + resolved_method = "zne" if method == "adaptive" else method + if circuit.num_qubits == 0 or circuit.depth() == 0: return MitigationPlan( noise_scales=[1.0], @@ -208,7 +210,7 @@ def create_plan( ) scales_per_segment = self._assign_noise_scales( - sensitivity, method + sensitivity, resolved_method ) all_scales = sorted( @@ -258,11 +260,13 @@ def execute( Returns: A MitigationResult with mitigated values. """ - if plan.method == "zne": + resolved_method = "zne" if plan.method == "adaptive" else plan.method + + if resolved_method == "zne": return self._extrapolate_zne(plan, raw_values) - elif plan.method == "pec": + elif resolved_method == "pec": return self._execute_pec(plan, raw_values) - elif plan.method == "cdr": + elif resolved_method == "cdr": return self._execute_cdr(plan, raw_values) else: return MitigationResult(method=plan.method) @@ -469,7 +473,7 @@ def _extrapolate_zne( raw_values = self._simulate_values(plan) if not scales or not raw_values: - return MitigationResult(method="zne") + return MitigationResult(method=plan.method) if len(raw_values) == 1: return MitigationResult( @@ -478,7 +482,7 @@ def _extrapolate_zne( noise_scales=scales, extrapolation_coefficients=[1.0], shots_used=plan.total_shots, - method="zne", + method=plan.method, ) if len(raw_values) == 2: @@ -501,7 +505,7 @@ def _extrapolate_zne( noise_scales=scales[:2], extrapolation_coefficients=[c1, c2] if abs(denom) >= 1e-12 else [1.0, 0.0], shots_used=plan.total_shots, - method="zne", + method=plan.method, ) coeffs = self._richardson_coefficients(scales[:len(raw_values)]) @@ -513,7 +517,7 @@ def _extrapolate_zne( noise_scales=scales[:len(raw_values)], extrapolation_coefficients=coeffs, shots_used=plan.total_shots, - method="zne", + method=plan.method, ) def _execute_pec( diff --git a/src/qc_compiler/transpiler.py b/src/qc_compiler/transpiler.py index 9dd08b0..7686f7f 100644 --- a/src/qc_compiler/transpiler.py +++ b/src/qc_compiler/transpiler.py @@ -237,11 +237,8 @@ def optimize( ) if config.mitigation != "none": - mitigation_method = config.mitigation - if mitigation_method == "adaptive": - mitigation_method = "zne" sub_mitigation = self.mitigation.create_plan( - sub_current, method=mitigation_method + sub_current, method=config.mitigation ) sub_result.mitigation_plan = sub_mitigation sub_result.passes_applied.append( @@ -280,11 +277,8 @@ def optimize( # Pass 5: Error Mitigation if config.mitigation != "none": - mitigation_method = config.mitigation - if mitigation_method == "adaptive": - mitigation_method = "zne" mitigation_plan = self.mitigation.create_plan( - current_circuit, method=mitigation_method + current_circuit, method=config.mitigation ) result.mitigation_plan = mitigation_plan passes_applied.append(f"mitigation:{config.mitigation}") diff --git a/tests/test_mitigation.py b/tests/test_mitigation.py index a70053a..2d33f10 100644 --- a/tests/test_mitigation.py +++ b/tests/test_mitigation.py @@ -439,4 +439,35 @@ def test_zne_result_is_not_placeholder(self): result = mitigation.execute(qc, plan) assert result.placeholder is False assert result.method == "zne" - assert plan.subcircuit_sensitivity is not None \ No newline at end of file + assert plan.subcircuit_sensitivity is not None + + +class TestAdaptiveMitigation: + """Regression tests for 'adaptive' mitigation method (issue #47).""" + + def test_adaptive_creates_plan(self): + mitigation = AdaptiveErrorMitigation(CostModel()) + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + plan = mitigation.create_plan(qc, method="adaptive") + assert plan.method == "adaptive" + + def test_adaptive_executes_as_zne(self): + mitigation = AdaptiveErrorMitigation(CostModel()) + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + plan = mitigation.create_plan(qc, method="adaptive") + result = mitigation.execute(qc, plan) + assert result.method == "adaptive" + assert result.placeholder is False + + def test_adaptive_uses_zne_noise_scales(self): + mitigation = AdaptiveErrorMitigation(CostModel()) + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + plan_adaptive = mitigation.create_plan(qc, method="adaptive") + plan_zne = mitigation.create_plan(qc, method="zne") + assert plan_adaptive.noise_scales == plan_zne.noise_scales \ No newline at end of file