Domain: Biology — Genetics

Problem:
Question (single numeric answer): using the Dirichlet‑multinomial (Pólya) likelihood and the exact parameters below, compute and print the single numeric maximum‑likelihood estimate of \(\omega\) \(\left(\frac{dN}{dS}\right)\) for \(\textit{TP53}\) (\(\texttt{ENST00000269305}\)) in a high‑grade serous ovarian carcinoma (\(\textit{HGSOC}\)) cohort.

You must write a Python program whose only top‑level function is:
def solve():

All helper logic must be nested inside solve() (use lambdas or inline constructs only). Use only \(\texttt{numpy}\) as an import. Use a Genetic Algorithm (\(\textit{GA}\)) to maximize a specified log‑likelihood over \(\omega\) \(\left(\frac{dN}{dS}\right)\) within bounds \([0.001, 100.0]\) using the exact GA settings given below. The program must print exactly one line: the maximum likelihood estimate of \(\omega\) rounded to \(3\) decimal places.

Provided parameters (necessary and sufficient):

Transcript and counts: Gene/transcript: \(\textit{TP53}\), \(\texttt{ENST00000269305}\); CDS length \(= 1182\) bp (codons \(= 394\)). (Provenance only.)

Observed coding single‑nucleotide variants in cohort: nonsynonymous \(N = 182\); synonymous \(S = 9\); total \(M = N + S\).

Codon‑ and context‑resolved target sizes (precomputed under an HGSOC‑relevant trinucleotide spectrum and the standard genetic code; totals scaled to the transcript): \(T_{N} = 172.35\); \(T_{S} = 62.65\).

Context mutation weights used to compute per‑codon opportunities (given for provenance; not required to rederive): \(w_{\text{CpG}} = 0.25\); \(w_{\text{APOBEC\_TpC}} = 0.28\); \(w_{\text{Replicated\_GC}} = 0.18\); \(w_{\text{Other}} = 0.29\). These weights were used to compute the above \(T_{N}\) and \(T_{S}\); you must use \(T_{N}\) and \(T_{S}\) as given.

Overdispersion parameter (Pólya/Dirichlet–multinomial style): \(\phi = 0.014\).

Likelihood model (fully specified; implement exactly): for parameter \(\omega > 0\) define base probabilities \(p_{N}(\omega) = \dfrac{\omega \cdot T_{N}}{\omega \cdot T_{N} + T_{S}}\), and \(p_{S}(\omega) = \dfrac{T_{S}}{\omega \cdot T_{N} + T_{S}}\).

Concentration parameters are \(\alpha_{N} = \phi \cdot p_{N}(\omega)\), \(\alpha_{S} = \phi \cdot p_{S}(\omega)\).

The Dirichlet‑multinomial PMF for counts \((N,S)\) given total \(M\) and concentration \(\alpha = (\alpha_{N},\alpha_{S})\) is \(\text{PMF}(N,S\mid\alpha) = \dfrac{\Gamma(M+1)}{\Gamma(N+1)\,\Gamma(S+1)} \cdot \dfrac{\Gamma(\phi)}{\Gamma(M+\phi)} \cdot \dfrac{\Gamma(N+\alpha_{N})\,\Gamma(S+\alpha_{S})}{\Gamma(\alpha_{N})\,\Gamma(\alpha_{S})}\), and the log‑likelihood is the logarithm of that expression; implement numerically stable forms using log‑gamma.

GA search bounds and fixed hyperparameters (use these exact values): \(\omega\) bounds \([0.001, 100.0]\); population size \(= 180\); generations \(= 600\); tournament size \(= 5\); crossover rate \(= 0.85\); mutation rate \(= 0.25\); mutation step (Gaussian SD in log‑space) \(= 0.03\); elitism: keep best \(3\) per generation. Seed for RNG: use \(\texttt{numpy.random.default\_rng(31415926)}\).

Output requirement: print exactly one line, the \(\omega\) \(\left(\frac{dN}{dS}\right)\) estimate rounded to \(3\) decimal places (no extra output, no text).

Do not just return the Python script. Remember you must compute and return the the \(\omega\) \(\left(\frac{dN}{dS}\right)\) estimate rounded to \(3\) decimal places using your Python script as the final answer.

Answer: 0.371

Solution code:
import numpy as np

