Set 1:{" "}
average(g05347, g00048, g06271) → scoreA
>,
<>
Set 2: average(g15522, g02013) → scoreB
>,
<>
Combine: scoreA − scoreB → final score
>,
]}
/>
-
to capture a contrast — e.g. one group that’s low in
MSI-H and one that’s high; the gap between them can separate
better than either alone (like “repair activity minus immune
activity”). One set can’t express that; two can. If one
group is enough, the engine can still use 1.
Two short chains feed a Combine(sub) node and produce a final score.
>
),
},
lambda: {
title: "λ (size penalty)",
short:
"How hard the engine is penalised for using more genes. Higher pushes it toward fewer genes (simpler answers).",
detailed: (
<>
-
a “price per gene” that discourages bloated programs.
Every program is graded on a single number:
fitness = separation − λ × (number of genes)
so the engine ranks by accuracy minus a size tax.
-
λ is the admission price each gene must beat: with λ = 0.005, a gene
is only worth keeping if it adds more than 0.005 of separation.
-
a 3-gene program scores 0.90 → net 0.90 − 0.015 = 0.885. Add a 4th
gene that lifts it to 0.903 (only +0.003) → net 0.883, lower,
so it’s rejected. A 4th gene that lifts it to 0.91 (+0.01) →
net 0.890, higher, so it’s kept.
-
λ = 0 → genes free → bloated, overfit programs. λ small (0.005) →
trims useless genes, keeps useful ones. λ large → very lean programs,
but may drop useful genes.
Raw separation rises and plateaus; net fitness peaks at an
intermediate size, and a larger λ shifts the peak to fewer genes.
>
),
},
seed: {
title: "Seed",
short:
"The starting point for the engine's randomness. The same seed reproduces the exact same run; change it to see a different run.",
detailed: (
<>
-
the space of possible programs is astronomically large (picking even
8 genes out of 20,000 is ~10²⁹ combinations), so the engine
can’t try them all — it explores with randomness: random
starting programs, random mutations, random breeding.
-
computers don’t make true randomness; a formula generates a
sequence of numbers, each from the previous one. The seed is the{" "}
starting number fed into that formula — everything random
flows from it. (One common formula:{" "}
next = (1664525 × current + 1013904223) mod 2³²; with
seed 42 the first value is 1,083,814,273, then mapped onto a gene
position.)
-
think of the seed as one fixed list of dice rolls used in order
across the whole run — early rolls pick the starting genes, later
rolls drive mutations. Same seed → same list → identical run.
Different seed → a different list → a different run.
-
because the search has luck in it, run a few seeds (42, 7, 123) and
see whether the same genes keep appearing — that tells you a finding
is real, not a fluke.
The seed is one fixed list of dice rolls; early rolls pick initial
genes, later rolls drive mutations.
>
),
},
permutations: {
title: "Permutations",
short:
"How many times we re-run on deliberately scrambled labels to check the result isn't luck. The real result must beat these chance runs (that's the 'permutation p').",
detailed: (
<>
-
whether the winning score is real signal or could have come up by
luck. The engine searches so hard that some programs separate the
groups well by pure coincidence, so a high score alone isn’t
proof.
-
shuffle the MSI-H / MSS labels across patients at random — this
breaks any real gene↔label link, creating a “no-signal
world”. Re-score there: any score above 0.5 is pure chance.
Repeat many times (this knob = how many) to build a picture of what
luck looks like.
-
permutation p = the fraction of shuffled runs that scored ≥ the real
winner. p = 0.005 means only 0.5% of chance runs matched it → very
unlikely a fluke. Conventionally p < 0.05 is the “unlikely
to be luck” line.
-
with 200 shuffles the smallest p you can report is about 1 / 200 ≈
0.005; more permutations give a finer, more trustworthy p.
200 shuffled-label runs cluster near 0.58; the real result at 0.89
sits past every one of them, giving p ≈ 0.005.
>
),
},
prefilter_n: {
title: "Prefilter top-N",
short:
"By default the engine searches all ~20,000 genes, so nothing is pre-excluded. Turning this on narrows to the N most promising genes first — faster, but it can drop a real gene that only shows signal in combination.",
detailed: (
<>
-
the engine searches all ~20,000 genes, so nothing is pre-excluded —
the most honest setting for a discovery demo.
-
first narrows to the N genes most individually associated with the
target (computed name-blind, on the training split only), then
searches within that shortlist. Faster, because the space is ~10×
smaller.
-
speed and focus vs completeness. A univariate shortlist can drop a
gene that only matters in combination (no signal on its
own), and it sets a ceiling — if a gene isn’t in the
shortlist, the engine can never find it.
~20,000 genes → keep the N most individually associated → the GP
searches within N. Off by default = no funnel.
>
),
},
// ---------- Objective explainers ----------
obj_msi: {
title: "MSI separation",
short:
"Reward programs whose score separates MSI-H from MSS (measured by AUROC). It rewards ANY separator, so it often finds shortcut genes, not the cause.",
detailed: (
<>
-
a score that ranks MSI-H patients above MSS — i.e. tells the two
subtypes apart.
-
pick one random MSI-H and one random MSS patient; AUROC is the
chance the score puts the MSI-H one higher. 0.5 = coin-flip (no
separation), 1.0 = perfect. We use AUROC, not plain accuracy,
because MSI-H is only ~15% of patients — “always guess
MSS” would look 85% accurate while separating nothing.
-
{`{ target: msi, metric: AUROC }`}
{" "}— the engine sees only the score + the MSI label, never
gene names.
-
it rewards ANY separator. MSI-H and MSS differ in thousands of
genes, so it usually grabs easy “shortcut” genes
(consequences or coincidences), not the causal MMR genes.
>
),
},
obj_tmb: {
title: "Mutation burden",
short:
"Reward programs whose score is negatively associated with mutation burden (low score ↔ high TMB) — the broken spell-checker's fingerprint. A sharper proxy for the cause, but still association, not proof.",
detailed: (
<>
-
a score that goes DOWN as mutation count goes UP — the
fingerprint of a broken repair gene (switch it off → mutations
pile up). The repair genes are the MMR set: MLH1, MSH2, MSH6,
PMS2.
-
{`{ target: tmb, metric: correlation, direction: negative }`}
{" "}— the engine sees only the score + the TMB numbers.
-
we reward the score being NEGATIVELY correlated with TMB.
Rewarding any correlation would also pick genes that RISE with
mutations — the opposite of the repair signature.
-
this is a mechanism-shaped association — a much better proxy
for the cause than predicting the label, but it doesn’t
prove causation. Genes silenced alongside the repair genes can
mimic the same low-expression↔high-TMB pattern.
-
the program can choose to ADJUST for confounders (age, stage)
via the Effect operator — the most causal move the
observational data honestly allows. The engine decides whether
it helps; we don’t hardcode it.
>
),
},
obj_hpv: {
title: "HPV detection",
short:
"Find a gene-expression pattern that tells HPV+ tumours apart from HPV−. Scored by AUROC (0.5 = coin-flip, 1.0 = perfect).",
detailed: (
<>
HPV detection. Head &
neck cancers split into two kinds: those caused by the HPV
virus (HPV+) and those that aren’t (HPV−). The engine
looks for a gene-expression pattern that tells the two apart.
-
pick one random HPV+ and one random HPV− patient; AUROC is
the chance the engine’s score puts the HPV+ one higher.
0.5 is a coin-flip, 1.0 is perfect. Because only ~15% of
tumours are HPV+, we use AUROC rather than plain accuracy —
“always guess HPV−” would look 85% accurate while
separating nothing.
-
only the score its program produces and the HPV+/HPV− label —
never gene names. That’s what makes recovering the
known biology afterwards a real rediscovery, not a lookup.
-
this is detecting a known viral fingerprint (the
virus switches specific genes on), not discovering a new
cause. The cause is the virus itself. A high AUROC means the
engine recognised the fingerprint blind.
>
),
},
obj_unsupervised: {
title: "Unsupervised",
short:
"No target column at all. The engine searches for a program whose score splits patients into two clean groups; afterwards we check what that split lines up with.",
detailed: (
<>
-
Unlike the other objectives, this one is given NO target — only
the gene numbers. The search never sees MSI, TMB, or any label.
-
a score that divides patients into two as-cleanly-separated-as-
possible groups (measured by cluster separation). It is NOT told
what the groups should be.
-
it always produces a two-group split, but it doesn’t aim
at MSS/dMMR — or anything. It finds whatever the strongest
natural division in the data is.
-
after the run we check what the discovered split lines up with
— MSI? TMB? — the “post-hoc alignment” in the
Result panel.
-
if the strongest natural split turns out to BE the MSS-vs-dMMR
divide, it aligns with MSI at high AUROC → the engine
rediscovered the subtype without ever being told it exists.
-
it may instead land on a different dominant axis (e.g. immune
hot vs cold) that only partly overlaps MSI. MSI-H is only ~15%
of patients, so a clean recovery isn’t guaranteed — the
result is HOW MUCH the blind split overlaps MSS/dMMR.
>
),
},
module_survival: {
title: "What “Survives” checks",
short:
"Whether a group still separates the label when you take away a possible confounder — something that travels with the label but isn't its biology.",
detailed: (
<>
-
whether a group still separates HPV when you take away a possible
confounder — something that travels with HPV but
isn’t HPV biology. If the group’s AUROC mostly came
from the confounder, it collapses when the confounder is held
constant; if the signal is real, it holds.
-
HPV+ tumours are mostly in the oropharynx (back of the throat),
so a gene could look like an “HPV gene” just by
marking that location. The site check re-scores the group using
ONLY oropharynx patients — everyone the same location. The two
numbers in the chip read full-cohort → oropharynx-only.
-
a tumour sample is a mix of cancer cells and immune cells; HPV+
tumours carry more immune cells, so a gene could look like an
“HPV gene” just by marking immune content. The
purity check re-scores using only the “purest”
(least-immune) tumours. It’s usually — here
because those tumours include almost no HPV+ patients, so
there’s nothing to test.
-
✓
{" "}
= held up when the confounder was held constant (likely real
signal).{" "}
✗
{" "}
= dropped past the tolerance (part of it was the confounder).{" "}
— = couldn’t test (too few patients per
class in the subgroup).
Tolerance is 0.05 AUROC: a subgroup AUROC within 0.05 of the
full-cohort AUROC counts as “survives.” HNSC/HPV
coherence-on runs only — other (dataset, target) pairs omit
this column.
>
),
},
};
function ObjectiveIntro() {
return (
An objective is the rule that scores every program — the fitness the
engine maximises. It’s computed only from the program’s
per-patient output (and, for the supervised objectives, a target
column — MSI, TMB, or HPV — never gene names). It sets what
“good” means; the engine then composes DSL programs to
satisfy it.
);
}
function ObjectiveFooter() {
return (
What the program chooses is how to build the score and how to
compare it — a raw association (Associate) or a confounder-adjusted
one (Effect), plus the correlation kind. What stays outside the DSL
is the compass: the target it’s scored against, in which
direction, judged honestly on held-out data. The program can’t
pick the target — that would let the answer into the language.
);
}
// Unsupervised has no target / no Associate / no Effect — its footer
// is the deeper invariant: the program can't see a target to optimise
// against, which is exactly what makes a downstream label-alignment
// meaningful.
function UnsupObjectiveFooter() {
return (
The only thing outside the DSL here is “find the cleanest
split, judged honestly on held-out data” — there is no target,
and the program can’t see one. That’s exactly what makes
it meaningful when the split turns out to line up with a known
label (MSI on the colorectal cohort, HPV on head & neck): the
engine wasn’t told to look for it.
);
}