import sys from dataclasses import dataclass from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SRC_DIR = ROOT / "src" if str(SRC_DIR) not in sys.path: sys.path.insert(0, str(SRC_DIR)) import renunney.track1_analysis as analysis import renunney.track1_api as api from track1_reference import GenerationSummary, Track1Parameters def test_fit_linear_cost_by_loci_recovers_line(): rows = [ analysis.LocusThresholdRow(n=1, threshold_T=12.0, accepted=True), analysis.LocusThresholdRow(n=2, threshold_T=14.0, accepted=True), analysis.LocusThresholdRow(n=3, threshold_T=16.0, accepted=True), ] fit = analysis.fit_linear_cost_by_loci(rows) assert fit is not None assert abs(fit.intercept_c0 - 10.0) < 1e-9 assert abs(fit.slope_c1 - 2.0) < 1e-9 def test_sweep_number_of_loci_uses_search_results(monkeypatch): params = Track1Parameters(K=5000, N0=20, n=1, u=5e-6, R=10.0, T=20) class Dummy: def __init__(self, threshold_T): self.threshold_T = threshold_T def fake_search(params, candidate_T_values, runs=20, seed_start=0, cache_path=None, jobs=1): return Dummy(threshold_T=10.0 + params.n) monkeypatch.setattr(analysis, "search_threshold_over_candidates", fake_search) sweep = analysis.sweep_number_of_loci(params, [1, 2, 3], [10, 20, 30], runs=2, seed_start=1, jobs=3) assert [row.threshold_T for row in sweep.rows] == [11.0, 12.0, 13.0] assert sweep.fit is not None def test_run_config_loci_regression_mode(monkeypatch): @dataclass(frozen=True) class DummyFit: intercept_c0: float = 5.0 slope_c1: float = 2.0 r_squared: float = 1.0 points_used: int = 3 class DummySweep: rows = [ analysis.LocusThresholdRow(n=1, threshold_T=7.0, accepted=True), analysis.LocusThresholdRow(n=2, threshold_T=9.0, accepted=True), analysis.LocusThresholdRow(n=3, threshold_T=11.0, accepted=True), ] fit = DummyFit() monkeypatch.setattr(api, "sweep_number_of_loci", lambda *args, **kwargs: DummySweep()) config = api.Track1RunConfig( mode="loci_regression", loci_values=[1, 2, 3], t_start=10, t_stop=30, t_step=10, runs=2, ) payload = api.run_config(config) assert payload["mode"] == "loci_regression" assert payload["loci_values"] == [1, 2, 3] assert payload["fit"]["slope_c1"] == 2.0 def test_summarize_tracking_detects_post_initial_nonzero_alleles(): summaries = [ GenerationSummary( t=-2, N=10, female_fraction=0.5, male_count=5, female_count=5, fecundity=1.0, mean_fitness=1.0, mean_expected_female_productivity=1.0, target_value=-0.1, mean_allele_value=0.0, mean_genotype_value=0.0, mean_tracking_gap=0.1, paper_M=0.05, expected_mutations_current_N=0.0001, realized_mutation_count=0, realized_mutation_rate_per_allele=0.0, birth_count=0, surviving_offspring_count=0, ne_approx=5.0, extinct=False, ), GenerationSummary( t=-1, N=10, female_fraction=0.5, male_count=5, female_count=5, fecundity=1.0, mean_fitness=1.0, mean_expected_female_productivity=1.0, target_value=-0.05, mean_allele_value=0.2, mean_genotype_value=0.2, mean_tracking_gap=0.25, paper_M=0.05, expected_mutations_current_N=0.0001, realized_mutation_count=1, realized_mutation_rate_per_allele=0.05, birth_count=2, surviving_offspring_count=1, ne_approx=5.0, extinct=False, ), ] tracking = analysis.summarize_tracking(summaries) assert tracking.extinction_occurred is False assert tracking.first_extinction_t is None assert tracking.first_nonzero_allele_t == -1 assert tracking.last_nonzero_allele_t == -1 assert tracking.stayed_zero_after_initialization is False def test_summarize_tracking_detects_extinction_time(): summaries = [ GenerationSummary( t=0, N=10, female_fraction=0.5, male_count=5, female_count=5, fecundity=1.0, mean_fitness=1.0, mean_expected_female_productivity=1.0, target_value=0.0, mean_allele_value=0.0, mean_genotype_value=0.0, mean_tracking_gap=0.0, paper_M=0.05, expected_mutations_current_N=0.0001, realized_mutation_count=0, realized_mutation_rate_per_allele=0.0, birth_count=0, surviving_offspring_count=0, ne_approx=5.0, extinct=False, ), GenerationSummary( t=1, N=0, female_fraction=0.0, male_count=0, female_count=0, fecundity=0.0, mean_fitness=0.0, mean_expected_female_productivity=0.0, target_value=0.1, mean_allele_value=0.0, mean_genotype_value=0.0, mean_tracking_gap=-0.1, paper_M=0.05, expected_mutations_current_N=0.0, realized_mutation_count=0, realized_mutation_rate_per_allele=0.0, birth_count=0, surviving_offspring_count=0, ne_approx=0.0, extinct=True, ), ] tracking = analysis.summarize_tracking(summaries) assert tracking.extinction_occurred is True assert tracking.first_extinction_t == 1