@@ -1058,10 +1058,8 @@ def addMissingHydrogens(self, pH=7.0, forcefield=None):
1058
1058
1059
1059
Notes
1060
1060
-----
1061
- No extensive electrostatic analysis is performed; only default residue pKas are used.
1062
-
1063
- Examples
1064
- --------
1061
+ No extensive electrostatic analysis is performed; only default residue pKas are used. The pH is only
1062
+ taken into account for standard amino acids.
1065
1063
1066
1064
Examples
1067
1065
--------
@@ -1070,13 +1068,104 @@ def addMissingHydrogens(self, pH=7.0, forcefield=None):
1070
1068
1071
1069
>>> fixer = PDBFixer(pdbid='1VII')
1072
1070
>>> fixer.addMissingHydrogens(pH=8.0)
1073
-
1074
1071
"""
1072
+ extraDefinitions = self ._downloadNonstandardDefinitions ()
1073
+ variants = [self ._describeVariant (res , extraDefinitions ) for res in self .topology .residues ()]
1075
1074
modeller = app .Modeller (self .topology , self .positions )
1076
- modeller .addHydrogens (pH = pH , forcefield = forcefield )
1075
+ modeller .addHydrogens (pH = pH , forcefield = forcefield , variants = variants )
1077
1076
self .topology = modeller .topology
1078
1077
self .positions = modeller .positions
1079
1078
1079
+ def _downloadNonstandardDefinitions (self ):
1080
+ """If the file contains any nonstandard residues, download their definitions and build
1081
+ the information needed to add hydrogens to them.
1082
+ """
1083
+ app .Modeller ._loadStandardHydrogenDefinitions ()
1084
+ resnames = set (residue .name for residue in self .topology .residues ())
1085
+ definitions = {}
1086
+ for name in resnames :
1087
+ if name not in app .Modeller ._residueHydrogens :
1088
+ # Try to download the definition.
1089
+
1090
+ try :
1091
+ file = urlopen (f'https://files.rcsb.org/ligands/download/{ name } .cif' )
1092
+ contents = file .read ().decode ('utf-8' )
1093
+ file .close ()
1094
+ except :
1095
+ continue
1096
+
1097
+ # Record the atoms and bonds.
1098
+
1099
+ from openmm .app .internal .pdbx .reader .PdbxReader import PdbxReader
1100
+ reader = PdbxReader (StringIO (contents ))
1101
+ data = []
1102
+ reader .read (data )
1103
+ block = data [0 ]
1104
+ atomData = block .getObj ('chem_comp_atom' )
1105
+ atomNameCol = atomData .getAttributeIndex ('atom_id' )
1106
+ symbolCol = atomData .getAttributeIndex ('type_symbol' )
1107
+ leavingCol = atomData .getAttributeIndex ('pdbx_leaving_atom_flag' )
1108
+ atoms = [(row [atomNameCol ], row [symbolCol ].upper (), row [leavingCol ] == 'Y' ) for row in atomData .getRowList ()]
1109
+ bondData = block .getObj ('chem_comp_bond' )
1110
+ if bondData is None :
1111
+ bonds = []
1112
+ else :
1113
+ atom1Col = bondData .getAttributeIndex ('atom_id_1' )
1114
+ atom2Col = bondData .getAttributeIndex ('atom_id_2' )
1115
+ bonds = [(row [atom1Col ], row [atom2Col ]) for row in bondData .getRowList ()]
1116
+ definitions [name ] = (atoms , bonds )
1117
+ return definitions
1118
+
1119
+ def _describeVariant (self , residue , definitions ):
1120
+ """Build the variant description to pass to addHydrogens() for a residue."""
1121
+ if residue .name not in definitions :
1122
+ return None
1123
+ atoms , bonds = definitions [residue .name ]
1124
+
1125
+ # See if the heavy atoms are identical.
1126
+
1127
+ topologyHeavy = dict ((atom .name , atom ) for atom in residue .atoms () if atom .element is not None and atom .element != app .element .hydrogen )
1128
+ definitionHeavy = dict ((atom [0 ], atom ) for atom in atoms if atom [1 ] != '' and atom [1 ] != 'H' )
1129
+ for name in topologyHeavy :
1130
+ if name not in definitionHeavy or definitionHeavy [name ][1 ] != topologyHeavy [name ].element .symbol .upper ():
1131
+ # This atom isn't present in the definition
1132
+ return None
1133
+ for name in definitionHeavy :
1134
+ if name not in topologyHeavy and not definitionHeavy [name ][2 ]:
1135
+ # This isn't a leaving atom, and it isn't present in the topology.
1136
+ return None
1137
+
1138
+ # Build the list of hydrogens.
1139
+
1140
+ variant = []
1141
+ definitionAtoms = dict ((atom [0 ], atom ) for atom in atoms )
1142
+ topologyBonds = list (residue .bonds ())
1143
+ for name1 , name2 in bonds :
1144
+ if definitionAtoms [name1 ][1 ] == 'H' :
1145
+ h , parent = name1 , name2
1146
+ elif definitionAtoms [name2 ][1 ] == 'H' :
1147
+ h , parent = name2 , name1
1148
+ else :
1149
+ continue
1150
+ if definitionAtoms [h ][2 ]:
1151
+ # The hydrogen is marked as a leaving atom. Omit it if the parent is not present,
1152
+ # or if the parent has an external bond.
1153
+ if parent not in topologyHeavy :
1154
+ continue
1155
+ parentAtom = topologyHeavy [parent ]
1156
+ exclude = False
1157
+ for atom1 , atom2 in topologyBonds :
1158
+ if atom1 == parentAtom and atom2 .residue != residue :
1159
+ exclude = True
1160
+ break
1161
+ if atom2 == parentAtom and atom1 .residue != residue :
1162
+ exclude = True
1163
+ break
1164
+ if exclude :
1165
+ continue
1166
+ variant .append ((h , parent ))
1167
+ return variant
1168
+
1080
1169
def addSolvent (self , boxSize = None , padding = None , boxVectors = None , positiveIon = 'Na+' , negativeIon = 'Cl-' , ionicStrength = 0 * unit .molar , boxShape = 'cube' ):
1081
1170
"""Add a solvent box surrounding the structure.
1082
1171
0 commit comments