Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/logic_network_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,14 @@ def _map_annotated_entity_to_nodes(entity_id: str, member_set: Set[str]) -> Set[
if "Complex" in labels:
if not _complex_contains_entity_set(entity_id):
return {str(entity_id)} # simple complex → single bundled node
# Strict complex-as-node (LNG_COMPLEX_AS_NODE): a Complex is ONE node =
# its plain stId even when it contains an internal EntitySet. This makes
# the produced-occurrence node id match the catalyst/regulator node id
# (all plain stId), so a complex produced by one reaction and
# catalysing/regulating another unifies instead of siloing into
# disconnected ::variant:: nodes. See plan luminous-percolating-llama.
if os.environ.get("LNG_COMPLEX_AS_NODE", "1") != "0":
return {str(entity_id)}
# Set-variant node: OUTERMOST complex stId + a FLAT sorted list of the
# variant's FULL terminal membership. We enumerate the complex's true
# variants (each with complete membership) and pick the one this VR
Expand Down Expand Up @@ -1480,9 +1488,14 @@ def append_regulators(
# happen to reconnect what the silo severs. Until the silo × set-variant
# entity-identity problem is redesigned, subunit decomposition stays the
# default. See memory project_uuid_silo_bug / the catalyst-handling notes.
# Strict complex-as-node: bundle EVERY regulator complex (catalyst, pos,
# neg) to its plain stId so it matches the produced/consumed occurrence's
# node id and unifies instead of siloing. Subsumes the older opt-in
# LNG_CATALYST_BUNDLE (pos-only).
complex_as_node = os.environ.get("LNG_COMPLEX_AS_NODE", "1") != "0"
bundle_on = os.environ.get("LNG_CATALYST_BUNDLE", "0") == "1"
variant_decomposition = (pos_neg == "neg")
bundle_complex = (pos_neg == "pos") and bundle_on
variant_decomposition = (pos_neg == "neg") and not complex_as_node
bundle_complex = complex_as_node or ((pos_neg == "pos") and bundle_on)

for _, row in map_df.iterrows():
entity_id = str(row["entity_id"])
Expand Down
27 changes: 27 additions & 0 deletions tests/test_logic_network_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,3 +743,30 @@ def test_complex_with_internal_set_decomposes(self, monkeypatch):
assert result == frozenset({"A", "B", "P"}), (
f"complex-with-set must decompose to members, got {set(result)}"
)


class TestComplexAsNode:
"""LNG_COMPLEX_AS_NODE (default on): a Complex is ONE node = its plain stId
even when it contains an internal EntitySet, so the produced-complex node id
matches the catalyst/regulator node id and they unify (fixes the UUID silo
that severed produce->catalyze chains, e.g. ATM->p53:FAS-gene complex->FAS)."""

def test_complex_with_set_emits_plain_stid(self, monkeypatch):
import src.logic_network_generator as m
from src import neo4j_connector as nc
monkeypatch.setattr(nc, "get_labels", lambda e: ["Complex"] if e == "C" else ["EntitySet"])
monkeypatch.setattr(m, "_complex_contains_entity_set", lambda e: True)
# default-on: complex-with-set -> plain stId, never a ::variant:: node
result = m._map_annotated_entity_to_nodes("C", {"C", "A", "B"})
assert result == {"C"}, f"complex-as-node must emit plain stId, got {result}"

def test_flag_off_restores_variant_behavior(self, monkeypatch):
import src.logic_network_generator as m
from src import neo4j_connector as nc
monkeypatch.setenv("LNG_COMPLEX_AS_NODE", "0")
monkeypatch.setattr(nc, "get_labels", lambda e: ["Complex"])
monkeypatch.setattr(m, "_complex_contains_entity_set", lambda e: True)
monkeypatch.setattr(m, "_complex_variant_leafsets", lambda e: [frozenset({"A", "B"})])
m._variant_capped.discard("C")
result = m._map_annotated_entity_to_nodes("C", {"A", "B"})
assert any("::variant::" in r for r in result), f"flag off must variant-split, got {result}"
Loading