MayaChemTools

    1 #!/bin/env python
    2 #
    3 # File: OpenMMPerformMinimization.py
    4 # Author: Manish Sud <msud@san.rr.com>
    5 #
    6 # Copyright (C) 2026 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 import os
   32 import sys
   33 import time
   34 import re
   35 
   36 # OpenMM imports...
   37 try:
   38     import openmm as mm
   39     import openmm.app
   40 except ImportError as ErrMsg:
   41     sys.stderr.write("\nFailed to import OpenMM related module/package: %s\n" % ErrMsg)
   42     sys.stderr.write("Check/update your OpenMM environment and try again.\n\n")
   43     sys.exit(1)
   44 
   45 # MayaChemTools imports...
   46 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
   47 try:
   48     from docopt import docopt
   49     import MiscUtil
   50     import OpenMMUtil
   51 except ImportError as ErrMsg:
   52     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
   53     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
   54     sys.exit(1)
   55 
   56 ScriptName = os.path.basename(sys.argv[0])
   57 Options = {}
   58 OptionsInfo = {}
   59 
   60 
   61 def main():
   62     """Start execution of the script."""
   63 
   64     MiscUtil.PrintInfo(
   65         "\n%s (OpenMM v%s; MayaChemTools v%s; %s): Starting...\n"
   66         % (ScriptName, mm.Platform.getOpenMMVersion(), MiscUtil.GetMayaChemToolsVersion(), time.asctime())
   67     )
   68 
   69     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
   70 
   71     # Retrieve command line arguments and options...
   72     RetrieveOptions()
   73 
   74     # Process and validate command line arguments and options...
   75     ProcessOptions()
   76 
   77     # Perform actions required by the script...
   78     PerformMinimization()
   79 
   80     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
   81     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
   82 
   83 
   84 def PerformMinimization():
   85     """Perform minimization."""
   86 
   87     # Prepare system for simulation...
   88     System, Topology, Positions = PrepareSystem()
   89 
   90     # Freeze and restraint atoms...
   91     FreezeRestraintAtoms(System, Topology, Positions)
   92 
   93     # Setup integrator...
   94     Integrator = SetupIntegrator()
   95 
   96     # Setup simulation...
   97     Simulation = SetupSimulation(System, Integrator, Topology, Positions)
   98 
   99     # Perform energy minimization...
  100     PerformEnergyMinimization(Simulation)
  101 
  102 
  103 def PrepareSystem():
  104     """Prepare system for simulation."""
  105 
  106     System, Topology, Positions = OpenMMUtil.InitializeSystem(
  107         OptionsInfo["Infile"],
  108         OptionsInfo["ForcefieldParams"],
  109         OptionsInfo["SystemParams"],
  110         OptionsInfo["WaterBox"],
  111         OptionsInfo["WaterBoxParams"],
  112         OptionsInfo["SmallMolFile"],
  113         OptionsInfo["SmallMolID"],
  114     )
  115 
  116     # Write out a PDB file for the system...
  117     MiscUtil.PrintInfo("\nWriting PDB file %s..." % OptionsInfo["InitialPDBOutfile"])
  118     OpenMMUtil.WritePDBFile(
  119         OptionsInfo["InitialPDBOutfile"], Topology, Positions, OptionsInfo["OutputParams"]["PDBOutKeepIDs"]
  120     )
  121 
  122     return (System, Topology, Positions)
  123 
  124 
  125 def SetupIntegrator():
  126     """Setup integrator."""
  127 
  128     Integrator = OpenMMUtil.InitializeIntegrator(
  129         OptionsInfo["IntegratorParams"], OptionsInfo["SystemParams"]["ConstraintErrorTolerance"]
  130     )
  131 
  132     return Integrator
  133 
  134 
  135 def SetupSimulation(System, Integrator, Topology, Positions):
  136     """Setup simulation."""
  137 
  138     Simulation = OpenMMUtil.InitializeSimulation(System, Integrator, Topology, Positions, OptionsInfo["PlatformParams"])
  139 
  140     return Simulation
  141 
  142 
  143 def PerformEnergyMinimization(Simulation):
  144     """Perform energy minimization."""
  145 
  146     SimulationParams = OpenMMUtil.SetupSimulationParameters(OptionsInfo["SimulationParams"])
  147 
  148     OutputParams = OptionsInfo["OutputParams"]
  149 
  150     # Setup a local minimization reporter...
  151     MinimizeReporter = None
  152     if OutputParams["MinimizationDataStdout"] or OutputParams["MinimizationDataLog"]:
  153         MinimizeReporter = LocalMinimizationReporter()
  154 
  155     if MinimizeReporter is not None:
  156         MiscUtil.PrintInfo("\nAdding minimization reporters...")
  157         if OutputParams["MinimizationDataLog"]:
  158             MiscUtil.PrintInfo(
  159                 "Adding data log minimization reporter (Steps: %s; File: %s)..."
  160                 % (OutputParams["MinimizationDataSteps"], OutputParams["MinimizationDataLogFile"])
  161             )
  162         if OutputParams["MinimizationDataStdout"]:
  163             MiscUtil.PrintInfo(
  164                 "Adding data stdout minimization reporter (Steps: %s)..." % (OutputParams["MinimizationDataSteps"])
  165             )
  166     else:
  167         MiscUtil.PrintInfo("\nSkipping addition of minimization reporters...")
  168 
  169     MaxSteps = SimulationParams["MinimizationMaxSteps"]
  170 
  171     MaxStepsMsg = "MaxSteps: %s" % ("UntilConverged" if MaxSteps == 0 else MaxSteps)
  172     ToleranceMsg = "Tolerance: %.2f kcal/mol/A (%.2f kjoules/mol/nm)" % (
  173         SimulationParams["MinimizationToleranceInKcal"],
  174         SimulationParams["MinimizationToleranceInJoules"],
  175     )
  176 
  177     MiscUtil.PrintInfo("\nPerforming energy minimization (%s; %s)..." % (MaxStepsMsg, ToleranceMsg))
  178 
  179     if OutputParams["MinimizationDataStdout"]:
  180         HeaderLine = SetupMinimizationDataOutHeaderLine()
  181         print("\n%s" % HeaderLine)
  182 
  183     Simulation.minimizeEnergy(
  184         tolerance=SimulationParams["MinimizationTolerance"], maxIterations=MaxSteps, reporter=MinimizeReporter
  185     )
  186 
  187     if OutputParams["MinimizationDataLog"]:
  188         WriteMinimizationDataLogFile(MinimizeReporter.DataOutValues)
  189 
  190     # Write out minimized structure...
  191     MiscUtil.PrintInfo("\nWriting PDB file %s..." % OptionsInfo["MinimizedPDBOutfile"])
  192     OpenMMUtil.WriteSimulationStatePDBFile(
  193         Simulation, OptionsInfo["MinimizedPDBOutfile"], OptionsInfo["OutputParams"]["PDBOutKeepIDs"]
  194     )
  195 
  196 
  197 class LocalMinimizationReporter(mm.MinimizationReporter):
  198     """Setup a local minimization reporter."""
  199 
  200     (DataSteps, DataOutTypeList, DataOutDelimiter, StdoutStatus) = [None] * 4
  201 
  202     DataOutValues = []
  203     First = True
  204 
  205     def report(self, Iteration, PositonsList, GradientsList, DataStatisticsMap):
  206         """Report and track minimization."""
  207 
  208         if self.First:
  209             # Initialize...
  210             self.DataSteps = OptionsInfo["OutputParams"]["MinimizationDataSteps"]
  211             self.DataOutTypeList = OptionsInfo["OutputParams"]["MinimizationDataOutTypeOpenMMNameList"]
  212             self.DataOutDelimiter = OptionsInfo["OutputParams"]["DataOutDelimiter"]
  213             self.StdoutStatus = True if OptionsInfo["OutputParams"]["MinimizationDataStdout"] else False
  214 
  215             self.First = False
  216 
  217         if Iteration % self.DataSteps == 0:
  218             # Setup data values...
  219             DataValues = []
  220             DataValues.append("%s" % Iteration)
  221             for DataType in self.DataOutTypeList:
  222                 DataValue = "%.4f" % DataStatisticsMap[DataType]
  223                 DataValues.append(DataValue)
  224 
  225             # Track data...
  226             self.DataOutValues.append(DataValues)
  227 
  228             # Print data values...
  229             if self.StdoutStatus:
  230                 print("%s" % self.DataOutDelimiter.join(DataValues))
  231 
  232         # This method must return a bool. You may return true for early termination.
  233         return False
  234 
  235 
  236 def WriteMinimizationDataLogFile(DataOutValues):
  237     """Write minimization data log file."""
  238 
  239     OutputParams = OptionsInfo["OutputParams"]
  240 
  241     Outfile = OutputParams["MinimizationDataLogFile"]
  242     OutDelimiter = OutputParams["DataOutDelimiter"]
  243 
  244     MiscUtil.PrintInfo("\nWriting minimization log file %s..." % Outfile)
  245     OutFH = open(Outfile, "w")
  246 
  247     HeaderLine = SetupMinimizationDataOutHeaderLine()
  248     OutFH.write("%s\n" % HeaderLine)
  249 
  250     for LineWords in DataOutValues:
  251         Line = OutDelimiter.join(LineWords)
  252         OutFH.write("%s\n" % Line)
  253 
  254     OutFH.close()
  255 
  256 
  257 def SetupMinimizationDataOutHeaderLine():
  258     """Setup minimization data output header line."""
  259 
  260     LineWords = ["Iteration"]
  261     for Label in OptionsInfo["OutputParams"]["MinimizationDataOutTypeList"]:
  262         if re.match("^(SystemEnergy|RestraintEnergy)$", Label, re.I):
  263             LineWords.append("%s(kjoules/mol)" % Label)
  264         elif re.match("^RestraintStrength$", Label, re.I):
  265             LineWords.append("%s(kjoules/mol/nm^2)" % Label)
  266         else:
  267             LineWords.append(Label)
  268 
  269     Line = OptionsInfo["OutputParams"]["DataOutDelimiter"].join(LineWords)
  270 
  271     return Line
  272 
  273 
  274 def FreezeRestraintAtoms(System, Topology, Positions):
  275     """Handle freezing and restraining of atoms."""
  276 
  277     FreezeAtomList, RestraintAtomList = OpenMMUtil.ValidateAndFreezeRestraintAtoms(
  278         OptionsInfo["FreezeAtoms"],
  279         OptionsInfo["FreezeAtomsParams"],
  280         OptionsInfo["RestraintAtoms"],
  281         OptionsInfo["RestraintAtomsParams"],
  282         OptionsInfo["RestraintSpringConstant"],
  283         OptionsInfo["SystemParams"],
  284         System,
  285         Topology,
  286         Positions,
  287     )
  288 
  289 
  290 def ProcessOutfilePrefixParameter():
  291     """Process outfile prefix paramater."""
  292 
  293     OutfilePrefix = Options["--outfilePrefix"]
  294 
  295     if not re.match("^auto$", OutfilePrefix, re.I):
  296         OptionsInfo["OutfilePrefix"] = OutfilePrefix
  297         return
  298 
  299     if OptionsInfo["SmallMolFileMode"]:
  300         OutfilePrefix = "%s_%s_Complex" % (OptionsInfo["InfileRoot"], OptionsInfo["SmallMolFileRoot"])
  301     else:
  302         OutfilePrefix = "%s" % (OptionsInfo["InfileRoot"])
  303 
  304     if re.match("^yes$", Options["--waterBox"], re.I):
  305         OutfilePrefix = "%s_Solvated" % (OutfilePrefix)
  306 
  307     OptionsInfo["OutfilePrefix"] = OutfilePrefix
  308 
  309 
  310 def ProcessOutfileNames():
  311     """Process outfile names."""
  312 
  313     OutputParams = OptionsInfo["OutputParams"]
  314 
  315     OutfileParamName = "MinimizationDataLogFile"
  316     OutfileParamValue = OutputParams[OutfileParamName]
  317     if not Options["--overwrite"]:
  318         if os.path.exists(OutfileParamValue):
  319             MiscUtil.PrintError(
  320                 'The file specified, %s, for parameter name, %s, using option "--outfileParams" already exist. Use option "--ov" or "--overwrite" and try again. '
  321                 % (OutfileParamValue, OutfileParamName)
  322             )
  323 
  324     InitialPDBOutfile = "%s_Initial.%s" % (OptionsInfo["OutfilePrefix"], OutputParams["PDBOutfileExt"])
  325     MinimizedPDBOutfile = "%s_Minimized.%s" % (OptionsInfo["OutfilePrefix"], OutputParams["PDBOutfileExt"])
  326     for Outfile in [InitialPDBOutfile, MinimizedPDBOutfile]:
  327         if not Options["--overwrite"]:
  328             if os.path.exists(Outfile):
  329                 MiscUtil.PrintError(
  330                     'The file name, %s, generated using option "--outfilePrefix" already exist. Use option "--ov" or "--overwrite" and try again. '
  331                     % (Outfile)
  332                 )
  333     OptionsInfo["InitialPDBOutfile"] = InitialPDBOutfile
  334     OptionsInfo["MinimizedPDBOutfile"] = MinimizedPDBOutfile
  335 
  336 
  337 def ProcessWaterBoxParameters():
  338     """Process water box parameters."""
  339 
  340     OptionsInfo["WaterBox"] = True if re.match("^yes$", Options["--waterBox"], re.I) else False
  341     OptionsInfo["WaterBoxParams"] = OpenMMUtil.ProcessOptionOpenMMWaterBoxParameters(
  342         "--waterBoxParams", Options["--waterBoxParams"]
  343     )
  344 
  345     if OptionsInfo["WaterBox"]:
  346         if OptionsInfo["ForcefieldParams"]["ImplicitWater"]:
  347             MiscUtil.PrintInfo("")
  348             MiscUtil.PrintWarning(
  349                 'The value, %s, specified using option "--waterBox" may not be valid for the combination of biopolymer and water forcefields, %s and %s, specified using "--forcefieldParams". You may consider using a valid combination of biopolymer and water forcefields for explicit water during the addition of a water box.'
  350                 % (
  351                     Options["--waterBox"],
  352                     OptionsInfo["ForcefieldParams"]["Biopolymer"],
  353                     OptionsInfo["ForcefieldParams"]["Water"],
  354                 )
  355             )
  356 
  357 
  358 def ProcessOptions():
  359     """Process and validate command line arguments and options."""
  360 
  361     MiscUtil.PrintInfo("Processing options...")
  362 
  363     ValidateOptions()
  364 
  365     OptionsInfo["Infile"] = Options["--infile"]
  366     FileDir, FileName, FileExt = MiscUtil.ParseFileName(OptionsInfo["Infile"])
  367     OptionsInfo["InfileRoot"] = FileName
  368 
  369     SmallMolFile = Options["--smallMolFile"]
  370     SmallMolID = Options["--smallMolID"]
  371     SmallMolFileMode = False
  372     SmallMolFileRoot = None
  373     if SmallMolFile is not None:
  374         FileDir, FileName, FileExt = MiscUtil.ParseFileName(SmallMolFile)
  375         SmallMolFileRoot = FileName
  376         SmallMolFileMode = True
  377 
  378     OptionsInfo["SmallMolFile"] = SmallMolFile
  379     OptionsInfo["SmallMolFileRoot"] = SmallMolFileRoot
  380     OptionsInfo["SmallMolFileMode"] = SmallMolFileMode
  381     OptionsInfo["SmallMolID"] = SmallMolID.upper()
  382 
  383     ProcessOutfilePrefixParameter()
  384 
  385     ParamsDefaultInfoOverride = {
  386         "MinimizationDataSteps": 100,
  387         "MinimizationDataStdout": False,
  388         "MinimizationDataLog": True,
  389     }
  390     OptionsInfo["OutputParams"] = OpenMMUtil.ProcessOptionOpenMMOutputParameters(
  391         "--outputParams", Options["--outputParams"], OptionsInfo["OutfilePrefix"], ParamsDefaultInfoOverride
  392     )
  393     ProcessOutfileNames()
  394 
  395     OptionsInfo["ForcefieldParams"] = OpenMMUtil.ProcessOptionOpenMMForcefieldParameters(
  396         "--forcefieldParams", Options["--forcefieldParams"]
  397     )
  398 
  399     OptionsInfo["FreezeAtoms"] = True if re.match("^yes$", Options["--freezeAtoms"], re.I) else False
  400     if OptionsInfo["FreezeAtoms"]:
  401         OptionsInfo["FreezeAtomsParams"] = OpenMMUtil.ProcessOptionOpenMMAtomsSelectionParameters(
  402             "--freezeAtomsParams", Options["--freezeAtomsParams"]
  403         )
  404     else:
  405         OptionsInfo["FreezeAtomsParams"] = None
  406 
  407     ParamsDefaultInfoOverride = {"Name": Options["--platform"], "Threads": 1}
  408     OptionsInfo["PlatformParams"] = OpenMMUtil.ProcessOptionOpenMMPlatformParameters(
  409         "--platformParams", Options["--platformParams"], ParamsDefaultInfoOverride
  410     )
  411 
  412     OptionsInfo["RestraintAtoms"] = True if re.match("^yes$", Options["--restraintAtoms"], re.I) else False
  413     if OptionsInfo["RestraintAtoms"]:
  414         OptionsInfo["RestraintAtomsParams"] = OpenMMUtil.ProcessOptionOpenMMAtomsSelectionParameters(
  415             "--restraintAtomsParams", Options["--restraintAtomsParams"]
  416         )
  417     else:
  418         OptionsInfo["RestraintAtomsParams"] = None
  419     OptionsInfo["RestraintSpringConstant"] = float(Options["--restraintSpringConstant"])
  420 
  421     OptionsInfo["SystemParams"] = OpenMMUtil.ProcessOptionOpenMMSystemParameters(
  422         "--systemParams", Options["--systemParams"]
  423     )
  424 
  425     OptionsInfo["IntegratorParams"] = OpenMMUtil.ProcessOptionOpenMMIntegratorParameters(
  426         "--integratorParams",
  427         Options["--integratorParams"],
  428         HydrogenMassRepartioningStatus=OptionsInfo["SystemParams"]["HydrogenMassRepartioning"],
  429     )
  430 
  431     OptionsInfo["SimulationParams"] = OpenMMUtil.ProcessOptionOpenMMSimulationParameters(
  432         "--simulationParams", Options["--simulationParams"]
  433     )
  434 
  435     ProcessWaterBoxParameters()
  436 
  437     OptionsInfo["Overwrite"] = Options["--overwrite"]
  438 
  439 
  440 def RetrieveOptions():
  441     """Retrieve command line arguments and options."""
  442 
  443     # Get options...
  444     global Options
  445     Options = docopt(_docoptUsage_)
  446 
  447     # Set current working directory to the specified directory...
  448     WorkingDir = Options["--workingdir"]
  449     if WorkingDir:
  450         os.chdir(WorkingDir)
  451 
  452     # Handle examples option...
  453     if "--examples" in Options and Options["--examples"]:
  454         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
  455         sys.exit(0)
  456 
  457 
  458 def ValidateOptions():
  459     """Validate option values."""
  460 
  461     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
  462     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "pdb cif")
  463 
  464     FileDir, FileName, FileExt = MiscUtil.ParseFileName(Options["--infile"])
  465     OutfilePrefix = Options["--outfilePrefix"]
  466     if not re.match("^auto$", OutfilePrefix, re.I):
  467         if re.match("^(%s)$" % OutfilePrefix, FileName, re.I):
  468             MiscUtil.PrintError(
  469                 'The value specified, %s, for option "--outfilePrefix" is not valid. You must specify a value different from, %s, the root of infile name.'
  470                 % (OutfilePrefix, FileName)
  471             )
  472 
  473     if Options["--smallMolFile"] is not None:
  474         MiscUtil.ValidateOptionFilePath("-l, --smallMolFile", Options["--smallMolFile"])
  475         MiscUtil.ValidateOptionFileExt("-l, --smallMolFile", Options["--smallMolFile"], "sd sdf")
  476 
  477     SmallMolID = Options["--smallMolID"]
  478     if len(SmallMolID) != 3:
  479         MiscUtil.PrintError(
  480             'The value specified, %s, for option "--smallMolID" is not valid. You must specify a three letter small molecule ID.'
  481             % (SmallMolID)
  482         )
  483 
  484     MiscUtil.ValidateOptionTextValue("--freezeAtoms", Options["--freezeAtoms"], "yes no")
  485     if re.match("^yes$", Options["--freezeAtoms"], re.I):
  486         if Options["--freezeAtomsParams"] is None:
  487             MiscUtil.PrintError(
  488                 'No value specified for option "--freezeAtomsParams". You must specify valid values during, yes, value for "--freezeAtoms" option.'
  489             )
  490 
  491     MiscUtil.ValidateOptionTextValue("-p, --platform", Options["--platform"], "CPU CUDA OpenCL Reference")
  492 
  493     MiscUtil.ValidateOptionTextValue("--restraintAtoms", Options["--restraintAtoms"], "yes no")
  494     if re.match("^yes$", Options["--restraintAtoms"], re.I):
  495         if Options["--restraintAtomsParams"] is None:
  496             MiscUtil.PrintError(
  497                 'No value specified for option "--restraintAtomsParams". You must specify valid values during, yes, value for "--restraintAtoms" option.'
  498             )
  499 
  500     MiscUtil.ValidateOptionFloatValue("--restraintSpringConstant", Options["--restraintSpringConstant"], {">": 0})
  501 
  502     MiscUtil.ValidateOptionTextValue("--waterBox", Options["--waterBox"], "yes no")
  503 
  504 
  505 # Setup a usage string for docopt...
  506 _docoptUsage_ = """
  507 OpenMMPerformMinimization.py - Perform an energy minimization
  508 
  509 Usage:
  510     OpenMMPerformMinimization.py [--forcefieldParams <Name,Value,..>] [--freezeAtoms <yes or no>]
  511                                  [--freezeAtomsParams <Name,Value,..>] [--integratorParams <Name,Value,..>]
  512                                  [--outputParams <Name,Value,..>] [--outfilePrefix <text>]
  513                                  [--overwrite] [--platform <text>] [--platformParams <Name,Value,..>]
  514                                  [--restraintAtoms <yes or no>]
  515                                  [--restraintAtomsParams <Name,Value,..>] [--restraintSpringConstant <number>]
  516                                  [--simulationParams <Name,Value,..>] [--smallMolFile <SmallMolFile>] [--smallMolID <text>]
  517                                  [--systemParams <Name,Value,..>] [--waterBox <yes or no>]
  518                                  [--waterBoxParams <Name,Value,..>] [-w <dir>] -i <infile>
  519     OpenMMPerformMinimization.py -h | --help | -e | --examples
  520 
  521 Description:
  522     Perform energy minimization for a macromolecule or a macromolecule in a
  523     complex with small molecule. You may optionally add a water box and
  524     freeze/restraint atoms to your system before minimization.
  525 
  526     The input file must contain a macromolecule already prepared for simulation.
  527     The preparation of the macromolecule for a simulation generally involves the
  528     following: identification and replacement non-standard residues; addition of
  529     missing residues; addition of missing heavy atoms; addition of missing
  530     hydrogens; addition of a water box which is optional.
  531 
  532     In addition, the small molecule input file must contain a molecule already
  533     prepared for simulation. It must contain  appropriate 3D coordinates relative
  534     to the macromolecule along with no missing hydrogens.
  535 
  536     The supported macromolecule input file formats are:  PDB (.pdb) and
  537     CIF (.cif)
  538 
  539     The supported small molecule input file format are : SD (.sdf, .sd)
  540 
  541     Possible outfile prefixes:
  542          
  543         <InfileRoot>
  544         <InfileRoot>_Solvated
  545         <InfileRoot>_<SmallMolFileRoot>_Complex
  546         <InfileRoot>_<SmallMolFileRoot>_Complex_Solvated
  547          
  548     Possible output files:
  549          
  550         <OutfilePrefix>_Initial.<pdb or cif>
  551         <OutfilePrefix>_Minimized.<pdb or cif>
  552         <OutfilePrefix>_Minimization.csv
  553          
  554 Options:
  555     -e, --examples
  556         Print examples.
  557     -f, --forcefieldParams <Name,Value,..>  [default: auto]
  558         A comma delimited list of parameter name and value pairs for biopolymer,
  559         water, and small molecule forcefields.
  560         
  561         The supported parameter names along with their default values are
  562         are shown below:
  563             
  564             biopolymer, amber14-all.xml  [ Possible values: Any Valid value ]
  565             smallMolecule, openff-2.2.1  [ Possible values: Any Valid value ]
  566             water, auto  [ Possible values: Any Valid value ]
  567             additional, none [ Possible values: Space delimited list of any
  568                 valid value ]
  569             
  570         Possible biopolymer forcefield values:
  571             
  572             amber14-all.xml, amber99sb.xml, amber99sbildn.xml, amber03.xml,
  573             amber10.xml
  574             charmm36.xml, charmm_polar_2019.xml
  575             amoeba2018.xml
  576         
  577         Possible small molecule forcefield values:
  578             
  579             openff-2.2.1, openff-2.0.0, openff-1.3.1, openff-1.2.1,
  580             openff-1.1.1, openff-1.1.0,...
  581             smirnoff99Frosst-1.1.0, smirnoff99Frosst-1.0.9,...
  582             gaff-2.11, gaff-2.1, gaff-1.81, gaff-1.8, gaff-1.4,...
  583         
  584         The default water forcefield valus is dependent on the type of the
  585         biopolymer forcefield as shown below:
  586             
  587             Amber: amber14/tip3pfb.xml
  588             CHARMM: charmm36/water.xml or None for charmm_polar_2019.xml
  589             Amoeba: None (Explicit)
  590             
  591         Possible water forcefield values:
  592             
  593             amber14/tip3p.xml, amber14/tip3pfb.xml, amber14/spce.xml,
  594             amber14/tip4pew.xml, amber14/tip4pfb.xml,
  595             charmm36/water.xml, charmm36/tip3p-pme-b.xml,
  596             charmm36/tip3p-pme-f.xml, charmm36/spce.xml,
  597             charmm36/tip4pew.xml, charmm36/tip4p2005.xml,
  598             charmm36/tip5p.xml, charmm36/tip5pew.xml,
  599             implicit/obc2.xml, implicit/GBn.xml, implicit/GBn2.xml,
  600             amoeba2018_gk.xml (Implict water)
  601             None (Explicit water for amoeba)
  602         
  603         The additional forcefield value is a space delimited list of any valid
  604         forcefield values and is passed on to the OpenMMForcefields
  605         SystemGenerator along with the specified forcefield  values for
  606         biopolymer, water, and mall molecule. Possible additional forcefield
  607         values are:
  608             
  609             amber14/DNA.OL15.xml amber14/RNA.OL3.xml
  610             amber14/lipid17.xml amber14/GLYCAM_06j-1.xml
  611             ... ... ...
  612             
  613         You may specify any valid forcefield names supported by OpenMM. No
  614         explicit validation is performed.
  615     --freezeAtoms <yes or no>  [default: no]
  616         Freeze atoms during energy minimization. The specified atoms are kept
  617         completely fixed by setting their masses to zero. Their positions do not
  618         change during  energy minimization.
  619     --freezeAtomsParams <Name,Value,..>
  620         A comma delimited list of parameter name and value pairs for freezing
  621         atoms during energy minimization. You must specify these parameters for 'yes'
  622         value of '--freezeAtoms' option.
  623         
  624         The supported parameter names along with their default values are
  625         are shown below:
  626             
  627             selection, none [ Possible values: CAlphaProtein, Ions, Ligand,
  628                 Protein, Residues, or Water ]
  629             selectionSpec, auto [ Possible values: A space delimited list of
  630                 residue names ]
  631             negate, no [ Possible values: yes or no ]
  632             
  633         A brief description of parameters is provided below:
  634             
  635             selection: Atom selection to freeze.
  636             selectionSpec: A space delimited list of residue names for
  637                 selecting atoms to freeze. You must specify its value during
  638                 'Ligand' and 'Protein' value for 'selection'. The default values
  639                 are automatically set for 'CAlphaProtein', 'Ions', 'Protein',
  640                 and 'Water' values of 'selection' as shown below:
  641                 
  642                 CAlphaProtein: List of stadard protein residues from pdbfixer
  643                     for selecting CAlpha atoms.
  644                 Ions: Li Na K Rb Cs Cl Br F I
  645                 Water: HOH
  646                 Protein: List of standard protein residues from pdbfixer.
  647                 
  648             negate: Negate atom selection match to select atoms for freezing.
  649             
  650         In addition, you may specify an explicit space delimited list of residue
  651         names using 'selectionSpec' for any 'selection". The specified residue
  652         names are appended to the appropriate default values during the
  653         selection of atoms for freezing.
  654     -h, --help
  655         Print this help message.
  656     -i, --infile <infile>
  657         Input file name containing a macromolecule.
  658     --integratorParams <Name,Value,..>  [default: auto]
  659         A comma delimited list of parameter name and value pairs for integrator
  660         to setup the system for local energy minimization. No MD simulation is
  661         performed.
  662         
  663         The supported parameter names along with their default values are
  664         are shown below:
  665             
  666             temperature, 300.0 [ Units: kelvin ]
  667             
  668     --outfilePrefix <text>  [default: auto]
  669         File prefix for generating the names of output files. The default value
  670         depends on the names of input files for macromolecule and small molecule
  671         along with the type of statistical ensemble and the nature of the solvation.
  672         
  673         The possible values for outfile prefix are shown below:
  674             
  675             <InfileRoot>
  676             <InfileRoot>_Solvated
  677             <InfileRoot>_<SmallMolFileRoot>_Complex
  678             <InfileRoot>_<SmallMolFileRoot>_Complex_Solvated
  679             
  680     --outputParams <Name,Value,..>  [default: auto]
  681         A comma delimited list of parameter name and value pairs for generating
  682        output during energy minimization..
  683         
  684         The supported parameter names along with their default values are
  685         are shown below:
  686             
  687             minimizationDataSteps, 100
  688             minimizationDataStdout, no  [ Possible values: yes or no ]
  689             minimizationDataLog, yes  [ Possible values: yes or no ]
  690             minimizationDataLogFile, auto  [ Default:
  691                 <OutfilePrefix>_MinimizationOut.csv ]
  692             minimizationDataOutType, auto [ Possible values: A space delimited
  693                 list of valid parameter names.  Default: SystemEnergy
  694                 RestraintEnergy MaxConstraintError.
  695                 Other valid names: RestraintStrength ]
  696             
  697             pdbOutFormat, PDB  [ Possible values: PDB or CIF ]
  698             pdbOutKeepIDs, yes  [ Possible values: yes or no ]
  699             
  700         A brief description of parameters is provided below:
  701             
  702             minimizationDataSteps: Frequency of writing data to stdout
  703                 and log file.
  704             minimizationDataStdout: Write data to stdout.
  705             minimizationDataLog: Write data to log file.
  706             minimizationDataLogFile: Data log fie name.
  707             minimizationDataOutType: Type of data to write to stdout
  708                 and log file.
  709             
  710             pdbOutFormat: Format of output PDB files.
  711             pdbOutKeepIDs: Keep existing chain and residue IDs.
  712             
  713     --overwrite
  714         Overwrite existing files.
  715     -p, --platform <text>  [default: CPU]
  716         Platform to use for running MD simulation. Possible values: CPU, CUDA,
  717        OpenCL, or Reference.
  718     --platformParams <Name,Value,..>  [default: auto]
  719         A comma delimited list of parameter name and value pairs to configure
  720         platform for running energy minimization calculations..
  721         
  722         The supported parameter names along with their default values for
  723         different platforms are shown below:
  724             
  725             CPU:
  726             
  727             threads, 1  [ Possible value: >= 0 or auto.  The value of 'auto'
  728                 or zero implies the use of all available CPUs for threading. ]
  729             
  730             CUDA:
  731             
  732             deviceIndex, auto  [ Possible values: 0, '0 1' etc. ]
  733             deterministicForces, auto [ Possible values: yes or no ]
  734             precision, single  [ Possible values: single, double, or mix ]
  735             tempDirectory, auto [ Possible value: DirName ]
  736             useBlockingSync, auto [ Possible values: yes or no ]
  737             useCpuPme, auto [ Possible values: yes or no ]
  738             
  739             OpenCL:
  740             
  741             deviceIndex, auto  [ Possible values: 0, '0 1' etc. ]
  742             openCLPlatformIndex, auto  [ Possible value: Number]
  743             precision, single  [ Possible values: single, double, or mix ]
  744             useCpuPme, auto [ Possible values: yes or no ]
  745             
  746         A brief description of parameters is provided below:
  747             
  748             CPU:
  749             
  750             threads: Number of threads to use for simulation.
  751             
  752             CUDA:
  753             
  754             deviceIndex: Space delimited list of device indices to use for
  755                 calculations.
  756             deterministicForces: Generate reproducible results at the cost of a
  757                 small decrease in performance.
  758             precision: Number precision to use for calculations.
  759             tempDirectory: Directory name for storing temporary files.
  760             useBlockingSync: Control run-time synchronization between CPU and
  761                 GPU.
  762             useCpuPme: Use CPU-based PME implementation.
  763             
  764             OpenCL:
  765             
  766             deviceIndex: Space delimited list of device indices to use for
  767                 simulation.
  768             openCLPlatformIndex: Platform index to use for calculations.
  769             precision: Number precision to use for calculations.
  770             useCpuPme: Use CPU-based PME implementation.
  771             
  772     --restraintAtoms <yes or no>  [default: no]
  773         Restraint atoms during energy minimization. The motion of specified atoms is
  774         restricted by adding a harmonic force that binds them to their starting
  775         positions. The atoms are not completely fixed unlike freezing of atoms.
  776         Their motion, however, is restricted and they are not able to move far away
  777         from their starting positions during energy minimization.
  778     --restraintAtomsParams <Name,Value,..>
  779         A comma delimited list of parameter name and value pairs for restraining
  780         atoms during energy minimization. You must specify these parameters for 'yes'
  781         value of '--restraintAtoms' option.
  782         
  783         The supported parameter names along with their default values are
  784         are shown below:
  785             
  786             selection, none [ Possible values: CAlphaProtein, Ions, Ligand,
  787                 Protein, Residues, or Water ]
  788             selectionSpec, auto [ Possible values: A space delimited list of
  789                 residue names ]
  790             negate, no [ Possible values: yes or no ]
  791             
  792         A brief description of parameters is provided below:
  793             
  794             selection: Atom selection to restraint.
  795             selectionSpec: A space delimited list of residue names for
  796                 selecting atoms to restraint. You must specify its value during
  797                 'Ligand' and 'Protein' value for 'selection'. The default values
  798                 are automatically set for 'CAlphaProtein', 'Ions', 'Protein',
  799                 and 'Water' values of 'selection' as shown below:
  800                 
  801                 CAlphaProtein: List of stadard protein residues from pdbfixer
  802                     for selecting CAlpha atoms.
  803                 Ions: Li Na K Rb Cs Cl Br F I
  804                 Water: HOH
  805                 Protein: List of standard protein residues from pdbfixer.
  806                 
  807             negate: Negate atom selection match to select atoms for freezing.
  808             
  809         In addition, you may specify an explicit space delimited list of residue
  810         names using 'selectionSpec' for any 'selection". The specified residue
  811         names are appended to the appropriate default values during the
  812         selection of atoms for restraining.
  813     --restraintSpringConstant <number>  [default: 2.5]
  814         Restraint spring constant for applying external restraint force to restraint
  815         atoms relative to their initial positions during 'yes' value of '--restraintAtoms'
  816         option. Default units: kcal/mol/A**2. The default value, 2.5, corresponds to
  817         1046.0 kjoules/mol/nm**2. The default value is automatically converted into
  818         units of kjoules/mol/nm**2 before its usage.
  819     --simulationParams <Name,Value,..>  [default: auto]
  820         A comma delimited list of parameter name and value pairs for simulation.
  821         
  822         The supported parameter names along with their default values are
  823         are shown below:
  824             
  825             minimizationMaxSteps, auto  [ Possible values: >= 0. The value of
  826                 zero implies until the minimization is converged. ]
  827             minimizationTolerance, 0.24  [ Units: kcal/mol/A. The default value
  828                 0.24, corresponds to OpenMM default of value of 10.04
  829                 kjoules/mol/nm. It is automatically converted into OpenMM
  830                 default units before its usage. ]
  831             
  832         A brief description of parameters is provided below:
  833             
  834             minimizationMaxSteps: Maximum number of minimization steps. The
  835                 value of zero implies until the minimization is converged.
  836             minimizationTolerance: Energy convergence tolerance during
  837                 minimization.
  838             
  839     -s, --smallMolFile <SmallMolFile>
  840         Small molecule input file name. The macromolecue and small molecule are
  841         merged for simulation and the complex is written out to a PDB file.
  842     --smallMolID <text>  [default: LIG]
  843         Three letter small molecule residue ID. The small molecule ID corresponds
  844         to the residue name of the small molecule and is written out to a PDB file
  845         containing the complex.
  846     --systemParams <Name,Value,..>  [default: auto]
  847         A comma delimited list of parameter name and value pairs to configure
  848         a system for energy minimization. No MD simulation is performed.
  849         
  850         The supported parameter names along with their default values are
  851         are shown below:
  852             
  853             constraints, BondsInvolvingHydrogens [ Possible values: None,
  854                 WaterOnly, BondsInvolvingHydrogens, AllBonds, or
  855                 AnglesInvolvingHydrogens ]
  856             constraintErrorTolerance, 0.000001
  857             ewaldErrorTolerance, 0.0005
  858             
  859             nonbondedMethodPeriodic, PME [ Possible values: NoCutoff,
  860                 CutoffNonPeriodic, or PME ]
  861             nonbondedMethodNonPeriodic, NoCutoff [ Possible values:
  862                 NoCutoff or CutoffNonPeriodic]
  863             nonbondedCutoff, 1.0 [ Units: nm ]
  864             
  865             hydrogenMassRepartioning, yes [ Possible values: yes or no ]
  866             hydrogenMass, 1.5 [ Units: amu]
  867             
  868             removeCMMotion, yes [ Possible values: yes or no ]
  869             rigidWater, auto [ Possible values: yes or no. Default: 'No' for
  870                 'None' value of constraints; Otherwise, yes ]
  871             
  872         A brief description of parameters is provided below:
  873             
  874             constraints: Type of system constraints to use for simulation. These
  875                 constraints are different from freezing and restraining of any
  876                 atoms in the system.
  877             constraintErrorTolerance: Distance tolerance for constraints as a
  878                 fraction of the constrained distance.
  879             ewaldErrorTolerance: Ewald error tolerance for a periodic system.
  880             
  881             nonbondedMethodPeriodic: Nonbonded method to use during the
  882                 calculation of long range interactions for a periodic system.
  883             nonbondedMethodNonPeriodic: Nonbonded method to use during the
  884                 calculation of long range interactions for a non-periodic system.
  885             nonbondedCutoff: Cutoff distance to use for long range interactions
  886                 in both perioidic non-periodic systems.
  887             
  888             hydrogenMassRepartioning: Use hydrogen mass repartioning. It
  889                 increases the mass of the hydrogen atoms attached to the heavy
  890                 atoms and decreasing the mass of the bonded heavy atom to
  891                 maintain constant system mass. This allows the use of larger
  892                 integration step size (4 fs) during a simulation.
  893             hydrogenMass: Hydrogen mass to use during repartioning.
  894             
  895             removeCMMotion: Remove all center of mass motion at every time step.
  896             rigidWater: Keep water rigid during a simulation. This is determined
  897                 automatically based on the value of 'constraints' parameter.
  898             
  899     --waterBox <yes or no>  [default: no]
  900         Add water box.
  901     --waterBoxParams <Name,Value,..>  [default: auto]
  902         A comma delimited list of parameter name and value pairs for adding
  903         a water box.
  904         
  905         The supported parameter names along with their default values are
  906         are shown below:
  907             
  908             model, tip3p [ Possible values: tip3p, spce, tip4pew, tip5p or
  909                 swm4ndp ]
  910             mode, Padding  [ Possible values: Size or Padding ]
  911             padding, 1.0
  912             size, None  [ Possible value: xsize ysize zsize ]
  913             shape, cube  [ Possible values: cube, dodecahedron, or octahedron ]
  914             ionPositive, Na+ [ Possible values: Li+, Na+, K+, Rb+, or Cs+ ]
  915             ionNegative, Cl- [ Possible values: Cl-, Br-, F-, or I- ]
  916             ionicStrength, 0.0
  917             
  918         A brief description of parameters is provided below:
  919             
  920             model: Water model to use for adding water box. The van der
  921                 Waals radii and atomic charges are determined using the
  922                 specified water forcefield. You must specify an appropriate
  923                 water forcefield. No validation is performed.
  924             mode: Specify the size of the waterbox explicitly or calculate it
  925                 automatically for a macromolecule along with adding padding
  926                 around ther macromolecule.
  927             padding: Padding around a macromolecule in nanometers for filling
  928                 box with water. It must be specified during 'Padding' value of
  929                 'mode' parameter.
  930             size: A space delimited triplet of values corresponding to water
  931                 size in nanometers. It must be specified during 'Size' value of
  932                 'mode' parameter.
  933             ionPositive: Type of positive ion to add during the addition of a
  934                 water box.
  935             ionNegative: Type of negative ion to add during the addition of a
  936                 water box.
  937             ionicStrength: Total concentration of both positive and negative
  938                 ions to add excluding the ions added to neutralize the system
  939                 during the addition of a water box.
  940             
  941     -w, --workingdir <dir>
  942         Location of working directory which defaults to the current directory.
  943 
  944 Examples:
  945     To perform energy minimization for a macromolecule in a PDB file until the
  946     energy is converged, writing information to log file every 100 steps and 
  947     generate a PDB file for the minimized system, type:
  948 
  949         % OpenMMPerformMinimization.py -i Sample13.pdb
  950 
  951     To run the first example for writing information to both stdout and log file
  952      every 100 steps and generate various output files, type:
  953 
  954         % OpenMMPerformMinimization.py -i Sample13.pdb --outputParams
  955           "minimizationDataStdout, yes"
  956 
  957     To run the first example for performing OpenMM simulation using multi-
  958     threading employing all available CPUs on your machine and generate various
  959     output files, type:
  960 
  961         % OpenMMPerformMinimization.py -i Sample13.pdb
  962           --platformParams "threads,0"
  963 
  964     To run the first example for performing OpenMM simulation using CUDA platform
  965     on your machine and generate various output files, type:
  966 
  967         % OpenMMPerformMinimization.py -i Sample13.pdb -p CUDA
  968 
  969     To run the second example for a marcomolecule in a complex with a small
  970     molecule and generate various output files, type:
  971 
  972         % OpenMMPerformMinimization.py -i Sample13.pdb -s Sample13Ligand.sdf
  973           --platformParams "threads,0"
  974 
  975     To run the second example by adding a water box to the system and generate
  976     various output files, type:
  977 
  978         % OpenMMPerformMinimization.py -i Sample13.pdb --waterBox yes
  979           --platformParams "threads,0"
  980 
  981     To run the second example for a marcomolecule in a complex with a small
  982     molecule by adding a water box to the system and generate various output
  983     files, type:
  984 
  985         % OpenMMPerformMinimization.py -i Sample13.pdb -s Sample13Ligand.sdf
  986           --waterBox yes --platformParams "threads,0"
  987 
  988     To run the second example by freezing CAlpha atoms in a macromolecule without
  989     using any system constraints to avoid any issues with the freezing of the same atoms
  990     and generate various output files, type:
  991 
  992         % OpenMMPerformMinimization.py -i Sample13.pdb
  993           --freezeAtoms yes --freezeAtomsParams "selection,CAlphaProtein"
  994           --systemParams "constraints, None"
  995           --platformParams "threads,0"
  996 
  997         % OpenMMPerformMinimization.py -i Sample13.pdb
  998           --freezeAtoms yes --freezeAtomsParams "selection,CAlphaProtein"
  999           --systemParams "constraints, None"
 1000           --platformParams "threads,0" --waterBox yes
 1001 
 1002     To run the second example by restrainting CAlpha atoms in a macromolecule and
 1003     and generate various output files, type:
 1004 
 1005         % OpenMMPerformMinimization.py -i Sample13.pdb
 1006           --restraintAtomsParams "selection,CAlphaProtein"
 1007           --platformParams "threads,0"
 1008 
 1009         % OpenMMPerformMinimization.py -i Sample13.pdb
 1010           --restraintAtomsParams "selection,CAlphaProtein"
 1011           --platformParams "threads,0"
 1012           --waterBox yes
 1013 
 1014     To run the second example by specifying explict values for various parametres
 1015     and generate various output files, type:
 1016 
 1017         % OpenMMPerformMinimization.py -i Sample13.pdb
 1018           -f " biopolymer,amber14-all.xml,smallMolecule, openff-2.2.1,
 1019           water,amber14/tip3pfb.xml" --waterBox yes
 1020           --outputParams "minimizationDataSteps, 100, minimizationDataStdout,
 1021           yes,minimizationDataLog,yes"
 1022           -p CPU --platformParams "threads,0"
 1023           --simulationParams "minimizationMaxSteps,500,
 1024           minimizationTolerance, 0.24"
 1025           --systemParams "constraints,BondsInvolvingHydrogens,
 1026           nonbondedMethodPeriodic,PME,nonbondedMethodNonPeriodic,NoCutoff,
 1027           hydrogenMassRepartioning, yes"
 1028 
 1029 Author:
 1030     Manish Sud(msud@san.rr.com)
 1031 
 1032 See also:
 1033     OpenMMPrepareMacromolecule.py, OpenMMPerformMDSimulation.py,
 1034     OpenMMPerformSimulatedAnnealing.py
 1035 
 1036 Copyright:
 1037     Copyright (C) 2026 Manish Sud. All rights reserved.
 1038 
 1039     The functionality available in this script is implemented using OpenMM, an
 1040     open source molecuar simulation package.
 1041 
 1042     This file is part of MayaChemTools.
 1043 
 1044     MayaChemTools is free software; you can redistribute it and/or modify it under
 1045     the terms of the GNU Lesser General Public License as published by the Free
 1046     Software Foundation; either version 3 of the License, or (at your option) any
 1047     later version.
 1048 
 1049 """
 1050 
 1051 if __name__ == "__main__":
 1052     main()