diff --git a/src/logic_network_generator.py b/src/logic_network_generator.py index 0285445..035b1be 100755 --- a/src/logic_network_generator.py +++ b/src/logic_network_generator.py @@ -1588,6 +1588,7 @@ def create_pathway_logic_network( decomposed_uid_mapping: pd.DataFrame, reaction_connections: pd.DataFrame, best_matches: Any, + diagram_bridge_pairs: Optional[Set[Tuple[str, str]]] = None, ) -> PathwayResult: """Create a pathway logic network from decomposed UID mappings and reaction connections. @@ -1810,6 +1811,40 @@ def create_pathway_logic_network( "edge_reaction_id": reaction_stid, }) + # Phase 3b (optional): additive diagram bridges. For each diagram-drawn + # (producer, consumer) reaction pair, connect their shared entity by ADDING + # an edge from the producer's output-copy to the consumer's input-copy + # instead of MERGING the two copies (Phase 2). This preserves every node and + # edge — nothing is unified or dropped — while still giving the + # producer -> entity_out -> entity_in -> consumer path. Pairs already merged + # via precedingEvent (same canonical uuid) are skipped. Layout-filtered pairs + # only (cofactor hubs already excluded by the caller). See #39. + if diagram_bridge_pairs: + n_bridges = 0 + for a_rid, b_rid in diagram_bridge_pairs: + for p_vr in reactome_to_vr.get(a_rid, []): + p_outputs = set(vr_entities.get(p_vr, ([], [], {}, {}))[1]) + for f_vr in reactome_to_vr.get(b_rid, []): + f_inputs = set(vr_entities.get(f_vr, ([], [], {}, {}))[0]) + for eid in p_outputs & f_inputs: + src = entity_uuid_registry.get((eid, p_vr, "output")) + tgt = entity_uuid_registry.get((eid, f_vr, "input")) + # Skip if missing or already the same node (already + # connected via a precedingEvent merge). + if not src or not tgt or src == tgt: + continue + pathway_logic_network_data.append({ + "source_id": src, + "target_id": tgt, + "pos_neg": "pos", + "and_or": None, + "edge_type": "diagram_bridge", + "stoichiometry": 1, + "edge_reaction_id": None, + }) + n_bridges += 1 + logger.info(f"Diagram bridges: +{n_bridges} additive edges (no merge)") + # Log UUID registry statistics unique_uuids = set(entity_uuid_registry.values()) unique_entities = set(key[0] for key in entity_uuid_registry.keys()) diff --git a/src/pathway_generator.py b/src/pathway_generator.py index adcd1bd..6510da8 100755 --- a/src/pathway_generator.py +++ b/src/pathway_generator.py @@ -122,17 +122,34 @@ def generate_pathway_file( logger.warning(f"Could not cache decomposition results: {e}") # Continue without caching - # Augment connectivity with curator-drawn diagram flow (product->substrate - # pairs the diagram links but precedingEvent may omit — esp. old pathways). - # Only the connectivity/merge step sees this; the matching layer above - # stays on pure precedingEvent. See reactome/logic-network-generator#39. - from src.diagram_connectivity import augment_reaction_connections - connectivity = augment_reaction_connections(pathway_id, reaction_connections) + # Add curator-drawn diagram connectivity (product->substrate pairs the + # diagram links but precedingEvent may omit). See reactome/logic-network-generator#39. + # + # DEFAULT = additive-bridge mode (LNG_DIAGRAM_BRIDGE=1): the diagram-drawn + # shared-entity connections are added as NEW bridge edges (producer's + # output-copy -> consumer's input-copy) — nothing is merged or lost. Raw + # precedingEvent connectivity still merges its shared entities (the core + # mechanism), but the diagram json NEVER drives a merge. Benchmark-neutral + # vs the legacy merge (curator 83.2%, experimental 73.6%; TP53 +2) while + # preserving every node and edge. Set LNG_DIAGRAM_BRIDGE=0 to fall back to + # the legacy merge-based augmentation; set both LNG_DIAGRAM_BRIDGE=0 and + # LNG_DIAGRAM_CONNECTIVITY=0 to disable diagram connectivity entirely. + from src.diagram_connectivity import ( + augment_reaction_connections, + diagram_shared_product_pairs, + ) + diagram_bridge_pairs = None + if os.environ.get("LNG_DIAGRAM_BRIDGE", "1") == "1": + connectivity = reaction_connections + diagram_bridge_pairs = diagram_shared_product_pairs(pathway_id) + else: + connectivity = augment_reaction_connections(pathway_id, reaction_connections) # Generate logic network logger.info("Creating pathway logic network...") result = create_pathway_logic_network( - decomposed_uid_mapping, connectivity, best_matches + decomposed_uid_mapping, connectivity, best_matches, + diagram_bridge_pairs=diagram_bridge_pairs, ) # Save logic network (main output file users need)