diff --git a/src/qc_compiler/autotuning.py b/src/qc_compiler/autotuning.py index b909ade..fd79ac9 100644 --- a/src/qc_compiler/autotuning.py +++ b/src/qc_compiler/autotuning.py @@ -61,6 +61,16 @@ class TranspileConfig: gate_fusion: bool = True scheduling_method: str = "coherence_aware" + def __post_init__(self): + if not isinstance(self.optimization_level, int) or not 0 <= self.optimization_level <= 3: + raise ValueError( + f"optimization_level must be 0-3, got {self.optimization_level}." + ) + if not isinstance(self.seed, int) or self.seed < 0: + raise ValueError( + f"seed must be a non-negative integer, got {self.seed}." + ) + def config_key(self) -> str: """Generate a unique string key for this configuration.""" return ( diff --git a/src/qc_compiler/transpiler.py b/src/qc_compiler/transpiler.py index 8d30485..a52a9c0 100644 --- a/src/qc_compiler/transpiler.py +++ b/src/qc_compiler/transpiler.py @@ -59,6 +59,21 @@ class OptimizerConfig: batch: bool = True autotune: bool = False + def __post_init__(self): + valid_mitigation = {"adaptive", "zne", "pec", "cdr", "none"} + valid_scheduling = {"asap", "alap", "coherence_aware", "none"} + + if self.mitigation not in valid_mitigation: + raise ValueError( + f"Invalid mitigation '{self.mitigation}'. " + f"Choose from {valid_mitigation}." + ) + if self.scheduling not in valid_scheduling: + raise ValueError( + f"Invalid scheduling '{self.scheduling}'. " + f"Choose from {valid_scheduling}." + ) + @dataclass class QCompilerResult: @@ -171,6 +186,11 @@ def optimize( if config is None: config = OptimizerConfig() + if circuit is None: + raise ValueError("circuit must not be None") + if circuit.num_qubits == 0: + raise ValueError("circuit must have at least one qubit") + result = QCompilerResult( original_circuit=circuit.copy(), config=config, diff --git a/tests/test_autotuning.py b/tests/test_autotuning.py index 9d30277..bc1918c 100644 --- a/tests/test_autotuning.py +++ b/tests/test_autotuning.py @@ -320,4 +320,25 @@ def test_search_space_covers_all_optimization_levels(self): tuner = AutoTuner(cost_model=CostModel()) configs = tuner._generate_configurations() opt_levels = {c.optimization_level for c in configs} - assert opt_levels == {1, 2, 3} \ No newline at end of file + assert opt_levels == {1, 2, 3} + + +class TestTranspileConfigValidation: + """Regression tests for TranspileConfig validation (issue #59).""" + + def test_invalid_optimization_level_raises(self): + with pytest.raises(ValueError, match="optimization_level"): + TranspileConfig(optimization_level=5) + + def test_negative_seed_raises(self): + with pytest.raises(ValueError, match="seed"): + TranspileConfig(seed=-1) + + def test_valid_config_does_not_raise(self): + config = TranspileConfig( + routing_method="stochastic", + layout_method="dense", + optimization_level=2, + seed=42, + ) + assert config.optimization_level == 2 \ No newline at end of file diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index b631d65..b9c5e90 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -392,4 +392,26 @@ def test_cutting_with_scheduling_applies_to_subcircuits(self): assert result.cutting_result is not None if result.cutting_result.should_cut and result.subcircuits is not None: assert len(result.subcircuits) > 1 - assert result.fidelity_after > 0 \ No newline at end of file + assert result.fidelity_after > 0 + + +class TestInputValidation: + """Regression tests for input validation (issue #59).""" + + def test_none_circuit_raises(self): + compiler = QCompiler() + with pytest.raises(ValueError, match="circuit must not be None"): + compiler.optimize(None) + + def test_empty_circuit_raises(self): + compiler = QCompiler() + with pytest.raises(ValueError, match="at least one qubit"): + compiler.optimize(QuantumCircuit(0)) + + def test_invalid_mitigation_raises(self): + with pytest.raises(ValueError, match="Invalid mitigation"): + OptimizerConfig(mitigation="invalid") + + def test_invalid_scheduling_raises(self): + with pytest.raises(ValueError, match="Invalid scheduling"): + OptimizerConfig(scheduling="invalid") \ No newline at end of file