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
10 changes: 10 additions & 0 deletions src/qc_compiler/autotuning.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
20 changes: 20 additions & 0 deletions src/qc_compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
23 changes: 22 additions & 1 deletion tests/test_autotuning.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
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
24 changes: 23 additions & 1 deletion tests/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
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")
Loading