Skip to content

fix: filter GCFs left MIBiG-only after BGC attachment (#351) - #352

Open
vinvomero wants to merge 1 commit into
NPLinker:devfrom
vinvomero:fix/mibig-only-gcf-links
Open

fix: filter GCFs left MIBiG-only after BGC attachment (#351)#352
vinvomero wants to merge 1 commit into
NPLinker:devfrom
vinvomero:fix/mibig-only-gcf-links

Conversation

@vinvomero

Copy link
Copy Markdown

Summary

Fixes #351. Gene-cluster families (GCFs) composed solely of MIBiG reference BGCs produce spurious Metcalf links to metabolite ions. The existing MIBiG filter runs at load time on the raw bgc_ids (via GCF.has_mibig_only()) before BGC objects are attached, so a GCF that loaded with a mix of antiSMASH + MIBiG ids but whose antiSMASH BGC was not loaded is left with only its MIBiG BGC attached — while bgc_ids still lists the missing id, so the id-based check can't catch it. As @justinjjvanderhooft noted in #351, such GCFs should be filtered out.

What this does

  • Adds GCF.has_mibig_only_bgcs() — like has_mibig_only() but inspecting the attached bgcs objects (via BGC.is_mibig()), with an empty-set guard.
  • Adds drop_mibig_only_gcfs() in genomics/utils.py (alongside add_bgc_to_gcf/get_mibig_from_gcf): removes such GCFs and detach_bgc()s their BGCs, leaving no stale BGC.parents back-reference.
  • DatasetLoader._load_genomics applies it to self.gcfs after get_mibig_from_gcf (when MIBiG is in use), so self.bgcs / self.mibig_strains_in_use are unchanged — only the scoring-facing GCF list is filtered.

Tests

  • test_has_mibig_only_bgcs (empty / all-MIBiG / mixed).
  • test_drop_mibig_only_gcfs — exercises the drop + detach directly, including a MIBiG BGC shared between a dropped and a kept GCF (the survivor keeps it; the back-reference points only to the survivor) and a no-op case.
  • A seam regression via real add_bgc_to_gcf showing the old id-based check misses the leak while the new one catches it.

The fix is a no-op on datasets where every antiSMASH BGC has a strain — verified: tests/integration/test_nplinker_local.py counts (390/64/46) are unchanged. It only filters the pathological attachment-loss case.

Notes for reviewers

  • Root cause (not fixed here): the antiSMASH BGC ends up unattached because it lost its strain match in add_strain_to_bgc (the unmatched list is discarded, only an aggregate count logged). This PR filters the symptom the issue asks about; a follow-up could surface the strain-match loss (e.g. a WARNING naming the dropped BGC ids). Happy to do that separately.
  • MIBiG detection uses the existing id.startswith("BGC") heuristic (BGC.is_mibig, already used repo-wide and documented as false-positive-prone). This PR escalates its consequence from mislabeling a BGC to dropping a GCF, so a real antiSMASH BGC whose id happens to start with BGC could have its GCF dropped — rare, but a robust MIBiG provenance flag would be the durable fix.
  • Trade-off on self.bgcs: a dropped GCF's MIBiG BGC stays in self.bgcs with an emptied .parents (so self.bgcs is identical to before this change). Harmless for scoring (the presence matrix is built from self.gcfs), but an exported BGC can then reference no surviving GCF. I kept self.bgcs untouched deliberately — happy to recompute it from the surviving GCFs instead if you prefer.
  • The new public method isn't yet reflected in docs/figure/data_loading_pipeline.svg; can add it if useful.

🤖 Generated with Claude Code

GCFs composed solely of MIBiG reference BGCs generate spurious Metcalf links to
metabolite ions. The load-time MIBiG filter runs on raw `bgc_ids` before BGC
objects are attached, so a GCF that loaded with a mix of antiSMASH and MIBiG ids
but whose antiSMASH BGC was not loaded (e.g. no strain) is left with only its
MIBiG BGC attached while `bgc_ids` still lists the missing id -- the id-based
`has_mibig_only` cannot catch it.

Add `GCF.has_mibig_only_bgcs()` (inspects the attached BGC objects) and a
`drop_mibig_only_gcfs()` helper in genomics/utils.py that removes such GCFs and
detaches their BGCs (leaving no stale `BGC.parents`). The loader applies it to
`self.gcfs` after `get_mibig_from_gcf` (when MIBiG is in use), so
`self.bgcs` / `self.mibig_strains_in_use` are unaffected -- only the
scoring-facing GCF list changes.

The fix is a no-op on datasets where every antiSMASH BGC has a strain (verified:
tests/integration/test_nplinker_local.py counts unchanged); it only filters the
pathological attachment-loss case, covered by unit tests including the shared-BGC
invariant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

NPLinker output gives links between MIBiG-only GCFs and metabolite ions

1 participant