180 lines
5.7 KiB
Python
180 lines
5.7 KiB
Python
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
|