Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| """Tests for the latent-state simulator modules.""" | |
| import pytest | |
| from models import ActionType, ExperimentAction, OutputType | |
| from server.simulator.latent_state import ( | |
| CellPopulation, | |
| ExperimentProgress, | |
| FullLatentState, | |
| LatentBiologicalState, | |
| ResourceState, | |
| TechnicalState, | |
| ) | |
| from server.simulator.noise import NoiseModel | |
| from server.simulator.output_generator import OutputGenerator | |
| from server.simulator.transition import TransitionEngine | |
| def _make_state() -> FullLatentState: | |
| return FullLatentState( | |
| biology=LatentBiologicalState( | |
| cell_populations=[ | |
| CellPopulation(name="A", proportion=0.6, marker_genes=["G1"]), | |
| CellPopulation(name="B", proportion=0.4, marker_genes=["G2"]), | |
| ], | |
| true_de_genes={"disease_vs_healthy": {"G1": 2.0, "G2": -1.5}}, | |
| true_pathways={"apoptosis": 0.7}, | |
| true_markers=["G1"], | |
| causal_mechanisms=["G1-driven apoptosis"], | |
| n_true_cells=5000, | |
| ), | |
| technical=TechnicalState(dropout_rate=0.1, doublet_rate=0.04), | |
| progress=ExperimentProgress(), | |
| resources=ResourceState(budget_total=50_000, time_limit_days=90), | |
| ) | |
| class TestNoiseModel: | |
| def test_deterministic_with_seed(self): | |
| n1 = NoiseModel(seed=42) | |
| n2 = NoiseModel(seed=42) | |
| assert n1.sample_qc_metric(0.5, 0.1) == n2.sample_qc_metric(0.5, 0.1) | |
| def test_false_positives(self): | |
| n = NoiseModel(seed=0) | |
| fps = n.generate_false_positives(1000, 0.01) | |
| assert all(g.startswith("FP_GENE_") for g in fps) | |
| def test_quality_degradation_bounded(self): | |
| n = NoiseModel(seed=0) | |
| for _ in range(100): | |
| q = n.quality_degradation(0.9, [0.8, 0.7]) | |
| assert 0.0 <= q <= 1.0 | |
| class TestOutputGenerator: | |
| def test_collect_sample(self): | |
| noise = NoiseModel(seed=1) | |
| gen = OutputGenerator(noise) | |
| s = _make_state() | |
| action = ExperimentAction( | |
| action_type=ActionType.COLLECT_SAMPLE, | |
| parameters={"n_samples": 4}, | |
| ) | |
| out = gen.generate(action, s, 1) | |
| assert out.output_type == OutputType.SAMPLE_COLLECTION_RESULT | |
| assert out.data["n_samples"] == 4 | |
| def test_de_includes_true_genes(self): | |
| noise = NoiseModel(seed=42) | |
| gen = OutputGenerator(noise) | |
| s = _make_state() | |
| s.progress.data_normalized = True | |
| action = ExperimentAction( | |
| action_type=ActionType.DIFFERENTIAL_EXPRESSION, | |
| parameters={"comparison": "disease_vs_healthy"}, | |
| ) | |
| out = gen.generate(action, s, 5) | |
| assert out.output_type == OutputType.DE_RESULT | |
| gene_names = [g["gene"] for g in out.data["top_genes"]] | |
| assert "G1" in gene_names or "G2" in gene_names | |
| class TestTransitionEngine: | |
| def test_progress_flags_set(self): | |
| noise = NoiseModel(seed=0) | |
| engine = TransitionEngine(noise) | |
| s = _make_state() | |
| action = ExperimentAction(action_type=ActionType.COLLECT_SAMPLE) | |
| result = engine.step(s, action) | |
| assert result.next_state.progress.samples_collected is True | |
| def test_hard_violation_blocks(self): | |
| noise = NoiseModel(seed=0) | |
| engine = TransitionEngine(noise) | |
| s = _make_state() | |
| result = engine.step( | |
| s, | |
| ExperimentAction(action_type=ActionType.COLLECT_SAMPLE), | |
| hard_violations=["test_block"], | |
| ) | |
| assert result.output.success is False | |
| assert result.output.output_type == OutputType.FAILURE_REPORT | |
| def test_resource_deduction(self): | |
| noise = NoiseModel(seed=0) | |
| engine = TransitionEngine(noise) | |
| s = _make_state() | |
| action = ExperimentAction(action_type=ActionType.SEQUENCE_CELLS) | |
| s.progress.library_prepared = True | |
| result = engine.step(s, action) | |
| assert result.next_state.resources.budget_used == 15_000 | |
| def test_conclusion_ends_episode(self): | |
| noise = NoiseModel(seed=0) | |
| engine = TransitionEngine(noise) | |
| s = _make_state() | |
| s.progress.de_performed = True | |
| action = ExperimentAction(action_type=ActionType.SYNTHESIZE_CONCLUSION) | |
| result = engine.step(s, action) | |
| assert result.done is True | |