@@ -673,28 +673,36 @@ def _append_ligand(self, atom_array, atom_array_input_annotated):
673673 + list (atom_array_input_annotated .get_annotation_categories ())
674674 ),
675675 )
676- # Error if any ligand shares a chain ID with the already-built
677- # atom array — chain ID is leaked to the model so collisions
678- # represent a significant deviation from the expected convention.
676+ # Validate chain assignments — chain ID is leaked to the model
677+ # so collisions are a significant deviation from convention.
679678 ligand_chains = np .unique (ligand_array .chain_id )
680679 existing_chains = set (np .unique (atom_array .chain_id ))
681680 overlapping = sorted (existing_chains & set (ligand_chains ))
682- if overlapping and not self .allow_ligand_on_existing_chain :
683- raise ValueError (
684- f"Ligand chain(s) { overlapping } overlap with existing "
685- f"chain(s) { sorted (existing_chains )} . Place ligands on "
686- f"separate chains or set 'allow_ligand_on_existing_chain: "
687- f"true' to override this check."
688- )
689- # Reset ligand res_id to start from 1 per chain, matching the
690- # convention AF3 uses in its output CIF files. Use dense
691- # rank-based renumbering so gaps in the original numbering
692- # (e.g. res_id 850, 900) become sequential (1, 2).
681+ if not self .allow_ligand_on_existing_chain :
682+ if overlapping :
683+ raise ValueError (
684+ f"Ligand chain(s) { overlapping } overlap with existing "
685+ f"chain(s) { sorted (existing_chains )} . Place ligands on "
686+ f"separate chains or set 'allow_ligand_on_existing_chain: "
687+ f"true' to override this check."
688+ )
689+ # Multiple ligands must each be on their own chain.
690+ for chain in ligand_chains :
691+ n_residues = len (
692+ np .unique (ligand_array .res_id [ligand_array .chain_id == chain ])
693+ )
694+ if n_residues > 1 :
695+ raise ValueError (
696+ f"Multiple ligand residues on chain { chain } . Each "
697+ f"ligand must be on its own chain, or set "
698+ f"'allow_ligand_on_existing_chain: true' to override."
699+ )
700+ # Reset ligand res_id to start from 1 per chain. When ligands
701+ # share a chain (override mode), preserve relative gaps.
693702 for chain in ligand_chains :
694703 mask = ligand_array .chain_id == chain
695704 chain_res_ids = ligand_array .res_id [mask ]
696- _ , inverse = np .unique (chain_res_ids , return_inverse = True )
697- ligand_array .res_id [mask ] = inverse + 1
705+ ligand_array .res_id [mask ] = chain_res_ids - np .min (chain_res_ids ) + 1
698706 # Harmonize conditioning annotations before concatenation: biotite's
699707 # concatenate only preserves annotations present in ALL arrays (set
700708 # intersection), so mismatched optional conditioning annotations
0 commit comments