From 962bca9256e4cd7e2d3a85874d68485abde7c853 Mon Sep 17 00:00:00 2001 From: Dima <33123184+DimaMolod@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:48:06 +0100 Subject: [PATCH] Handle duplicate AF3 residue IDs during inference --- alphafold3 | 2 +- test/check_alphafold3_predictions.py | 33 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/alphafold3 b/alphafold3 index 8420ad39..6ad1a659 160000 --- a/alphafold3 +++ b/alphafold3 @@ -1 +1 @@ -Subproject commit 8420ad39e9988ca02178294cd3f7b7420b88cabf +Subproject commit 6ad1a65994c2111d291a386cdc048d8c9bfae4af diff --git a/test/check_alphafold3_predictions.py b/test/check_alphafold3_predictions.py index f93c2559..14f09cdb 100755 --- a/test/check_alphafold3_predictions.py +++ b/test/check_alphafold3_predictions.py @@ -1262,6 +1262,39 @@ class TestAlphaFold3RunModes(_TestBase): self.assertEqual(relative_encoding[3, 4, inter_chain_bin], 0) self.assertEqual(np.argmax(relative_encoding[3, 4, : 2 * 4 + 2]), 0) + def test_af3_duplicate_residue_ids_survive_empty_structure_round_trip(self): + """AF3 must preserve duplicate residue IDs when rebuilding empty structures.""" + from alphafold3.common import folding_input + from alphafold3.constants import chemical_components + from alphafold3.model.atom_layout import atom_layout + + expected_residue_ids = list(range(1, 11)) + list(range(2, 6)) + list(range(12, 16)) + chain = folding_input.ProteinChain( + id="A", + sequence="ACDEFGHIKLCDEFMNPQ", + ptms=[], + residue_ids=expected_residue_ids, + unpaired_msa="", + paired_msa="", + templates=[], + ) + fold_input = folding_input.Input( + name="duplicate_residue_ids_test", + chains=[chain], + rng_seeds=[1], + ) + ccd = chemical_components.Ccd() + struc = fold_input.to_structure(ccd=ccd) + flat_layout = atom_layout.atom_layout_from_structure(struc) + rebuilt = atom_layout.make_structure( + flat_layout, + atom_coords=np.zeros((flat_layout.atom_name.shape[0], 3), dtype=np.float32), + name="duplicate_residue_ids_test", + all_physical_residues=struc.present_residues, + ) + + self.assertEqual(rebuilt.present_residues.id.tolist(), expected_residue_ids) + def test_af3_keeps_discontinuous_chopped_regions_in_one_gapped_chain(self): """AF3 must keep multi-region chopped inputs as one gapped protein chain.""" from alphapulldown.folding_backend.alphafold3_backend import (