1 #!/bin/env python 2 # 3 # File: OpenMMPrepareMacromolecule.py 4 # Author: Manish Sud <msud@san.rr.com> 5 # 6 # Copyright (C) 2025 Manish Sud. All rights reserved. 7 # 8 # The functionality available in this script is implemented using OpenMM, an 9 # open source molecuar simulation package. 10 # 11 # This file is part of MayaChemTools. 12 # 13 # MayaChemTools is free software; you can redistribute it and/or modify it under 14 # the terms of the GNU Lesser General Public License as published by the Free 15 # Software Foundation; either version 3 of the License, or (at your option) any 16 # later version. 17 # 18 # MayaChemTools is distributed in the hope that it will be useful, but without 19 # any warranty; without even the implied warranty of merchantability of fitness 20 # for a particular purpose. See the GNU Lesser General Public License for more 21 # details. 22 # 23 # You should have received a copy of the GNU Lesser General Public License 24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or 25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, 26 # Boston, MA, 02111-1307, USA. 27 # 28 29 from __future__ import print_function 30 31 # Add local python path to the global path and import standard library modules... 32 import os 33 import sys; sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python")) 34 import time 35 import re 36 37 # OpenMM imports... 38 try: 39 import openmm as mm 40 import pdbfixer 41 from pdbfixer.pdbfixer import __version__ as pdbfixerversion 42 except ImportError as ErrMsg: 43 sys.stderr.write("\nFailed to import OpenMM related module/package: %s\n" % ErrMsg) 44 sys.stderr.write("Check/update your OpenMM environment and try again.\n\n") 45 sys.exit(1) 46 47 # MayaChemTools imports... 48 try: 49 from docopt import docopt 50 import MiscUtil 51 import OpenMMUtil 52 except ImportError as ErrMsg: 53 sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg) 54 sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n") 55 sys.exit(1) 56 57 ScriptName = os.path.basename(sys.argv[0]) 58 Options = {} 59 OptionsInfo = {} 60 61 def main(): 62 """Start execution of the script.""" 63 64 MiscUtil.PrintInfo("\n%s (OpenMM v%s; PDBFixerVersion v%s; MayaChemTools v%s; %s): Starting...\n" % (ScriptName, mm.Platform.getOpenMMVersion(), pdbfixerversion, MiscUtil.GetMayaChemToolsVersion(), time.asctime())) 65 66 (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime() 67 68 # Retrieve command line arguments and options... 69 RetrieveOptions() 70 71 # Process and validate command line arguments and options... 72 ProcessOptions() 73 74 # Perform actions required by the script... 75 PrepareMacromolecule() 76 77 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName) 78 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime)) 79 80 def PrepareMacromolecule(): 81 """Prepare a macromolecule for simulation and write it out.""" 82 83 # Read macromolecule... 84 PDBFixerObjectHandle = ReadMacromolecule() 85 86 # Identify missing residues... 87 IdentifyMissingResidues(PDBFixerObjectHandle) 88 89 # Identify and replace non-standard residues... 90 IdentifyAndReplaceNonStandardResidues(PDBFixerObjectHandle) 91 92 # Identify and delete heterogen residues... 93 IdentifyAndDeleteHeterogenResidues(PDBFixerObjectHandle) 94 95 # Identify and add missing atoms... 96 IdentifyAndAddMissingAtoms(PDBFixerObjectHandle) 97 98 if OptionsInfo["WaterBox"]: 99 AddWaterBox(PDBFixerObjectHandle) 100 elif OptionsInfo["Membrane"]: 101 AddLipidMembrane(PDBFixerObjectHandle) 102 else: 103 MiscUtil.PrintInfo("\nSkipping addtion of water box or membrane...") 104 105 # Write macromolecule... 106 WriteMacromolecule(PDBFixerObjectHandle) 107 108 def ReadMacromolecule(): 109 """Read macromolecule.""" 110 111 Infile = OptionsInfo["Infile"] 112 MiscUtil.PrintInfo("\nProcessing file %s..." % Infile) 113 114 PDBFixerObjectHandle = pdbfixer.pdbfixer.PDBFixer(filename = Infile) 115 116 return PDBFixerObjectHandle 117 118 def WriteMacromolecule(PDBFixerObjectHandle): 119 """Write macromolecule. """ 120 121 MiscUtil.PrintInfo("\nGenerating output file %s..." % OptionsInfo["Outfile"]) 122 OpenMMUtil.WritePDBFile(OptionsInfo["Outfile"], PDBFixerObjectHandle.topology, PDBFixerObjectHandle.positions, KeepIDs = True) 123 124 def IdentifyMissingResidues(PDBFixerObjectHandle): 125 """Identify missing residues based on PDB REMARK records.""" 126 127 MiscUtil.PrintInfo("\nIdentifying missing residues...") 128 129 PDBFixerObjectHandle.missingResidues = {} 130 PDBFixerObjectHandle.findMissingResidues() 131 132 ListMissingResidues(PDBFixerObjectHandle) 133 134 def ListMissingResidues(PDBFixerObjectHandle): 135 """List missing residues.""" 136 137 if len(PDBFixerObjectHandle.missingResidues) == 0: 138 MiscUtil.PrintInfo("Number of missing residues: 0") 139 return 140 141 Chains = list(PDBFixerObjectHandle.topology.chains()) 142 143 MissingResiduesCount = 0 144 MissingResiduesInfo = [] 145 for Index, Key in enumerate(sorted(PDBFixerObjectHandle.missingResidues)): 146 ChainIndex = Key[0] 147 # ResidueIndex corresponds to the residue after the insertion of missing residues...` 148 ResidueIndex = Key[1] 149 150 MissingResidues = PDBFixerObjectHandle.missingResidues[Key] 151 MissingResiduesCount += len(MissingResidues) 152 153 Chain = Chains[ChainIndex] 154 ChainResidues = list(Chain.residues()) 155 156 if ResidueIndex < len(ChainResidues): 157 ResidueNumOffset = int(ChainResidues[ResidueIndex].id) - len(MissingResidues) - 1 158 else: 159 ResidueNumOffset = int(ChainResidues[-1].id) 160 161 StartResNum = ResidueNumOffset + 1 162 EndResNum = ResidueNumOffset + len(MissingResidues) 163 164 MissingResiduesInfo.append("Chain: %s; StartResNum-EndResNum: %s-%s; Count: %s; Residues: %s" % (Chain.id, StartResNum, EndResNum, len(MissingResidues), ", ".join(MissingResidues))) 165 166 MiscUtil.PrintInfo("Total number of missing residues: %s" % (MissingResiduesCount)) 167 if OptionsInfo["ListDetails"]: 168 MiscUtil.PrintInfo("%s" % ("\n".join(MissingResiduesInfo))) 169 170 def IdentifyAndReplaceNonStandardResidues(PDBFixerObjectHandle): 171 """Identify and replace non-standard residues.""" 172 173 MiscUtil.PrintInfo("\nIdentifying non-standard residues...") 174 175 PDBFixerObjectHandle.findNonstandardResidues() 176 NonStandardResidues = PDBFixerObjectHandle.nonstandardResidues 177 NumNonStandardResiues = len(NonStandardResidues) 178 179 MiscUtil.PrintInfo("Total number of non-standard residues: %s" % NumNonStandardResiues) 180 181 if NumNonStandardResiues == 0: 182 return 183 184 if OptionsInfo["ListDetails"]: 185 ListNonStandardResidues(PDBFixerObjectHandle) 186 187 if not OptionsInfo["ReplaceNonStandardResidues"]: 188 MiscUtil.PrintInfo("Skipping replacement of non-standard residues...") 189 return 190 191 MiscUtil.PrintInfo("Replacing non-standard residues...") 192 PDBFixerObjectHandle.replaceNonstandardResidues() 193 194 def ListNonStandardResidues(PDBFixerObjectHandle): 195 """List non-standard residues. """ 196 197 NonStandardResidues = PDBFixerObjectHandle.nonstandardResidues 198 199 if len(NonStandardResidues) == 0: 200 return 201 202 NonStandardResiduesMappingInfo = [] 203 for Values in NonStandardResidues: 204 NonStandardRes = Values[0] 205 StandardResName = Values[1] 206 207 MappingInfo = "<%s %s %s>: %s" % (NonStandardRes.id, NonStandardRes.name, NonStandardRes.chain.id, StandardResName) 208 NonStandardResiduesMappingInfo.append(MappingInfo) 209 210 if len(NonStandardResiduesMappingInfo): 211 MiscUtil.PrintInfo("Non-standard residues mapping: %s\n" % ",".join(NonStandardResiduesMappingInfo)) 212 213 def IdentifyAndDeleteHeterogenResidues(PDBFixerObjectHandle): 214 """Identify and delete heterogen residues.""" 215 216 DeleteHeterogens = OptionsInfo["DeleteHeterogens"] 217 if DeleteHeterogens is None: 218 MiscUtil.PrintInfo("\nSkipping deletion of any heterogen residues...") 219 return 220 221 if re.match("^All$", DeleteHeterogens, re.I): 222 MiscUtil.PrintInfo("\nDeleting all heterogen residues including water...") 223 PDBFixerObjectHandle.removeHeterogens(keepWater = False) 224 elif re.match("^AllExceptWater$", DeleteHeterogens, re.I): 225 MiscUtil.PrintInfo("\nDeleting all heterogen residues except water...") 226 PDBFixerObjectHandle.removeHeterogens(keepWater = True) 227 elif re.match("^WaterOnly$", DeleteHeterogens, re.I): 228 MiscUtil.PrintInfo("\nDeleting water only heterogen residues...") 229 DeleteWater(PDBFixerObjectHandle) 230 else: 231 MiscUtil.PrintError("The value specified, %s, for option \"--deleteHeterogens\" is not valid. Supported values: All AllExceptWater WaterOnly None" % DeleteHeterogens) 232 233 def DeleteWater(PDBFixerObjectHandle): 234 """Delete water. """ 235 236 ModellerObjectHandle = mm.app.Modeller(PDBFixerObjectHandle.topology, PDBFixerObjectHandle.positions) 237 ModellerObjectHandle.deleteWater() 238 239 PDBFixerObjectHandle.topology = ModellerObjectHandle.topology 240 PDBFixerObjectHandle.positions = ModellerObjectHandle.positions 241 242 def IdentifyAndAddMissingAtoms(PDBFixerObjectHandle): 243 """Identify and missing atoms along with already identified missing residues.""" 244 245 MiscUtil.PrintInfo("\nIdentifying missing atoms...") 246 PDBFixerObjectHandle.findMissingAtoms() 247 248 ListMissingAtoms(PDBFixerObjectHandle) 249 250 if OptionsInfo["AddHeavyAtoms"] and OptionsInfo["AddResidues"]: 251 Msg = "Adding missing heavy atoms along with missing residues..." 252 elif OptionsInfo["AddHeavyAtoms"]: 253 Msg = "Adding missing heavy atoms..." 254 elif OptionsInfo["AddResidues"]: 255 Msg = "Adding missing residues..." 256 else: 257 Msg = "Skipping addition of any heavy atoms along with any missing residues..." 258 259 if not OptionsInfo["AddHeavyAtoms"]: 260 PDBFixerObjectHandle.missingAtoms = {} 261 PDBFixerObjectHandle.missingTerminals = {} 262 263 MiscUtil.PrintInfo("\n%s" % Msg) 264 if OptionsInfo["AddHeavyAtoms"] or OptionsInfo["AddResidues"]: 265 PDBFixerObjectHandle.addMissingAtoms() 266 267 if OptionsInfo["AddHydrogens"]: 268 MiscUtil.PrintInfo("Adding missing hydrogens at pH %s..." % OptionsInfo["AddHydrogensAtpH"]) 269 PDBFixerObjectHandle.addMissingHydrogens(pH = OptionsInfo["AddHydrogensAtpH"], forcefield = None) 270 else: 271 MiscUtil.PrintInfo("Skipping addition of any missing hydrogens...") 272 273 def ListMissingAtoms(PDBFixerObjectHandle): 274 """List missing atoms.""" 275 276 if len(PDBFixerObjectHandle.missingAtoms) == 0 and len(PDBFixerObjectHandle.missingTerminals) == 0: 277 MiscUtil.PrintInfo("Total number of missing atoms: 0") 278 return 279 280 ListMissingAtomsInfo(PDBFixerObjectHandle.missingAtoms, TerminalAtoms = False) 281 ListMissingAtomsInfo(PDBFixerObjectHandle.missingTerminals, TerminalAtoms = True) 282 283 def ListMissingAtomsInfo(MissingAtoms, TerminalAtoms): 284 """Setup missing atoms information. """ 285 286 MissingAtomsInfo = [] 287 MissingAtomsCount = 0 288 MissingAtomsResiduesCount = 0 289 for Residue, Atoms in MissingAtoms.items(): 290 MissingAtomsResiduesCount += 1 291 MissingAtomsCount += len(Atoms) 292 293 if TerminalAtoms: 294 AtomsNames = [AtomName for AtomName in Atoms] 295 else: 296 AtomsNames = [Atom.name for Atom in Atoms] 297 AtomsInfo = "<%s %s %s>: %s" % (Residue.id, Residue.name, Residue.chain.id, ", ".join(AtomsNames)) 298 MissingAtomsInfo.append(AtomsInfo) 299 300 AtomsLabel = "terminal atoms" if TerminalAtoms else "atoms" 301 if MissingAtomsCount == 0: 302 MiscUtil.PrintInfo("Total number of missing %s: %s" % (MissingAtomsCount, AtomsLabel)) 303 else: 304 ResiduesLabel = "residue" if MissingAtomsResiduesCount == 1 else "residues" 305 MiscUtil.PrintInfo("Total number of missing %s across %s %s: %s" % (AtomsLabel, MissingAtomsResiduesCount, ResiduesLabel, MissingAtomsCount)) 306 if OptionsInfo["ListDetails"]: 307 MiscUtil.PrintInfo("Missing %s across residues: %s" % (AtomsLabel, "; ".join(MissingAtomsInfo))) 308 309 def AddWaterBox(PDBFixerObjectHandle): 310 """Add water box. """ 311 312 MiscUtil.PrintInfo("\nAdding water box...") 313 314 WaterBoxParams = OptionsInfo["WaterBoxParams"] 315 316 Size, Padding, Shape = [None] * 3 317 if WaterBoxParams["ModeSize"]: 318 SizeList = WaterBoxParams["SizeList"] 319 Size = mm.Vec3(SizeList[0], SizeList[1], SizeList[2]) * mm.unit.nanometer 320 elif WaterBoxParams["ModePadding"]: 321 Padding = WaterBoxParams["Padding"] * mm.unit.nanometer 322 Shape = WaterBoxParams["Shape"] 323 else: 324 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, mode, using \"--waterBoxParams\" option is not a valid value. Supported values: Size Padding \n" % (WaterBoxParams["Mode"])) 325 326 IonicStrength = OptionsInfo["IonicStrength"] * mm.unit.molar 327 PDBFixerObjectHandle.addSolvent(boxSize = Size, padding = Padding, boxShape = Shape, positiveIon = OptionsInfo["IonPositive"], negativeIon = OptionsInfo["IonNegative"], ionicStrength = IonicStrength) 328 329 def AddLipidMembrane(PDBFixerObjectHandle): 330 """Add lipid membrane along with water.""" 331 332 MiscUtil.PrintInfo("\nAdding membrane along with water...") 333 334 MembraneParams = OptionsInfo["MembraneParams"] 335 336 LipidType = MembraneParams["LipidType"] 337 MembraneCenterZ = MembraneParams["MembraneCenterZ"] * mm.unit.nanometer 338 Padding = MembraneParams["Padding"] * mm.unit.nanometer 339 340 IonicStrength = OptionsInfo["IonicStrength"] * mm.unit.molar 341 342 PDBFixerObjectHandle.addMembrane(lipidType = LipidType, membraneCenterZ = MembraneCenterZ, minimumPadding = Padding, positiveIon = OptionsInfo["IonPositive"], negativeIon = OptionsInfo["IonNegative"], ionicStrength = IonicStrength) 343 344 def ProcessMembraneParamsOption(): 345 """Process option for membrane parameters.""" 346 347 ParamsOptionName = "--membraneParams" 348 ParamsOptionValue = Options[ParamsOptionName] 349 ParamsDefaultInfo = {"LipidType" : ["str", "POPC"], "MembraneCenterZ": ["float", 0.0], "Padding": ["float", 1.0]} 350 MembraneParams = MiscUtil.ProcessOptionNameValuePairParameters(ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo) 351 352 for ParamName in ["LipidType", "MembraneCenterZ", "Padding"]: 353 ParamValue = MembraneParams[ParamName] 354 if ParamName == "LipidType": 355 LipidTypes = ["POPC", "POPE", "DLPC", "DLPE", "DMPC", "DOPC", "DPPC"] 356 if ParamValue not in LipidTypes: 357 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: %s \n" % (ParamValue, ParamName, ParamsOptionName, LipidTypes)) 358 elif ParamName == "Padding": 359 if ParamValue <= 0: 360 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: > 0\n" % (ParamValue, ParamName, ParamsOptionName)) 361 362 OptionsInfo["MembraneParams"] = MembraneParams 363 364 def ProcessOptions(): 365 """Process and validate command line arguments and options.""" 366 367 MiscUtil.PrintInfo("Processing options...") 368 369 # Validate options... 370 ValidateOptions() 371 372 OptionsInfo["Infile"] = Options["--infile"] 373 FileDir, FileName, FileExt = MiscUtil.ParseFileName(OptionsInfo["Infile"]) 374 OptionsInfo["InfileRoot"] = FileName 375 376 OptionsInfo["Outfile"] = Options["--outfile"] 377 378 OptionsInfo["AddHeavyAtoms"] = True if re.match("^yes$", Options["--addHeavyAtoms"], re.I) else False 379 380 OptionsInfo["AddHydrogens"] = True if re.match("^yes$", Options["--addHydrogens"], re.I) else False 381 OptionsInfo["AddHydrogensAtpH"] = float(Options["--addHydrogensAtpH"]) 382 383 OptionsInfo["AddResidues"] = True if re.match("^yes$", Options["--addResidues"], re.I) else False 384 385 DeleteHeterogens = Options["--deleteHeterogens"] 386 if re.match("^All$", DeleteHeterogens, re.I): 387 DeleteHeterogens = "All" 388 elif re.match("^AllExceptWater$", DeleteHeterogens, re.I): 389 DeleteHeterogens = "AllExceptWater" 390 elif re.match("^WaterOnly$", DeleteHeterogens, re.I): 391 DeleteHeterogens = "WaterOnly" 392 elif re.match("^None$", DeleteHeterogens, re.I): 393 DeleteHeterogens = None 394 elif re.match("^auto$", DeleteHeterogens, re.I): 395 DeleteHeterogens = None 396 if re.match("^yes$", Options["--membrane"], re.I) or re.match("^yes$", Options["--waterBox"], re.I): 397 DeleteHeterogens = "WaterOnly" 398 else: 399 MiscUtil.PrintError("The value specified, %s, for option \"--deleteHeterogens\" is not valid. Supported values: All AllExceptWater WaterOnly None" % DeleteHeterogens) 400 OptionsInfo["DeleteHeterogens"] = DeleteHeterogens 401 402 OptionsInfo["IonPositive"] = Options["--ionPositive"] 403 OptionsInfo["IonNegative"] = Options["--ionNegative"] 404 OptionsInfo["IonicStrength"] = float(Options["--ionicStrength"]) 405 406 OptionsInfo["ListDetails"] = True if re.match("^yes$", Options["--listDetails"], re.I) else False 407 408 OptionsInfo["Membrane"] = True if re.match("^yes$", Options["--membrane"], re.I) else False 409 ProcessMembraneParamsOption() 410 411 OptionsInfo["ReplaceNonStandardResidues"] = True if re.match("^yes$", Options["--replaceNonStandardResidues"], re.I) else False 412 413 OptionsInfo["WaterBox"] = True if re.match("^yes$", Options["--waterBox"], re.I) else False 414 OptionsInfo["WaterBoxParams"] = OpenMMUtil.ProcessOptionOpenMMWaterBoxParameters("--waterBoxParams", Options["--waterBoxParams"]) 415 416 OptionsInfo["Overwrite"] = Options["--overwrite"] 417 418 def RetrieveOptions(): 419 """Retrieve command line arguments and options.""" 420 421 # Get options... 422 global Options 423 Options = docopt(_docoptUsage_) 424 425 # Set current working directory to the specified directory... 426 WorkingDir = Options["--workingdir"] 427 if WorkingDir: 428 os.chdir(WorkingDir) 429 430 # Handle examples option... 431 if "--examples" in Options and Options["--examples"]: 432 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_)) 433 sys.exit(0) 434 435 def ValidateOptions(): 436 """Validate option values.""" 437 438 MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"]) 439 MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "pdb cif") 440 441 MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "pdb cif") 442 MiscUtil.ValidateOptionsOutputFileOverwrite("-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"]) 443 MiscUtil.ValidateOptionsDistinctFileNames("-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"]) 444 445 MiscUtil.ValidateOptionTextValue("--addHeavyAtoms", Options["--addHeavyAtoms"], "yes no") 446 447 MiscUtil.ValidateOptionTextValue("--addHydrogens", Options["--addHydrogens"], "yes no") 448 MiscUtil.ValidateOptionFloatValue("--addHydrogensAtpH", Options["--addHydrogensAtpH"], {">=": 0}) 449 450 MiscUtil.ValidateOptionTextValue("--addResidues", Options["--addResidues"], "yes no") 451 452 MiscUtil.ValidateOptionTextValue("--deleteHeterogens", Options["--deleteHeterogens"], "All AllExceptWater WaterOnly None auto") 453 DeleteHeterogens = Options["--deleteHeterogens"] 454 if re.match("^(AllExceptWater|None)$", DeleteHeterogens, re.I): 455 if re.match("^yes$", Options["--membrane"], re.I): 456 MiscUtil.PrintError("The value specified, %s, for option \"--deleteHeterogens\" is not valid during \"yes\" value of option \"--membrane\". " % DeleteHeterogens) 457 elif re.match("^yes$", Options["--waterBox"], re.I): 458 MiscUtil.PrintError("The value specified, %s, for option \"--deleteHeterogens\" is not valid during \"yes\" value of option \"--waterBox\". " % DeleteHeterogens) 459 460 ValidValues = "Li+ Na+ K+ Rb+ Cs+" 461 EscapedValidValuesPattern = "Li\+|Na\+|K\+|Rb\+|Cs\+" 462 Value = Options["--ionPositive"] 463 if not re.match("^(%s)$" % EscapedValidValuesPattern, Value): 464 MiscUtil.PrintError("The value specified, %s, for option \"-ionPositive\" is not valid: Supported value(s): %s" % (Value, ValidValues)) 465 466 ValidValues = "F- Cl- Br- I-" 467 ValidValuesPattern = "F-|Cl-|Br-|I-" 468 Value = Options["--ionNegative"] 469 if not re.match("^(%s)$" % ValidValuesPattern, Value): 470 MiscUtil.PrintError("The value specified, %s, for option \"-ionNegative\" is not valid: Supported value(s): %s" % (Value, ValidValues)) 471 472 MiscUtil.ValidateOptionFloatValue("--ionicStrength", Options["--ionicStrength"], {">=": 0}) 473 474 MiscUtil.ValidateOptionTextValue("--listDetails", Options["--listDetails"], "yes no") 475 MiscUtil.ValidateOptionTextValue("--replaceNonStandardResidues", Options["--replaceNonStandardResidues"], "yes no") 476 477 MiscUtil.ValidateOptionTextValue("--membrane", Options["--membrane"], "yes no") 478 MiscUtil.ValidateOptionTextValue("--waterBox", Options["--waterBox"], "yes no") 479 480 if re.match("^yes$", Options["--membrane"], re.I) and re.match("^yes$", Options["--waterBox"], re.I): 481 MiscUtil.PrintError("You've specified, \"Yes\" for both \"--membrane\" and \"--waterBox\" options. It's not allowed. You must choose between adding a water box or a membrane. The water is automatically added during the construction of the membrane.") 482 483 # Setup a usage string for docopt... 484 _docoptUsage_ = """ 485 OpenMMPrepareMacromolecule.py - Prepare a macromolecule for simulation. 486 487 Usage: 488 OpenMMPrepareMacromolecule.py [--addHeavyAtoms <yes or no>] [--addHydrogens <yes or no>] [--addHydrogensAtpH <number>] 489 [--addResidues <yes or no>] [--deleteHeterogens <All, AllExceptWater, WaterOnly, None>] [--ionPositive <text>] 490 [--ionNegative <text>] [--ionicStrength <number>] [--listDetails <yes or no> ] [--membrane <yes or no>] 491 [--membraneParams <Name,Value,..>] [--overwrite] [--replaceNonStandardResidues <yes or no>] [--waterBox <yes or no>] 492 [--waterBoxParams <Name,Value,..>] [-w <dir>] -i <infile> -o <outfile> 493 OpenMMPrepareMacromolecule.py -h | --help | -e | --examples 494 495 Description: 496 Prepare a macromolecute in an input file for molecular simulation and 497 and write it out to an output file. The macromolecule is prepared by 498 automatically performing the following actions: 499 500 . Identify and replace non-standard residues 501 . Add missing residues 502 . Add missing heavy atoms 503 . Add missing hydrogens 504 505 You may optionally remove heterogens, add a water box, and add a lipid 506 membrane. 507 508 The supported input file format are: PDB (.pdb) and CIF (.cif) 509 510 The supported output file formats are: PDB (.pdb) and CIF (.cif) 511 512 Options: 513 --addHeavyAtoms <yes or no> [default: yes] 514 Add missing non-hydrogen atoms based on the templates. The terminal atoms 515 are also added. 516 --addHydrogens <yes or no> [default: yes] 517 Add missing hydrogens at pH value specified using '-addHydrogensAtpH' 518 option. The missing hydrogens are added based on the templates and pKa 519 calculations are performed. 520 --addHydrogensAtpH <number> [default: 7.0] 521 pH value to use for adding missing hydrogens. 522 --addResidues <yes or no> [default: yes] 523 Add missing residues unidentified based on the PDB records. 524 --deleteHeterogens <All, AllExceptWater, WaterOnly, None> [default: auto] 525 Delete heterogens corresponding to non-standard names of amino acids, dna, 526 and rna along with any ligand names. 'N' and 'UNK' also consider standard 527 residues. Default value: WaterOnly during addition of WaterBox or Membrane; 528 Otherwise, None. 529 530 The 'AllExceptWater' or 'None' values are not allowed during the addition of 531 a water box or membrane. The waters must be deleted as they are explicitly 532 added during the construction of a water box and membrane. 533 -e, --examples 534 Print examples. 535 -h, --help 536 Print this help message. 537 -i, --infile <infile> 538 Input file name. 539 --ionPositive <text> [default: Na+] 540 Type of positive ion to add during the addition of a water box or membrane. 541 Possible values: Li+, Na+, K+, Rb+, or Cs+. 542 --ionNegative <text> [default: Cl-] 543 Type of negative ion to add during the addition of a water box or membrane. 544 Possible values: Cl-, Br-, F-, or I-. 545 --ionicStrength <number> [default: 0.0] 546 Total concentration of both positive and negative ions to add excluding 547 the ions added to neutralize the system during the addition of a water box 548 or a membrane. 549 -l, --listDetails <yes or no> [default: no] 550 List details about missing and non-standard residues along with residues 551 containing missing atoms. 552 --membrane <yes or no> [default: no] 553 Add lipid membrane along with a water box. The script replies on OpenMM 554 method modeller.addMebrane() to perform the task. The membrane is added 555 in the XY plane. The existing macromolecule must be oriented and positioned 556 correctly. 557 558 A word to the wise: You may want to start with a model from the Orientations 559 of Proteins in Membranes (OPM) database at http://opm.phar.umich.edu. 560 561 The size of the membrane and water box are determined by the value of 562 'padding' parameter specified using '--membraneParams' option. All atoms 563 in macromolecule are guaranteed to be at least this far from any edge of 564 the periodic box. 565 --membraneParams <Name,Value,..> [default: auto] 566 A comma delimited list of parameter name and value pairs for adding 567 a lipid membrane and water. 568 569 The supported parameter names along with their default values are 570 are shown below: 571 572 lipidType, POPC [ Possible values: POPC, POPE, DLPC, DLPE, DMPC, 573 DOPC or DPPC ] 574 membraneCenterZ, 0.0 575 padding, 1.0 576 577 A brief description of parameters is provided below: 578 579 lipidType: Type of lipid to use for constructing the membrane. 580 membraneCenterZ: Position along the Z axis of the center of 581 the membrane in nanomertes. 582 padding: Minimum padding distance to use in nanomertes. It's used 583 to determine the size of the membrane and water box. All atoms 584 in the macromolecule are guaranteed to be at least this far from 585 any edge of the periodic box. 586 587 -o, --outfile <outfile> 588 Output file name. 589 --overwrite 590 Overwrite existing files. 591 --replaceNonStandardResidues <yes or no> [default: yes] 592 Replace non-standard residue names by standard residue names based on the 593 list of non-standard residues available in pdbfixer. 594 --waterBox <yes or no> [default: no] 595 Add water box. 596 --waterBoxParams <Name,Value,..> [default: auto] 597 A comma delimited list of parameter name and value pairs for adding 598 a water box. 599 600 The supported parameter names along with their default values are 601 are shown below: 602 603 mode, Padding [ Possible values: Size or Padding ] 604 size, None [ Possible value: xsize ysize zsize ] 605 padding, 1.0 606 shape, cube [ Possible values: cube, dodecahedron, or octahedron ] 607 608 A brief description of parameters is provided below: 609 610 mode: Specify the size of the waterbox explicitly or calculate it 611 automatically for a macromolecule along with adding padding 612 around ther macromolecule. 613 size: A space delimited triplet of values corresponding to water 614 size in nanometers. It must be specified during 'Size' value of 615 'mode' parameter. 616 padding: Padding around a macromolecule in nanometers for filling 617 box with water. It must be specified during 'Padding' value of 618 'mode' parameter. 619 620 -w, --workingdir <dir> 621 Location of working directory which defaults to the current directory. 622 623 Examples: 624 To prepare a macromolecule in a PDB file by replacing non-standard residues, 625 adding missing residues, adding missing heavy atoms and missing hydrogens, 626 and generate a PDB file, type: 627 628 % OpenMMPrepareMacromolecule.py -i Sample11.pdb -o Sample11Out.pdb 629 630 To run the first example for listing additional details about missing atoms and 631 residues, and generate a PDB file, type: 632 633 % OpenMMPrepareMacromolecule.py --listDetails yes -i Sample11.pdb 634 -o Sample11Out.pdb 635 636 To run the first example for deleting all heterogens including water along 637 with performing all default actions, and generate a PDB file, type: 638 639 % OpenMMPrepareMacromolecule.py --deleteHeterogens All -i Sample11.pdb 640 -o Sample11Out.pdb 641 642 To run the first example for deleting water only heterogens along with performing 643 all default actions, and generate a PDB file, type: 644 645 % OpenMMPrepareMacromolecule.py --deleteHeterogens WaterOnly 646 -i Sample11.pdb -o Sample11Out.pdb --ov 647 648 To run the first example for adding a water box by automatically calculating its 649 size, along with performing all default actions, and generate a PDB file, type: 650 651 % OpenMMPrepareMacromolecule.py --waterBox yes -i Sample11.pdb 652 -o Sample11Out.pdb 653 654 To run the previous example by explcitly specifying various water box parameters, 655 and generate a PDB file, type: 656 657 % OpenMMPrepareMacromolecule.py --waterBox yes 658 --waterBoxParams "mode,Padding, padding, 1.0, shape, cube" 659 -i Sample11.pdb -o Sample11Out.pdb 660 661 To run the first example for adding a water box of a specific size along with 662 performing all default actions, and generate a PDB file, type: 663 664 % OpenMMPrepareMacromolecule.py --waterBox yes 665 --waterBoxParams "mode,Size, size, 7.635 7.077 7.447" 666 -i Sample11.pdb -o Sample11Out.pdb 667 668 To add a lipid membrane around a correctly oriented and positioned macromolecule, 669 along with performing all default actions, and generate a PDB file, type: 670 671 % OpenMMPrepareMacromolecule.py --membrane yes 672 -i Sample12.pdb -o Sample12Out.pdb 673 674 To add a lipid membrane around a correctly oriented and positioned macromolecule, 675 deleting all heterogens along with performing all default actions, and generate a 676 PDB file, type: 677 678 % OpenMMPrepareMacromolecule.py --membrane yes --deleteHeterogens All 679 -i Sample12.pdb -o Sample12Out.pdb 680 681 To run the previous example by explcitly specifying various membrane parameters, 682 and generate a PDB file, type: 683 684 % OpenMMPrepareMacromolecule.py --membrane yes 685 --membraneParams "lipidType, POPC, membraneCenterZ, 0.0, padding, 1.0" 686 -i Sample12.pdb -o Sample12Out.pdb 687 688 Author: 689 Manish Sud(msud@san.rr.com) 690 691 See also: 692 InfoPDBFiles.pl, ExtractFromPDBFiles.pl, PyMOLExtractSelection.py, 693 PyMOLInfoMacromolecules.py, PyMOLSplitChainsAndLigands.py 694 695 Copyright: 696 Copyright (C) 2025 Manish Sud. All rights reserved. 697 698 The functionality available in this script is implemented using OpenMM, an 699 open source molecuar simulation package. 700 701 This file is part of MayaChemTools. 702 703 MayaChemTools is free software; you can redistribute it and/or modify it under 704 the terms of the GNU Lesser General Public License as published by the Free 705 Software Foundation; either version 3 of the License, or (at your option) any 706 later version. 707 708 """ 709 710 if __name__ == "__main__": 711 main()