Strict complex-as-node: fix the catalyst/regulator UUID silo#47
Merged
Conversation
A biological complex that is PRODUCED by one reaction and CATALYSES/REGULATES another was stored as separate, un-unified UUID nodes, severing the curated "complex produced -> then it catalyses/regulates" chain (traced: ATM -> active p53 -> p53:FAS-gene complex R-HSA-6799810 -> regulates FAS expression -> FAS was broken; ATM reached ~2000 nodes but not FAS). Two root causes, both specific to complexes-that-contain-an-internal-EntitySet: (1) the Phase-2 union-find only merges output@A<->input@B, never catalyst/regulator roles; (2) a complex-with-set got three DIFFERENT node-id encodings across produced / bundled-catalyst / negative-regulator paths, so the strings never matched to unify. Fix (LNG_COMPLEX_AS_NODE, default on): a Complex is ONE node = its plain stId regardless of internal EntitySets, in both emission encoders (_map_annotated_entity_to_nodes and _decompose_regulator_entity for all three regulator roles). All roles now agree on the plain stId, so produced and regulator occurrences unify. Purely an EMISSION change — the matching layer (reaction_generator cartesian) is untouched. Bare EntitySets still expand to OR members. The ::variant:: machinery is left in place, reachable via LNG_COMPLEX_AS_NODE=0 for A/B. Members stay perturbable via their production reactions (a family member is an input to the binding reaction that makes the complex), so no distinction is lost where it is a real reaction participant. The connected regulators then combine via the existing multiplicative/hill_log activator aggregation. Verified vs Neo4j: R-HSA-6799810 collapses 3 nodes -> 1; ATM/BRCA1/CHEK2/MDM2 all reach FAS. Benchmarks (tuned cutoffs, DS_KO_REMAP): experimental 66.43% -> 69.85% (RAF +36, WNT +20, Mitotic_Prophase +15, S_Phase +8, ERBB2 +2, nothing regressed); curator 81.18% -> 83.82%. Best result of the series, zero regressions. TestComplexAsNode added; suite green, ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
A complex that is produced by one reaction and catalyses/regulates another was stored as separate un-unified UUID nodes, severing the curated "complex produced → then catalyses/regulates" chain. Traced concretely: ATM → active p53 → p53:FAS-gene complex (R-HSA-6799810) → regulates FAS expression → FAS was broken (ATM reached ~2000 nodes but not FAS).
Two root causes, both specific to complexes-that-contain-an-internal-EntitySet in the produce→catalyse topology (simple complexes already unify via plain stId):
output@A ↔ input@B; catalyst/regulator roles are never registered/unified.::variant::{terminal leaves}, bundled-catalyst = plain stId, negative-regulator =::variant::{direct components}) — so the strings never matched to unify.The fix
LNG_COMPLEX_AS_NODE(default on): a Complex is one node = its plain stId regardless of internal EntitySets, in both emission encoders. All roles now agree on the plain stId, so produced and regulator occurrences unify. Purely an emission change — the matching-layer cartesian is untouched. Bare EntitySets still expand to OR members. The::variant::machinery stays reachable viaLNG_COMPLEX_AS_NODE=0for A/B.Members remain perturbable through their production reactions, so no real distinction is lost. Connected regulators then combine via the existing multiplicative/
hill_logactivator aggregation anddivide/devspec inhibition.Verified
TestComplexAsNodeadded; full non-DB suite green, ruff + mypy clean, coverage 46.5%.🤖 Generated with Claude Code