| """Tests for DeepPTR synthetic data generation and metrics.""" |
|
|
| import numpy as np |
| import pytest |
|
|
| from scptr.deep.synthetic import generate_kinetic_data, gamma_recovery, ci_coverage, latent_recovery |
|
|
|
|
| class TestGenerateKineticData: |
| def test_shapes(self): |
| adata, truth = generate_kinetic_data(n_cells=100, n_genes=30, seed=0) |
| assert adata.n_obs == 100 |
| assert adata.n_vars == 30 |
| assert adata.layers["spliced"].shape == (100, 30) |
| assert adata.layers["unspliced"].shape == (100, 30) |
|
|
| def test_truth_keys(self): |
| _, truth = generate_kinetic_data(n_cells=50, n_genes=20) |
| for key in ("alpha", "gamma", "beta", "z_T", "z_PT"): |
| assert key in truth |
|
|
| def test_truth_shapes(self): |
| adata, truth = generate_kinetic_data(n_cells=50, n_genes=20) |
| assert truth["alpha"].shape == (50, 20) |
| assert truth["gamma"].shape == (50, 20) |
| assert truth["beta"].shape == (20,) |
| assert truth["z_T"].shape[0] == 50 |
| assert truth["z_PT"].shape[0] == 50 |
|
|
| def test_non_negative_counts(self): |
| adata, _ = generate_kinetic_data(n_cells=200, n_genes=50) |
| assert (adata.layers["spliced"] >= 0).all() |
| assert (adata.layers["unspliced"] >= 0).all() |
|
|
| def test_cell_types(self): |
| adata, _ = generate_kinetic_data(n_cells=100, n_cell_types=4) |
| assert "cell_type" in adata.obs.columns |
| assert adata.obs["cell_type"].nunique() <= 4 |
|
|
| def test_sparsity(self): |
| adata_sparse, _ = generate_kinetic_data(n_cells=500, n_genes=100, sparsity=0.5) |
| adata_dense, _ = generate_kinetic_data(n_cells=500, n_genes=100, sparsity=0.0) |
| frac_zero_sparse = (adata_sparse.layers["spliced"] == 0).mean() |
| frac_zero_dense = (adata_dense.layers["spliced"] == 0).mean() |
| assert frac_zero_sparse > frac_zero_dense |
|
|
| def test_reproducible(self): |
| adata1, t1 = generate_kinetic_data(seed=42) |
| adata2, t2 = generate_kinetic_data(seed=42) |
| np.testing.assert_array_equal( |
| adata1.layers["spliced"], adata2.layers["spliced"] |
| ) |
| np.testing.assert_array_equal(t1["gamma"], t2["gamma"]) |
|
|
|
|
| class TestGammaRecovery: |
| def test_perfect_recovery(self): |
| rng = np.random.RandomState(0) |
| gamma = rng.rand(100, 20).astype(np.float32) |
| r = gamma_recovery(gamma, gamma, per_gene=True) |
| assert r > 0.99 |
|
|
| def test_random_is_low(self): |
| rng = np.random.RandomState(0) |
| g1 = rng.rand(100, 20).astype(np.float32) |
| g2 = rng.rand(100, 20).astype(np.float32) |
| r = gamma_recovery(g1, g2, per_gene=True) |
| assert abs(r) < 0.3 |
|
|
| def test_global_mode(self): |
| rng = np.random.RandomState(0) |
| gamma = rng.rand(100, 20).astype(np.float32) |
| r = gamma_recovery(gamma, gamma, per_gene=False) |
| assert r > 0.99 |
|
|
|
|
| class TestCICoverage: |
| def test_perfect_coverage(self): |
| rng = np.random.RandomState(0) |
| gamma = rng.rand(100, 20).astype(np.float32) |
| |
| cov = ci_coverage(gamma, gamma, np.ones_like(gamma) * 100.0) |
| assert cov > 0.99 |
|
|
| def test_zero_variance_coverage(self): |
| rng = np.random.RandomState(0) |
| gamma_true = rng.rand(100, 20).astype(np.float32) |
| gamma_pred = gamma_true + 1.0 |
| |
| cov = ci_coverage(gamma_true, gamma_pred, np.ones_like(gamma_true) * 1e-10) |
| assert cov < 0.05 |
|
|
| def test_returns_fraction(self): |
| rng = np.random.RandomState(0) |
| gamma = rng.rand(50, 10).astype(np.float32) |
| cov = ci_coverage(gamma, gamma, np.ones_like(gamma) * 0.1) |
| assert 0.0 <= cov <= 1.0 |
|
|
|
|
| class TestLatentRecovery: |
| def test_perfect_recovery(self): |
| rng = np.random.RandomState(0) |
| z = rng.randn(100, 5).astype(np.float32) |
| r = latent_recovery(z, z) |
| assert r > 0.99 |
|
|
| def test_random_is_lower(self): |
| rng = np.random.RandomState(0) |
| z1 = rng.randn(100, 5).astype(np.float32) |
| z2 = rng.randn(100, 5).astype(np.float32) |
| r = latent_recovery(z1, z2) |
| assert r < 0.5 |
|
|