File size: 2,733 Bytes
0fff343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"""Permutation nulls for engine_v2.

- ``permutation_null`` (MSI / TMB): hold the winning program FIXED,
  shuffle the target N times, re-evaluate against each shuffled target.
- ``unsup_random_null`` (unsupervised): there's no target to shuffle, so
  the null is "random Vector-only programs scored on the same held-out
  context". p = fraction of random programs with structure ≥ winner.
"""

from __future__ import annotations

import random
from typing import Sequence

import numpy as np
import pandas as pd

from engine_v2.fitness import V2Objective, evaluate_holdout
from engine_v2.nodes import ExecContext, Node
from engine_v2.types import TType


def permutation_null(
    program: Node,
    ctx: ExecContext,
    y: np.ndarray,
    *,
    objective: V2Objective,
    n_permutations: int = 200,
    seed: int = 0,
) -> list[float]:
    rng = np.random.default_rng(seed)
    nulls: list[float] = []
    label_key = objective.target  # "msi" or "tmb"
    for _ in range(n_permutations):
        y_shuf = rng.permutation(y)
        shuf_labels = {**ctx.labels, label_key: y_shuf}
        shuf_ctx = ExecContext(M=ctx.M, clinical=ctx.clinical, labels=shuf_labels)
        nulls.append(evaluate_holdout(program, shuf_ctx, y_shuf, objective=objective))
    return nulls


def unsup_random_null(
    ctx_test: ExecContext,
    pool: Sequence[str],
    *,
    objective: V2Objective,
    n_permutations: int = 200,
    rates: dict | None = None,
    max_depth: int = 4,
    max_genes_per_set: int = 8,
    seed: int = 0,
    ctx_train: ExecContext | None = None,
) -> list[float]:
    """Random-program null for the unsupervised objective.

    Sample ``n_permutations`` Vector-only programs (no target binding),
    score each on ``ctx_test`` (with ``ctx_train`` so the silhouette is
    out-of-sample, matching how the WINNER is now scored — otherwise the
    null and the observation are not on the same scale and p is
    deflated).
    """
    from engine_v2.synthesize import random_program

    py_rng = random.Random(seed + 9001)
    nulls: list[float] = []
    for _ in range(n_permutations):
        prog = random_program(
            py_rng,
            pool,
            objective_target=objective.target,
            max_depth=max_depth,
            max_genes_per_set=max_genes_per_set,
            rates=rates,
            return_type=TType.VECTOR,
        )
        nulls.append(
            evaluate_holdout(
                prog, ctx_test, None,
                objective=objective, ctx_train=ctx_train,
            )
        )
    return nulls


def permutation_p_value(observed: float, nulls: list[float]) -> float:
    arr = np.asarray(nulls)
    return float(((arr >= observed).sum() + 1) / (len(arr) + 1))