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
29 changes: 29 additions & 0 deletions src/tracer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class EmbeddingConfig:
batch_size: int = 128
normalize: bool = True

def __post_init__(self) -> None:
if self.batch_size <= 0:
raise ValueError(f"batch_size must be > 0, got {self.batch_size}")


@dataclass
class FitConfig:
Expand All @@ -35,3 +39,28 @@ class FitConfig:
# on ~3k × 22-class embedding inputs). See `tracer.fit.surrogate._candidates`
# for the full set of names.
skip_candidates: Tuple[str, ...] = ()

def __post_init__(self) -> None:
if not 0.0 < self.target_teacher_agreement <= 1.0:
raise ValueError(
"target_teacher_agreement must be in (0, 1], got "
f"{self.target_teacher_agreement}")
if not self.frontier_targets:
raise ValueError("frontier_targets must not be empty")
for t in self.frontier_targets:
if not 0.0 < t <= 1.0:
raise ValueError(
f"frontier_targets values must be in (0, 1], got {t}")
if not 0.0 <= self.min_deploy_coverage <= 1.0:
raise ValueError(
"min_deploy_coverage must be in [0, 1], got "
f"{self.min_deploy_coverage}")
if self.max_fit_labels <= 0:
raise ValueError(f"max_fit_labels must be > 0, got {self.max_fit_labels}")
if not 0.0 <= self.explore_rate <= 1.0:
raise ValueError(f"explore_rate must be in [0, 1], got {self.explore_rate}")
if self.retrain_every <= 0:
raise ValueError(f"retrain_every must be > 0, got {self.retrain_every}")
if self.min_labels_to_start < 0:
raise ValueError(
f"min_labels_to_start must be >= 0, got {self.min_labels_to_start}")
54 changes: 54 additions & 0 deletions tests/test_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,55 @@ def test_public_exports():
assert hasattr(tracer, "RepresentativeExample")


# ── Config validation ───────────────────────────────────────────────────────

def test_fitconfig_rejects_out_of_range_ta():
"""target_teacher_agreement must be in (0, 1]."""
from tracer.config import FitConfig
with pytest.raises(ValueError):
FitConfig(target_teacher_agreement=1.5)
with pytest.raises(ValueError):
FitConfig(target_teacher_agreement=-0.2)
with pytest.raises(ValueError):
FitConfig(target_teacher_agreement=0.0)


def test_fitconfig_rejects_empty_frontier_targets():
"""frontier_targets must be non-empty and within (0, 1]."""
from tracer.config import FitConfig
with pytest.raises(ValueError):
FitConfig(frontier_targets=())
with pytest.raises(ValueError):
FitConfig(frontier_targets=(0.9, 1.2))


def test_fitconfig_rejects_bad_scalars():
"""min_deploy_coverage, max_fit_labels, explore_rate, retrain_every are guarded."""
from tracer.config import FitConfig
with pytest.raises(ValueError):
FitConfig(min_deploy_coverage=1.5)
with pytest.raises(ValueError):
FitConfig(max_fit_labels=0)
with pytest.raises(ValueError):
FitConfig(explore_rate=2.0)
with pytest.raises(ValueError):
FitConfig(retrain_every=0)


def test_fitconfig_accepts_valid_config():
"""Default config and a typical override construct without error."""
from tracer.config import FitConfig
FitConfig()
FitConfig(target_teacher_agreement=0.95)


def test_embeddingconfig_rejects_nonpositive_batch_size():
from tracer.config import EmbeddingConfig
with pytest.raises(ValueError):
EmbeddingConfig(batch_size=0)
EmbeddingConfig(batch_size=64)


if __name__ == "__main__":
import traceback
tests = [
Expand All @@ -384,6 +433,11 @@ def test_public_exports():
test_skip_candidates_removes_models,
test_demo_runs,
test_public_exports,
test_fitconfig_rejects_out_of_range_ta,
test_fitconfig_rejects_empty_frontier_targets,
test_fitconfig_rejects_bad_scalars,
test_fitconfig_accepts_valid_config,
test_embeddingconfig_rejects_nonpositive_batch_size,
]
for t in tests:
try:
Expand Down
Loading