def solve():
    # Scientifically grounded task: estimate somatic dN/dS (ω) for TP53 in HGSOC using a Dirichlet–multinomial likelihood.
    # Biological rationale (comments only, no hints): dN/dS quantifies selection by comparing nonsynonymous to synonymous
    # substitutions normalized by codon- and context-resolved mutational opportunities, which is essential in cancer gene-level
    # selection inference (Martincorena et al., Cell 2017; Davies et al., Nat Genet 2023). Overdispersion (Dirichlet–multinomial)
    # accounts for cohort heterogeneity beyond simple multinomial (Lawrence et al., Nature 2013).

    # Minimal necessary inputs per task specification (TP53 ENST00000269305 provenance; counts; target sizes; overdispersion):
    N = 182           # observed nonsynonymous SNVs in HGSOC (tumor suppressor TP53 is under strong positive selection)
    S = 9             # observed synonymous SNVs (neutral baseline channel)
    M = N + S         # total observed coding SNVs
    T_N = 172.35      # codon- & context-weighted nonsynonymous target size (reflects mutational opportunities per codon)
    T_S = 62.65       # codon- & context-weighted synonymous target size (redundancy-weighted neutral opportunities)
    phi = 0.014       # Dirichlet–multinomial concentration (captures extra-multinomial variance in cancer cohorts)

    # GA hyperparameters (fixed and deterministic as required):
    POP = 180
    GENS = 600
    TOURN = 5
    CX_RATE = 0.85
    MUT_RATE = 0.25
    MUT_STEP = 0.03   # Gaussian SD in log-space for multiplicative exploration of ω
    ELITES = 3
    LO, HI = 0.001, 100.0
    rng = np.random.default_rng(31415926)  # reproducible GA run

    # Numerically stable log-gamma (gammaln) via Lanczos approximation (no external imports permitted).
    # Modeling note: Dirichlet–multinomial likelihood requires log-gamma for count and concentration terms.
    # Biological justification: Overdispersion models heterogeneity in mutation processes/signatures across tumors.
    # Constants for g>0 approximation (suitable for our positive arguments):
    lanczos_g = 7
    lanczos_p = np.array([
        0.99999999999980993,
        676.5203681218851,
        -1259.1392167224028,
        771.32342877765313,
        -176.61502916214059,
        12.507343278686905,
        -0.13857109526572012,
        9.9843695780195716e-6,
        1.5056327351493116e-7
    ])

    # Helper: log-gamma using Lanczos; valid for x>0, which holds for our inputs (N+α_N, S+α_S, α_N, α_S, φ, M+φ).
    gammaln = lambda x: (
        # Reflection not needed because all x>0 in this model; use standard Lanczos series
        (lambda z: (
            (lambda t: (
                0.5*np.log(2*np.pi) + (z+0.5)*np.log(t) - t + np.log(
                    lanczos_p[0] + np.sum(lanczos_p[1:] / (z + np.arange(1, lanczos_p.size)))
                ) - np.log(z)
            ))(z + lanczos_g + 0.5)
        ))(x)
    )

    # Base probabilities linking ω to nonsynonymous vs synonymous channels.
    # Biological note: ω scales nonsynonymous opportunities relative to synonymous, consistent with codon-target frameworks.
    pN = lambda w: (w * T_N) / (w * T_N + T_S)
    pS = lambda w: T_S / (w * T_N + T_S)

    # Dirichlet–multinomial log-likelihood for counts (N,S) given ω via α_N=φ*pN(ω), α_S=φ*pS(ω).
    # Biological note: This model inflates variance (overdispersion) to reflect tumor heterogeneity and mutational spectrum variability.
    ll = lambda w: (
        (lambda aN, aS: (
            # log PMF = log multinomial coeff + log dirichlet ratio
            # Multinomial coefficient term (constant w.r.t ω): Γ(M+1)/(Γ(N+1)Γ(S+1))
            # Dirichlet terms: Γ(φ)/Γ(M+φ) * Γ(N+aN)Γ(S+aS)/(Γ(aN)Γ(aS))
            (gammaln(M+1) - gammaln(N+1) - gammaln(S+1)) +
            (gammaln(phi) - gammaln(M + phi)) +
            (gammaln(N + aN) + gammaln(S + aS) - gammaln(aN) - gammaln(aS))
        ))(phi * pN(w), phi * pS(w))
    )

    # GA setup in log-space for ω>0 stability; multiplicative mutation captures biologically plausible scale changes in ω.
    pop = np.exp(rng.uniform(np.log(LO), np.log(HI), size=POP))
    fitness = np.array([ll(x) for x in pop])

    # Tournament selection (maintains selective pressure while avoiding premature convergence).
    select = lambda fits: (
        (lambda idxs: idxs[np.argmax([fits[i] for i in idxs])])(
            rng.choice(np.arange(POP), size=TOURN, replace=False)
        )
    )

    # Crossover combines parental ω; arithmetic + geometric forms preserve positivity and explore convex/multiplicative structure.
    crossover = lambda a, b: (
        np.clip((a + b) / 2.0, LO, HI),
        np.clip(np.sqrt(max(a, 1e-300) * max(b, 1e-300)), LO, HI)
    )

    # Log-space Gaussian mutation; appropriate for ratio parameters like ω where orders of magnitude matter biologically.
    mutate = lambda x: np.clip(np.exp(np.log(x) + rng.normal(0.0, MUT_STEP)), LO, HI)

    # GA main loop with elitism (retains top solutions, ensuring non-decreasing best fitness across generations).
    for _ in range(GENS):
        elite_idx = np.argsort(fitness)[-ELITES:]
        elites_vals = pop[elite_idx].copy()

        new_pop = []
        while len(new_pop) < POP - ELITES:
            ia = select(fitness); ib = select(fitness)
            pa, pb = pop[ia], pop[ib]

            if rng.random() < CX_RATE:
                c1, c2 = crossover(pa, pb)
            else:
                c1, c2 = pa, pb

            if rng.random() < MUT_RATE:
                c1 = mutate(c1)
            if rng.random() < MUT_RATE:
                c2 = mutate(c2)

            new_pop.extend([c1, c2])

        pop = np.concatenate([np.array(new_pop[:POP - ELITES]), elites_vals])
        fitness = np.array([ll(x) for x in pop])

    # Best ω (maximum log-likelihood under Dirichlet–multinomial model).
    omega_hat = pop[np.argmax(fitness)]

    # Output: single line, ω rounded to 3 decimals (latest specification); reproducible given fixed seed/settings.
    print(f"{omega_hat:.3f}")

if __name__ == "__main__":
    solve()