Source code for pdb2pqr.aa

"""Amino Acid Structures for PDB2PQR

This module contains the base amino acid structures for pdb2pqr.

.. codeauthor:: Todd Dolinsky
.. codeauthor:: Nathan Baker
"""
import logging
from . import residue
from . import structures as struct
from . import utilities as util
from . import quatfit as quat


_LOGGER = logging.getLogger(__name__)


[docs]class Amino(residue.Residue): """Amino acid class This class provides standard features of the amino acids. """
[docs] def __init__(self, atoms, ref): """Initialize object. .. todo:: need to see whether :func:`super().__init__()` should be called :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ sample_atom = atoms[-1] self.atoms = [] self.name = sample_atom.res_name self.chain_id = sample_atom.chain_id self.res_seq = sample_atom.res_seq self.ins_code = sample_atom.ins_code self.ffname = self.name self.map = {} self.dihedrals = [] self.patches = [] self.peptide_c = None self.peptide_n = None self.is_n_term = 0 self.is_c_term = 0 self.is5term = 0 self.is3term = 0 self.missing = [] self.reference = ref self.fixed = 0 self.stateboolean = {} # Create each atom for atom_ in atoms: if atom_.name in ref.altnames: # Rename atoms atom_.name = ref.altnames[atom_.name] if atom_.name not in self.map: atom = struct.Atom(atom_, "ATOM", self) self.add_atom(atom) else: _LOGGER.debug(f"Ignoring atom {atom_.name}")
[docs] def create_atom(self, atomname, newcoords): """Create an atom. .. todo:: Determine why this is different than superclass method. Override the generic residue's version of create_atom(). :param atomname: name of atom :type atomname: str :param newcoords: new coordinates for atom :type newcoords: [float, float, float] """ oldatom = self.atoms[0] newatom = struct.Atom(oldatom, "ATOM", self) newatom.x = newcoords[0] newatom.y = newcoords[1] newatom.z = newcoords[2] newatom.element = atomname[0] newatom.name = atomname newatom.occupancy = 1.00 newatom.temp_factor = 0.00 newatom.added = 1 self.add_atom(newatom)
[docs] def add_atom(self, atom): """Add atom to residue. Override the existing add_atom; include the link to the reference object. :param atom: atom to add :type atom: Atom """ self.atoms.append(atom) atomname = atom.name self.map[atomname] = atom try: atom.reference = self.reference.map[atomname] for bond in atom.reference.bonds: if self.has_atom(bond): bondatom = self.map[bond] if bondatom not in atom.bonds: atom.bonds.append(bondatom) if atom not in bondatom.bonds: bondatom.bonds.append(atom) except KeyError: _LOGGER.debug(f"Skipping atom reference for {atomname}") atom.reference = None
[docs] def add_dihedral_angle(self, value): """Add a dihedral angle to the residue list. :param value: dihedral angle (in degrees) to add :type value: float """ self.dihedrals.append(value)
[docs] def set_state(self): """Set the name to use for the forcefield based on the current state. Uses ``N*`` and ``C*`` for termini. """ if self.is_n_term: if "NEUTRAL-NTERM" in self.patches: self.ffname = f"NEUTRAL-N{self.ffname}" else: self.ffname = f"N{self.ffname}" elif self.is_c_term: if "NEUTRAL-CTERM" in self.patches: self.ffname = f"NEUTRAL-C{self.ffname}" else: self.ffname = f"C{self.ffname}" return
[docs] def rebuild_tetrahedral(self, atomname): """Rebuild a tetrahedral hydrogen group. This is necessary due to the shortcomings of the quatfit routine - given a tetrahedral geometry and two existing hydrogens, the quatfit routines have two potential solutions. This function uses basic tetrahedral geometry to fix this issue. :param atomname: the atom name to add :type atomname: str :return: indication of whether this was successful :rtype: bool """ hcount = 0 nextatomname = None atomref = self.reference.map.get(atomname) if atomref is None: return False bondname = atomref.bonds[0] # Return if the bonded atom does not exist if not self.has_atom(bondname): return False # This group is tetrahedral if bondatom has 4 bonds, # 3 of which are hydrogens for bond in self.reference.map[bondname].bonds: if bond.startswith("H"): hcount += 1 elif bond != "C-1" and bond != "N+1": nextatomname = bond # Check if this is a tetrahedral group if hcount != 3 or nextatomname is None: return False # Now rebuild according to the tetrahedral geometry bondatom = self.get_atom(bondname) nextatom = self.get_atom(nextatomname) numbonds = len(bondatom.bonds) if numbonds == 1: # Place according to two atoms coords = [bondatom.coords, nextatom.coords] refcoords = [ self.reference.map[bondname].coords, self.reference.map[nextatomname].coords, ] refatomcoords = atomref.coords newcoords = quat.find_coordinates( 2, coords, refcoords, refatomcoords ) self.create_atom(atomname, newcoords) # For LEU and ILE residues only: make sure the Hydrogens are in # staggered conformation instead of eclipsed. if isinstance(self, LEU): hcoords = newcoords cbatom = self.get_atom("CB") ang = util.dihedral( cbatom.coords, nextatom.coords, bondatom.coords, hcoords ) diffangle = 60 - ang self.rotate_tetrahedral(nextatom, bondatom, diffangle) elif isinstance(self, ILE): hcoords = newcoords cg1atom = self.get_atom("CG1") cbatom = self.get_atom("CB") if bondatom.name == "CD1" or bondatom.name != "CG2": ang = util.dihedral( cbatom.coords, nextatom.coords, bondatom.coords, hcoords, ) else: ang = util.dihedral( cg1atom.coords, nextatom.coords, bondatom.coords, hcoords, ) diffangle = 60 - ang self.rotate_tetrahedral(nextatom, bondatom, diffangle) return True elif numbonds == 2: # Get the single hydrogen coordinates hatom = None for bond in bondatom.reference.bonds: if self.has_atom(bond) and bond.startswith("H"): hatom = self.get_atom(bond) break # Use the existing hydrogen and rotate about the bond self.rotate_tetrahedral(nextatom, bondatom, 120) newcoords = hatom.coords self.rotate_tetrahedral(nextatom, bondatom, -120) self.create_atom(atomname, newcoords) return True elif numbonds == 3: # Find the one spot the atom can be hatoms = [ self.get_atom(bond) for bond in bondatom.reference.bonds if self.has_atom(bond) and bond.startswith("H") ] # If this is more than two something is wrong if len(hatoms) != 2: return False # Use the existing hydrogen and rotate about the bond self.rotate_tetrahedral(nextatom, bondatom, 120) newcoords1 = hatoms[0].coords self.rotate_tetrahedral(nextatom, bondatom, 120) newcoords2 = hatoms[0].coords self.rotate_tetrahedral(nextatom, bondatom, 120) # Determine which one hatoms[1] is not in if util.distance(hatoms[1].coords, newcoords1) > 0.1: self.create_atom(atomname, newcoords1) else: self.create_atom(atomname, newcoords2) return True return False
[docs]class ALA(Amino): """Alanine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "A"
[docs]class ARG(Amino): """Arginine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "R"
[docs] def set_state(self): """Set forcefield name based on current titration state.""" if "AR0" in self.patches or self.name == "AR0": self.ffname = "AR0" Amino.set_state(self)
[docs]class ASN(Amino): """Asparagine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "N"
[docs]class ASP(Amino): """Aspartic acid class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "D"
[docs] def set_state(self): """Set forcefield name based on current titration state.""" if "ASH" in self.patches or self.name == "ASH": self.ffname = "ASH" Amino.set_state(self)
[docs]class CYS(Amino): """Cysteine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref self.ss_bonded = 0 self.ss_bonded_partner = None
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "C"
[docs] def set_state(self): """Set forcefield name based on current state. If SS-bonded, use CYX. If negatively charged, use CYM. If HG is not present, use CYX. """ if "CYX" in self.patches or self.name == "CYX": self.ffname = "CYX" elif self.ss_bonded: self.ffname = "CYX" elif "CYM" in self.patches or self.name == "CYM": self.ffname = "CYM" elif not self.has_atom("HG"): self.ffname = "CYX" Amino.set_state(self)
[docs]class GLN(Amino): """Glutamine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "Q"
[docs]class GLU(Amino): """Glutamic acid class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "E"
[docs] def set_state(self): """Set forcefield name based on current titration state.""" if "GLH" in self.patches or self.name == "GLH": self.ffname = "GLH" Amino.set_state(self)
[docs]class GLY(Amino): """Glycine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "G"
[docs]class HIS(Amino): """Histidine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "H"
[docs] def set_state(self): """Set forcefield name based on current titration state. Histidines are a special case due to the presence of several different forms. This function sets all neutral forms of HIS to neutral HIS by checking to see if optimization removed :makevar:`hacceptor` or :makevar:`hdonor` flags. Otherwise HID is used as the default. """ errstr = "" if "HIP" not in self.patches and self.name not in ["HIP", "HSP"]: if ( self.get_atom("ND1").hdonor and not self.get_atom("ND1").hacceptor ): if self.has_atom("HE2"): self.remove_atom("HE2") elif ( self.get_atom("NE2").hdonor and not self.get_atom("NE2").hacceptor ): if self.has_atom("HD1"): self.remove_atom("HD1") elif ( self.get_atom("ND1").hacceptor and not self.get_atom("ND1").hdonor ): if self.has_atom("HD1"): self.remove_atom("HD1") else: # Default to HID if self.has_atom("HE2"): self.remove_atom("HE2") if self.has_atom("HD1") and self.has_atom("HE2"): self.ffname = "HIP" elif self.has_atom("HD1"): self.ffname = "HID" elif self.has_atom("HE2"): self.ffname = "HIE" else: errstr = f"Invalid type for {str(self)}! Missing both HD1 and HE2 " "atoms. If you receive this error while using the --assign-only " "option you can only resolve it by adding HD1, HE2 or both to " "this residue." raise TypeError(errstr) Amino.set_state(self)
[docs]class ILE(Amino): """Isoleucine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "I"
[docs]class LEU(Amino): """Leucine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "L"
[docs]class LYS(Amino): """Lysine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "K"
[docs] def set_state(self): """Set forcefield name based on current titration state.""" if "LYN" in self.patches or self.name == "LYN": self.ffname = "LYN" Amino.set_state(self)
[docs]class MET(Amino): """Methionine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "M"
[docs]class PHE(Amino): """Phenylalanine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "F"
[docs]class PRO(Amino): """Proline class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "P"
[docs] def set_state(self): """Set forcefield name based on the current state. Uses ``N*`` and ``C*`` for termini. """ if self.is_n_term: self.ffname = f"N{self.ffname}" elif self.is_c_term: if "NEUTRAL-CTERM" in self.patches: self.ffname = f"NEUTRAL-C{self.ffname}" else: self.ffname = f"C{self.ffname}"
[docs]class SER(Amino): """Serine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "S"
[docs]class THR(Amino): """Threonine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "T"
[docs]class TRP(Amino): """Tryptophan class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "W"
[docs]class TYR(Amino): """Tyrosine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "Y"
[docs] def set_state(self): """Set forcefield name based on current titration state.""" if "TYM" in self.patches or self.name == "TYM": self.ffname = "TYM" Amino.set_state(self)
[docs]class VAL(Amino): """Valine class."""
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the amino acid. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ Amino.__init__(self, atoms, ref) self.reference = ref
[docs] def letter_code(self): """Return letter code for amino acid. :return: amino acid 1-letter code :rtype: str """ return "V"
[docs]class WAT(residue.Residue): """Water class. .. todo:: Why is water in the amino acid module? """ water_residue_names = ["HOH", "WAT"]
[docs] def __init__(self, atoms, ref): """Initialize object. :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the residue. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ sample_atom = atoms[-1] self.atoms = [] self.name = sample_atom.res_name self.chain_id = sample_atom.chain_id self.res_seq = sample_atom.res_seq self.ins_code = sample_atom.ins_code self.fixed = 0 self.ffname = "WAT" self.map = {} self.reference = ref # Create each atom for atom_ in atoms: if atom_.name in ref.altnames: # Rename atoms atom_.name = ref.altnames[atom_.name] atom = struct.Atom(atom_, "HETATM", self) atomname = atom.name if atomname not in self.map: self.add_atom(atom) else: # Don't add duplicate atom with alt_loc field oldatom = self.get_atom(atomname) oldatom.alt_loc = ""
[docs] def create_atom(self, atomname, newcoords): """Create a water atom. Note the HETATM field. .. todo:: There is a huge amount of duplicated code in this module. :param atomname: name of atom to be added :type atomname: str :param newcoords: coordinates for new atom :type newcoords: [float, float, float] """ oldatom = self.atoms[0] newatom = struct.Atom(oldatom, "HETATM", self) newatom.x = newcoords[0] newatom.y = newcoords[1] newatom.z = newcoords[2] newatom.name = atomname newatom.element = atomname[0] newatom.occupancy = 1.00 newatom.temp_factor = 0.00 newatom.added = 1 self.add_atom(newatom)
[docs] def add_atom(self, atom): """Add an atom to the residue. Override the existing add_atom - include the link to the reference object. :param atom: add atom to residue :type atom: Atom """ self.atoms.append(atom) atomname = atom.name self.map[atomname] = atom try: atom.reference = self.reference.map[atomname] for bond in atom.reference.bonds: if self.has_atom(bond): bondatom = self.map[bond] if bondatom not in atom.bonds: atom.bonds.append(bondatom) if atom not in bondatom.bonds: bondatom.bonds.append(atom) except KeyError: _LOGGER.debug(f"Ignoring reference for WAT atom {atomname}") atom.reference = None
[docs]class LIG(residue.Residue): """Generic ligand class."""
[docs] def __init__(self, atoms, ref): """Initialize this object. .. todo:: why is the force field name "WAT" for this? :param atoms: A list of :class:`Atom` objects to be stored in this object :type atoms: [Atom] :param ref: The reference object for the residue. Used to convert from the alternate naming scheme to the main naming scheme. :type ref: Residue """ sample_atom = atoms[-1] self.atoms = [] self.name = sample_atom.res_name self.chain_id = sample_atom.chain_id self.res_seq = sample_atom.res_seq self.ins_code = sample_atom.ins_code self.fixed = 0 self.ffname = "WAT" self.map = {} self.reference = ref self.is_n_term = 0 self.is_c_term = 0 # Create each atom for atom_ in atoms: if atom_.name in ref.altnames: # Rename atoms atom_.name = ref.altnames[atom_.name] atom = struct.Atom(atom_, "HETATM", self) atomname = atom.name if atomname not in self.map: self.add_atom(atom) else: # Don't add duplicate atom with alt_loc field oldatom = self.get_atom(atomname) oldatom.alt_loc = ""
[docs] def create_atom(self, atomname, newcoords): """Create a ligand atom. :param atomname: name of atom to be added :type atomname: str :param newcoords: coordinates for new atom :type newcoords: [float, float, float] """ oldatom = self.atoms[0] newatom = struct.Atom(oldatom, "HETATM", self) newatom.x = newcoords[0] newatom.y = newcoords[1] newatom.z = newcoords[2] newatom.name = atomname newatom.element = atomname[0] newatom.occupancy = 1.00 newatom.temp_factor = 0.00 newatom.added = 1 self.add_atom(newatom)
[docs] def add_atom(self, atom): """Add an atom to the residue. Override the existing add_atom - include the link to the reference object. :param atom: add atom to residue :type atom: Atom """ self.atoms.append(atom) atomname = atom.name self.map[atomname] = atom try: atom.reference = self.reference.map[atomname] for bond in atom.reference.bonds: if self.has_atom(bond): bondatom = self.map[bond] if bondatom not in atom.bonds: atom.bonds.append(bondatom) if atom not in bondatom.bonds: bondatom.bonds.append(atom) except KeyError: _LOGGER.debug(f"Ignoring atom reference for ligand {atomname}") atom.reference = None