1 #!/bin/env python
2 #
3 # File: OpenFECalculateRelativeHydrationFreeEnergy.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 OpenFE, an
9 # open source package for alchemical free energy calculations.
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 import logging
36 import pathlib
37 import numpy as np
38 import pandas as pd
39
40 # OpenFE imports...
41 try:
42 import openfe
43 except ImportError as ErrMsg:
44 sys.stderr.write("\nFailed to import OpenFE related module/package: %s\n" % ErrMsg)
45 sys.stderr.write("Check/update your OpenFE environment and try again.\n\n")
46 sys.exit(1)
47
48 # RDKit imports...
49 try:
50 from rdkit import rdBase
51 except ImportError as ErrMsg:
52 sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
53 sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
54 sys.exit(1)
55
56 # MayaChemTools imports...
57 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
58 try:
59 from docopt import docopt
60 import MiscUtil
61 import OpenFEUtil
62 except ImportError as ErrMsg:
63 sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
64 sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
65 sys.exit(1)
66
67 ScriptName = os.path.basename(sys.argv[0])
68 Options = {}
69 OptionsInfo = {}
70
71
72 def main():
73 """Start execution of the script."""
74
75 MiscUtil.PrintInfo(
76 "\n%s (OpenFE v%s; OpenMM v%s; RDKit v%s; MayaChemTools v%s; %s): Starting...\n"
77 % (
78 ScriptName,
79 openfe.version("openfe"),
80 openfe.version("openmm"),
81 rdBase.rdkitVersion,
82 MiscUtil.GetMayaChemToolsVersion(),
83 time.asctime(),
84 )
85 )
86
87 (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
88
89 # Retrieve command line arguments and options...
90 RetrieveOptions()
91
92 if Options["--list"]:
93 ProcessListOption()
94 else:
95 # Process and validate command line arguments and options...
96 ProcessOptions()
97
98 # Perform actions required by the script...
99 CalculateRelativeHydrationFreeEnergy()
100
101 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
102 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
103
104
105 def CalculateRelativeHydrationFreeEnergy():
106 """Calculate relative hydration free energy."""
107
108 # Process input file...
109 Mols = ProcessInputFile()
110
111 # Validate molecule names...
112 ValidateMoleculeNames(Mols)
113
114 # Check for miising partial charges...
115 CheckMissingPartialCharges(Mols)
116
117 # Setup atom mapping...
118 MolAToMolBMappings = GenerateAtomMappings(Mols)
119
120 # Initialize RHFE protocols...
121 RHFEProtocol, RHFEProtocolChargeCorrection, RHFEProtocolVacuum = InitializeRelativeHybridTopologyProtocol()
122
123 # Initialize solvent...
124 Solvent = InitializeSolventComponent()
125
126 # Setup transformations...
127 MolAToMolBTransformations = SetupTransformations(
128 MolAToMolBMappings, Solvent, RHFEProtocol, RHFEProtocolChargeCorrection, RHFEProtocolVacuum
129 )
130
131 # Setup protocol DAGs...
132 MolAToMolBProtocolDAGs = SetupProtocolDAGs(MolAToMolBTransformations)
133
134 # Execute protocol DAGs and gather results...
135 MolAToMolBProtocolResults = ExecuteProtocolDAGsAndGatherResults(MolAToMolBTransformations, MolAToMolBProtocolDAGs)
136
137 # Process protocol results...
138 ProcessProtocolResults(MolAToMolBTransformations, MolAToMolBProtocolResults)
139
140
141 def InitializeRelativeHybridTopologyProtocol():
142 """Initialize relative hybrid toplology protocol."""
143
144 MiscUtil.PrintInfo("\nInitializing relative hybrid topology protocol...")
145
146 RHFESettings = OpenFEUtil.SetupRelativeFreeEnergySettings("-r, --rhfeParams", OptionsInfo["RHFEParams"])
147 RHFEProtocol = OpenFEUtil.InitializeRelativeFreeEngeryHybridTopologyProtocol(RHFESettings)
148
149 RHFESettingsChargeCorrection = OpenFEUtil.SetupRelativeFreeEnergySettings(
150 "-r, --rhfeParams", OptionsInfo["RHFEParams"]
151 )
152 OpenFEUtil.UpdateRelativeFreeEnergySettingsForChargeCorrection(
153 "--rhfeChargeCorrectionParams", OptionsInfo["RHFEChargeCorrectionParams"], RHFESettingsChargeCorrection
154 )
155 RHFEProtocolChargeCorrection = OpenFEUtil.InitializeRelativeFreeEngeryHybridTopologyProtocol(
156 RHFESettingsChargeCorrection
157 )
158
159 RHFESettingsVacuum = OpenFEUtil.SetupRelativeFreeEnergySettings("-r, --rhfeParams", OptionsInfo["RHFEParams"])
160 OpenFEUtil.UpdateRelativeFreeEnergySettingsForVacuum(
161 "--rhfeVacuumParams", OptionsInfo["RHFEVacuumParams"], RHFESettingsVacuum
162 )
163 RHFEProtocolVacuum = OpenFEUtil.InitializeRelativeFreeEngeryHybridTopologyProtocol(RHFESettingsVacuum)
164
165 return (RHFEProtocol, RHFEProtocolChargeCorrection, RHFEProtocolVacuum)
166
167
168 def InitializeSolventComponent():
169 """Initialize solvent component."""
170
171 SolventParams = OptionsInfo["SolventParams"]
172 MiscUtil.PrintInfo(
173 "\nInitializing solvent component (PositiveIon: %s; NegativeIon: %s; Neutralize: %s; IonConcentration: %s)..."
174 % (
175 SolventParams["PositiveIon"],
176 SolventParams["NegativeIon"],
177 SolventParams["Neutralize"],
178 SolventParams["IonConcentration"],
179 )
180 )
181
182 Solvent = OpenFEUtil.InitializeSolventComponent(SolventParams)
183
184 return Solvent
185
186
187 def SetupTransformations(MolAToMolBMappings, Solvent, RHFEProtocol, RHFEProtocolChargeCorrection, RHFEProtocolVacuum):
188 """Set up a transformation pair for each mapping."""
189
190 MiscUtil.PrintInfo("\nSetting up alchemical transformations (Count: %s)..." % (len(MolAToMolBMappings) * 2))
191
192 MolAToMolBTransformations = []
193
194 for MolAToMolBMapping in MolAToMolBMappings:
195 MolA = MolAToMolBMapping.componentA
196 MolB = MolAToMolBMapping.componentB
197
198 # Setup chemical systems...
199 MolASolventSystem = OpenFEUtil.InitializeChemicalSystem(
200 SmallMol=MolA, MacroMol=None, Solvent=Solvent, Name="%s_Solvent" % MolA.name
201 )
202 MolAVacuumSystem = OpenFEUtil.InitializeChemicalSystem(
203 SmallMol=MolA, MacroMol=None, Solvent=None, Name="%s_Vacuum" % MolA.name
204 )
205
206 MolBSolventSystem = OpenFEUtil.InitializeChemicalSystem(
207 SmallMol=MolB, MacroMol=None, Solvent=Solvent, Name="%s_Solvent" % MolB.name
208 )
209 MolBVacuumSystem = OpenFEUtil.InitializeChemicalSystem(
210 SmallMol=MolB, MacroMol=None, Solvent=None, Name="%s_Vacuum" % MolB.name
211 )
212
213 # Setup MolASolvent to MolBSolvent transformation...
214 TransformationProtocol = SetupTransformationProtocol(
215 MolAToMolBMapping, RHFEProtocol, RHFEProtocolChargeCorrection
216 )
217 TransformationName = "%s_To_%s_Solvent" % (MolA.name, MolB.name)
218 MolAToMolBSolventTransformation = OpenFEUtil.InitializeTransformation(
219 StateA=MolASolventSystem,
220 StateB=MolBSolventSystem,
221 Mapping=MolAToMolBMapping,
222 Protocol=TransformationProtocol,
223 Name=TransformationName,
224 Validate=False,
225 )
226 MolAToMolBTransformations.append(MolAToMolBSolventTransformation)
227
228 # Setup MolAVacuum to MolBVacuum transformation...
229 TransformationProtocol = RHFEProtocolVacuum
230 TransformationName = "%s_To_%s_Vacuum" % (MolA.name, MolB.name)
231 MolAToMolBVacuumTransformation = OpenFEUtil.InitializeTransformation(
232 StateA=MolAVacuumSystem,
233 StateB=MolBVacuumSystem,
234 Mapping=MolAToMolBMapping,
235 Protocol=TransformationProtocol,
236 Name=TransformationName,
237 Validate=False,
238 )
239 MolAToMolBTransformations.append(MolAToMolBVacuumTransformation)
240
241 # Write out transformatios...
242 WriteTransformations(MolAToMolBTransformations)
243
244 return MolAToMolBTransformations
245
246
247 def SetupTransformationProtocol(MolAToMolBMapping, RHFEProtocol, RHFEProtocolChargeCorrection):
248 """Setup transformation protocol."""
249
250 from openfe.utils import ligand_utils
251
252 ChargeDifference = ligand_utils.get_alchemical_charge_difference(MolAToMolBMapping)
253
254 if ChargeDifference != 0:
255 MolA = MolAToMolBMapping.componentA
256 MolB = MolAToMolBMapping.componentB
257 if OptionsInfo["RHFEChargeCorrection"]:
258 TransformationProtocol = RHFEProtocolChargeCorrection
259 MiscUtil.PrintInfo("")
260 MiscUtil.PrintWarning(
261 'The transformation between molecules %s and %s involves a charge change of %s. The RHFE setting parameters have been automatically updated for "Yes" value of option "--rhfeChargeCorrection" to employ a more expensive set of parameters specified by option "--rhfeChargeCorrectionParams". '
262 % (MolA.name, MolB.name, ChargeDifference)
263 )
264 else:
265 TransformationProtocol = RHFEProtocol
266 MiscUtil.PrintInfo("")
267 MiscUtil.PrintWarning(
268 'The transformation between molecules %s and %s involves a charge change of %s. The RHFE setting parameters have not been automatically updated for "No" value of option "--rhfeChargeCorrection" to employ more expensive set of parameters specified by option "--rhfeChargeCorrectionParams". A word to the wise: You may want to consider sepecifying "Yes" value for option "--rhfeChargeCorrection".'
269 % (MolA.name, MolB.name, ChargeDifference)
270 )
271 else:
272 TransformationProtocol = RHFEProtocol
273
274 return TransformationProtocol
275
276
277 def WriteTransformations(MolAToMolBTransformations):
278 """Write out transformations."""
279
280 TransformationsOutDirPath = pathlib.Path(OptionsInfo["TransformationsOutDirPath"])
281
282 MiscUtil.PrintInfo(
283 "Writing transformations files (Files: *.json; Count: %s; Subdirectory: %s)..."
284 % (len(MolAToMolBTransformations), OptionsInfo["TransformationsOutDir"])
285 )
286
287 for Transformation in MolAToMolBTransformations:
288 TransformationFilePath = TransformationsOutDirPath.joinpath("%s.json" % Transformation.name)
289 Transformation.dump(TransformationFilePath)
290
291
292 def SetupProtocolDAGs(MolAToMolBTransformations):
293 """Setup protocol Directed Acyclic Graphs (DAGs) for each transformation to
294 to perform calculations.
295 """
296
297 MiscUtil.PrintInfo("\nSetting up protocol DAGs (Count: %s)..." % len(MolAToMolBTransformations))
298
299 MolAToMolBProtocolDAGs = []
300 for Transformation in MolAToMolBTransformations:
301 ProtocolDAG = OpenFEUtil.InitializeProtocolDAG(Transformation, Name=Transformation.name)
302 MolAToMolBProtocolDAGs.append(ProtocolDAG)
303
304 return MolAToMolBProtocolDAGs
305
306
307 def ExecuteProtocolDAGsAndGatherResults(MolTransformations, MolProtocolDAGs):
308 """Execute protocol DAGs and gather results."""
309
310 ResultsSharedOutDirPath = OptionsInfo["ResultsOutDirPath"]
311 ResultsScratchOutDirPath = OptionsInfo["ResultsScratchOutDirPath"]
312 ExecuteDAGParams = OptionsInfo["ExecuteDAGParams"]
313
314 MolProtocolResults = OpenFEUtil.ExecuteProtocolDAGsAndGatherResults(
315 MolTransformations,
316 MolProtocolDAGs,
317 ResultsSharedOutDirPath,
318 ResultsScratchOutDirPath,
319 KeepShared=ExecuteDAGParams["KeepShared"],
320 KeepScratch=ExecuteDAGParams["KeepScratch"],
321 NRetries=ExecuteDAGParams["NRetries"],
322 WriteResults=True,
323 )
324
325 return MolProtocolResults
326
327
328 def ProcessProtocolResults(MolAToMolBTransformations, MolAToMolBProtocolResults):
329 """Process protocol results."""
330
331 ResultFileParams = OptionsInfo["ResultFileParams"]
332
333 ResultFile = "%s_RHFE_Results.%s" % (OptionsInfo["OutfilePrefix"], ResultFileParams["Ext"])
334 ResultFilePath = os.path.join(OptionsInfo["OutfileDirPath"], ResultFile)
335 MiscUtil.PrintInfo("\nWriting %s..." % ResultFile)
336
337 Precision = ResultFileParams["Precision"]
338
339 ResultData = []
340 for Index in range(0, len(MolAToMolBProtocolResults), 2):
341 MolAToMolBSolventProtocolResult = MolAToMolBProtocolResults[Index]
342 MolAToMolBVacuumProtocolResult = MolAToMolBProtocolResults[Index + 1]
343
344 # Setup mol names using solvent transformation. The vacuum transformation
345 # also contains the same pair of moleules.
346 MolAToMolBSolventTransformation = MolAToMolBTransformations[Index]
347
348 MolA = MolAToMolBSolventTransformation.stateA.components["ligand"]
349 MolB = MolAToMolBSolventTransformation.stateB.components["ligand"]
350 MolAName = MolA.name
351 MolBName = MolB.name
352
353 if MolAToMolBSolventProtocolResult is None or MolAToMolBVacuumProtocolResult is None:
354 DeltaDeltaGHydration = "NA"
355 DeltaDeltaGHydrationUncertainty = "NA"
356 else:
357 # Setup hyfration value without the units...
358 MolAToMolBSolventDeltaG = MolAToMolBSolventProtocolResult.get_estimate()
359 MolAToMolBVacuumDeltaG = MolAToMolBVacuumProtocolResult.get_estimate()
360
361 DeltaDeltaGHydration = MolAToMolBSolventDeltaG.m - MolAToMolBVacuumDeltaG.m
362 DeltaDeltaGHydration = "%.*f" % (Precision, DeltaDeltaGHydration)
363
364 # Setup uncertainty value without the units...
365 MolAToMolBSolventDeltaGUncertainty = MolAToMolBSolventProtocolResult.get_uncertainty()
366 MolAToMolBVacuumDeltaGUncertainty = MolAToMolBVacuumProtocolResult.get_uncertainty()
367
368 DeltaDeltaGHydrationUncertainty = np.sqrt(
369 np.sum(np.square([MolAToMolBSolventDeltaGUncertainty.m, MolAToMolBVacuumDeltaGUncertainty.m]))
370 )
371 DeltaDeltaGHydrationUncertainty = "%.*f" % (Precision, DeltaDeltaGHydrationUncertainty)
372
373 ResultData.append([MolAName, MolBName, DeltaDeltaGHydration, DeltaDeltaGHydrationUncertainty])
374
375 ResultDF = pd.DataFrame(
376 ResultData,
377 columns=["MolAName", "MolBName", "DeltaDeltaG (MolA->MolB; RHFE) (kcal/mol)", "Uncertainty (kcal/mol)"],
378 )
379 ResultDF.to_csv(ResultFilePath, sep=ResultFileParams["Delim"], lineterminator="\n", index=False)
380
381
382 def GenerateAtomMappings(Mols):
383 """Generate atom mappings."""
384
385 MiscUtil.PrintInfo("\nChanging directory to %s..." % OptionsInfo["OutfileDir"])
386 os.chdir(OptionsInfo["OutfileDirPath"])
387
388 # Initialize atom mappers...
389 MiscUtil.PrintInfo("\nInitializing atom mappers (%s)..." % " ".join(OptionsInfo["MapperList"]))
390 Mappers = OpenFEUtil.InitializeAtomMappers(OptionsInfo["MapperList"], OptionsInfo["MapperParams"])
391
392 MiscUtil.PrintInfo("\nInitializing atom mapper scorer (%s)..." % OptionsInfo["MapperScorer"])
393 MapperScorer = OpenFEUtil.InitializeAtomMapperScorer(OptionsInfo["MapperScorer"])
394
395 MolAToMolBMappings = None
396 if OptionsInfo["MoleculePairsMode"]:
397 MolAToMolBMappings = GenerateAtomMappingsForMoleculePairs(Mols, Mappers, MapperScorer)
398 elif OptionsInfo["MoleculeNetworkMode"]:
399 MolAToMolBMappings = GenerateAtomMappingForMoleculeNetwork(Mols, Mappers, MapperScorer)
400
401 if MolAToMolBMappings is None or len(MolAToMolBMappings) == 0:
402 MiscUtil.PrintError("Failed to generate atom mappings for small molecules.")
403
404 return MolAToMolBMappings
405
406
407 def GenerateAtomMappingsForMoleculePairs(Mols, Mappers, MapperScorer):
408 """Generate atom mappings for molecule pairs."""
409
410 MiscUtil.PrintInfo(
411 "\nGenerating atom mappings (%s: %d)..." % (OptionsInfo["Mode"], (len(OptionsInfo["MoleculePairsMolList"]) / 2))
412 )
413
414 # Generate atom mappings...
415 MolAToMolBMappings = OpenFEUtil.SuggestAtomMappingsForMoleculePairs(
416 OptionsInfo["MoleculePairsMolList"], Mappers, MapperScorer
417 )
418
419 # Write out image files...
420 WriteMoleculePairsOutputFiles(MolAToMolBMappings)
421
422 return MolAToMolBMappings
423
424
425 def WriteMoleculePairsOutputFiles(MolAToMolBMappings):
426 """Write mapping image output files for molecule pairs."""
427
428 if len(MolAToMolBMappings):
429 MiscUtil.PrintInfo(
430 "Writing molecule pairs output files (Files: <MolName1>_To_<MolName2>_*.png; Count: %s; Subdirectory: %s)..."
431 % (len(MolAToMolBMappings), OptionsInfo["PairImagesOutfileDir"])
432 )
433
434 for Mapping in MolAToMolBMappings:
435 PairOutfilePath = SetupMappingImageFilePath("Molecule_Pair", Mapping, OptionsInfo["PairImagesOutfileDirPath"])
436 OpenFEUtil.WriteMappingImageFile(Mapping, PairOutfilePath)
437
438
439 def GenerateAtomMappingForMoleculeNetwork(Mols, Mappers, MapperScorer):
440 """Setup atom mapping for molecule network."""
441
442 MiscUtil.PrintInfo("\nGenerating atom mappings (%s)..." % OptionsInfo["Mode"])
443
444 # Generate network...
445 LigandNetwork = OpenFEUtil.GenerateLigandNetwork(
446 Mols, OptionsInfo["Network"], OptionsInfo["NetworkParams"], Mappers, MapperScorer
447 )
448
449 # Write out network output files...
450 WriteMoleculeNetworkOutputFiles(LigandNetwork)
451
452 # Setup mappings...
453 MolAToMolBMappings = [Edge for Edge in LigandNetwork.edges]
454
455 return MolAToMolBMappings
456
457
458 def WriteMoleculeNetworkOutputFiles(LigandNetwork):
459 """Write out network output files."""
460
461 NetworkName = OptionsInfo["Network"]
462 MiscUtil.PrintInfo("\nGenerating ligand network (%s)..." % NetworkName)
463
464 # Write out ligand network graphml and image files...
465 NetworkOutfilePrefix = "%s_Network_%s_Mapper_%s" % (OptionsInfo["OutfilePrefix"], NetworkName, SetupMapperLabel())
466 GraphMLOutfile = "%s.graphml" % NetworkOutfilePrefix
467 ImageOutfile = "%s.%s" % (NetworkOutfilePrefix, OptionsInfo["NetworkParams"]["OutputNetworkFormat"])
468
469 GraphMLOutfilePath = os.path.join(OptionsInfo["OutfileDirPath"], GraphMLOutfile)
470 ImageOutfilePath = os.path.join(OptionsInfo["OutfileDirPath"], ImageOutfile)
471
472 MiscUtil.PrintInfo("Writing %s..." % GraphMLOutfile)
473 OpenFEUtil.WriteLigandNetworkGraphMLFile(LigandNetwork, GraphMLOutfilePath)
474
475 MiscUtil.PrintInfo("Writing %s..." % ImageOutfile)
476 OpenFEUtil.WriteLigandNetworkImageFile(LigandNetwork, ImageOutfilePath)
477
478 # Write out image files for edges...
479 NetworkEdges = [Edge for Edge in LigandNetwork.edges]
480 if OptionsInfo["NetworkParams"]["OutputEdges"]:
481 if len(NetworkEdges):
482 MiscUtil.PrintInfo(
483 "Writing edge output files (Files: <MolName1>_To_<MolName2>_*.png; Count: %s; Subdirectory: %s)..."
484 % (len(NetworkEdges), OptionsInfo["EdgeImagesOutfileDir"])
485 )
486
487 for Edge in NetworkEdges:
488 EdgeOutfilePath = SetupMappingImageFilePath("Network", Edge, OptionsInfo["EdgeImagesOutfileDirPath"])
489 OpenFEUtil.WriteMappingImageFile(Edge, EdgeOutfilePath)
490
491
492 def SetupMappingImageFilePath(ModeLabel, Mapping, OutfileDirPath):
493 """Setup mapping image file path."""
494
495 Outfile = "%s_To_%s_%s_Mapper_%s.png" % (
496 Mapping.componentA.name,
497 Mapping.componentB.name,
498 ModeLabel,
499 SetupMapperLabel(),
500 )
501 Outfile = re.sub(" ", "_", Outfile)
502
503 OutfilePath = os.path.join(OutfileDirPath, Outfile)
504
505 return OutfilePath
506
507
508 def SetupMapperLabel():
509 """Setup mapper label."""
510
511 return "_".join(OptionsInfo["MapperList"])
512
513
514 def ProcessInputFile():
515 """Process input file."""
516
517 # Read small molecule input file...
518 MiscUtil.PrintInfo("\nReading small molecule file %s..." % OptionsInfo["Infile"])
519 Mols, MolCount, ValidMolCount = OpenFEUtil.ReadAndValidateMolecules(
520 OptionsInfo["InfilePath"], **OptionsInfo["InfileParams"]
521 )
522
523 MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
524 MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
525 MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount))
526
527 if ValidMolCount == 0:
528 MiscUtil.PrintInfo("")
529 MiscUtil.PrintError("No valid molecules found in small molecule input file.\n")
530
531 if ValidMolCount < 2:
532 MiscUtil.PrintInfo("")
533 MiscUtil.PrintError("Small molecule Input file must contain at least 2 molecules.\n")
534
535 return Mols
536
537
538 def ValidateMoleculeNames(Mols):
539 """Validate molecule names."""
540
541 if OptionsInfo["MoleculePairsMode"]:
542 OptionsInfo["MoleculePairsMolList"] = OpenFEUtil.ProcessMoleculePairs(Mols, OptionsInfo["MoleculePairsList"])
543 elif OptionsInfo["MoleculeNetworkMode"]:
544 if OptionsInfo["RadialNetworkStatus"]:
545 OptionsInfo["NetworkParams"]["RadialCentralLigandMol"] = OpenFEUtil.ProcessRadialCentralLigandName(
546 Mols, OptionsInfo["NetworkParams"]["RadialCentralLigand"]
547 )
548
549
550 def CheckMissingPartialCharges(Mols):
551 """Check missing partial charges for small molecules."""
552
553 MiscUtil.PrintInfo("\nChecking missing partial charges for small molecules...")
554
555 MissingChargesMolCount = OpenFEUtil.GetMissingPartialChargesMolCount(Mols)
556 MiscUtil.PrintInfo("Number of molecules with missing partial charges: %s" % MissingChargesMolCount)
557
558 if MissingChargesMolCount == 0:
559 return
560
561 if re.match("^Stop$", OptionsInfo["MissingChargeMode"], re.I):
562 MiscUtil.PrintInfo("")
563 MiscUtil.PrintError(
564 'The small molecule input file contains molecules with missing partial charges. The execution of the script has been terminated for "Stop" value of "--missingChargedMode" option. You may continue the execution of the script by specifying "Calculate" value for "--missingChargedMode" option.\n\nThe missing charges will be automatically calculated by OpenFE RelativeHybridTopologyProtocol module during the calculation of RHFE. You may control the calculation of partial charges by specifying values for partialCharge* parameters using "--rhfeParams" option. Alternatively, you may employ the OpenFECalculatePartialCharges.py script to calculate partial charges and use the small molecule input file containing charges to calculate RHFE.\n'
565 )
566 else:
567 MiscUtil.PrintInfo("")
568 MiscUtil.PrintWarning(
569 'The small molecule input file contains molecules with missing partial charges. The missing charges will be automatically calculated by OpenFE RelativeHybridTopologyProtocol module during the calculation of RHFE. You may control the calculation of partial charges by specifying values for partialCharge* parameters using "--rhfeParams" option. Alternatively, you may employ the OpenFECalculatePartialCharges.py script to calculate partial charges and use the small molecule input file containing charges to calculate RHFE.\n'
570 )
571
572
573 def ProcessOutfilePrefixOption():
574 """Process outfile prefix option."""
575
576 OutfilePrefix = Options["--outfilePrefix"]
577
578 if re.match("^auto$", OutfilePrefix, re.I):
579 OutfilePrefix = OptionsInfo["InfileRoot"]
580
581 OptionsInfo["OutfilePrefix"] = OutfilePrefix
582
583
584 def ProcessOutfileDirOption():
585 """Process outfile directory Option."""
586
587 # Setup output directory...
588 OutfileDir = Options["--outfileDir"]
589 OutfileDirPath = os.path.abspath(OutfileDir)
590 if not os.path.exists(OutfileDir):
591 MiscUtil.PrintInfo("\nCreating output directory %s..." % (OutfileDir))
592 os.mkdir(OutfileDirPath)
593 OptionsInfo["OutfileDir"] = OutfileDir
594 OptionsInfo["OutfileDirPath"] = OutfileDirPath
595
596 # Setup a images subdirectory for a network...
597 EdgeImagesOutfileDir = "NetworkEdgeImages"
598 EdgeImagesOutfileDirPath = os.path.join(OptionsInfo["OutfileDirPath"], EdgeImagesOutfileDir)
599 if OptionsInfo["MoleculeNetworkMode"] and OptionsInfo["NetworkParams"]["OutputEdges"]:
600 if not os.path.exists(EdgeImagesOutfileDirPath):
601 os.mkdir(EdgeImagesOutfileDirPath)
602 OptionsInfo["EdgeImagesOutfileDir"] = EdgeImagesOutfileDir
603 OptionsInfo["EdgeImagesOutfileDirPath"] = EdgeImagesOutfileDirPath
604
605 # Setup a images subdirectory for molecule pairs...
606 PairImagesOutfileDir = "MoleculePairImages"
607 PairImagesOutfileDirPath = os.path.join(OptionsInfo["OutfileDirPath"], PairImagesOutfileDir)
608 if OptionsInfo["MoleculePairsMode"]:
609 if not os.path.exists(PairImagesOutfileDirPath):
610 os.mkdir(PairImagesOutfileDirPath)
611 OptionsInfo["PairImagesOutfileDir"] = PairImagesOutfileDir
612 OptionsInfo["PairImagesOutfileDirPath"] = PairImagesOutfileDirPath
613
614 # Setup a transformations subdirectory...
615 TransformationsOutDir = "Transformations"
616 TransformationsOutDirPath = os.path.join(OptionsInfo["OutfileDirPath"], TransformationsOutDir)
617 if not os.path.exists(TransformationsOutDirPath):
618 os.mkdir(TransformationsOutDirPath)
619 OptionsInfo["TransformationsOutDir"] = TransformationsOutDir
620 OptionsInfo["TransformationsOutDirPath"] = TransformationsOutDirPath
621
622 # Setup a results subdirectory...
623 ResultsOutDir = "Results"
624 ResultsOutDirPath = os.path.join(OptionsInfo["OutfileDirPath"], ResultsOutDir)
625 if not os.path.exists(ResultsOutDirPath):
626 os.mkdir(ResultsOutDirPath)
627 OptionsInfo["ResultsOutDir"] = ResultsOutDir
628 OptionsInfo["ResultsOutDirPath"] = ResultsOutDirPath
629
630 # Use results subdirectory for scratch results...
631 OptionsInfo["ResultsScratchOutDir"] = ResultsOutDir
632 OptionsInfo["ResultsScratchOutDirPath"] = ResultsOutDirPath
633
634
635 def ProcessListOption():
636 """Process list protocol settings option."""
637
638 RHFESettings = openfe.protocols.openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
639
640 MiscUtil.PrintInfo("\nListing RHFE settings...")
641 OpenFEUtil.ListOpenFESettings(RHFESettings)
642
643
644 def ConfigureLogging():
645 """Configure logging."""
646
647 OptionsInfo["LoggingLevel"] = Options["--loggingLevel"]
648
649 if re.match("^Error$", OptionsInfo["LoggingLevel"], re.I):
650 LoggingLevel = logging.ERROR
651 elif re.match("^Warning$", OptionsInfo["LoggingLevel"], re.I):
652 LoggingLevel = logging.WARNING
653 else:
654 LoggingLevel = logging.INFO
655
656 logging.basicConfig(format="%(levelname)s: %(message)s", level=LoggingLevel)
657
658 # Turn warnings issued by warnings.warn() into log message to avoid display
659 # of a stack trace...
660 logging.captureWarnings(True)
661
662
663 def ProcessOptions():
664 """Process and validate command line arguments and options."""
665
666 MiscUtil.PrintInfo("Processing options...")
667
668 # Validate options...
669 ValidateOptions()
670
671 # Configure logging...
672 ConfigureLogging()
673
674 OptionsInfo["Infile"] = Options["--infile"]
675 OptionsInfo["InfilePath"] = os.path.abspath(OptionsInfo["Infile"])
676 FileDir, FileName, FileExt = MiscUtil.ParseFileName(OptionsInfo["Infile"])
677 OptionsInfo["InfileRoot"] = FileName
678
679 ParamsDefaultInfoOverride = {"RemoveHydrogens": False}
680 OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters(
681 "--infileParams",
682 Options["--infileParams"],
683 InfileName=Options["--infile"],
684 ParamsDefaultInfo=ParamsDefaultInfoOverride,
685 )
686
687 OptionsInfo["ExecuteDAGParams"] = OpenFEUtil.ProcessOptionOpenFEExecuteDAGParameters(
688 "--executeDAGParams", Options["--executeDAGParams"]
689 )
690
691 OptionsInfo["LoggingLevel"] = Options["--loggingLevel"]
692
693 OptionsInfo["MapperList"] = OpenFEUtil.ProcessOptionOpenFEMapper("-m, --mapper", Options["--mapper"])
694 OptionsInfo["MapperParams"] = OpenFEUtil.ProcessOptionOpenFEMapperParameters(
695 "-m, --mapperParams", Options["--mapperParams"]
696 )
697 OptionsInfo["MapperScorer"] = Options["--mapperScorer"]
698
699 OptionsInfo["Mode"] = OpenFEUtil.ProcessOptionOpenFERelativeFreeEnergyMode("-m, --mode", Options["--mode"])
700 OptionsInfo["MoleculePairsMode"] = True if re.match("^MoleculePairs$", OptionsInfo["Mode"], re.I) else False
701 OptionsInfo["MoleculeNetworkMode"] = True if re.match("^MoleculeNetwork$", OptionsInfo["Mode"], re.I) else False
702
703 OptionsInfo["MissingChargeMode"] = OpenFEUtil.ProcessOptionOpenFEMissingChargeMode(
704 "--missingChargeMode", Options["--missingChargeMode"]
705 )
706
707 OptionsInfo["Network"] = OpenFEUtil.ProcessOptionOpenFENetwork("-n, --network", Options["--network"])
708 OptionsInfo["RadialNetworkStatus"] = True if re.match("^Radial$", OptionsInfo["Network"], re.I) else False
709
710 ParamsDefaultInfoOverride = {"OutputEdges": True}
711 OptionsInfo["NetworkParams"] = OpenFEUtil.ProcessOptionOpenFENetworkParameters(
712 "--networkParams",
713 Options["--networkParams"],
714 RadialNetworkStatus=OptionsInfo["RadialNetworkStatus"],
715 ParamsDefaultInfo=ParamsDefaultInfoOverride,
716 )
717
718 OptionsInfo["MoleculePairs"] = Options["--moleculePairs"]
719 OptionsInfo["MoleculePairsList"] = OpenFEUtil.ProcessOptionOpenFEMoleculePairs(
720 "--moleculePairs", Options["--moleculePairs"]
721 )
722 OptionsInfo["MoleculePairsMolList"] = None
723
724 OptionsInfo["ResultFileParams"] = OpenFEUtil.ProcessOptionOpenFEResultFileParameters(
725 "--resultFileParams", Options["--resultFileParams"]
726 )
727
728 ParamsDefaultInfoOverride = {"EngineComputePlatform": "CPU"}
729 OptionsInfo["RHFEParams"] = OpenFEUtil.ProcessOptionOpenFERelativeFreeEnergyParameters(
730 "--rhfeParams", Options["--rhfeParams"], ParamsDefaultInfo=ParamsDefaultInfoOverride
731 )
732
733 OptionsInfo["RHFEChargeCorrection"] = True if re.match("^yes$", Options["--rhfeChargeCorrection"]) else False
734 OptionsInfo["RHFEChargeCorrectionParams"] = (
735 OpenFEUtil.ProcessOptionOpenFERelativeFreeEnergyChargeCorrectionParameters(
736 "--rhfeChargeCorrectionParams", Options["--rhfeChargeCorrectionParams"]
737 )
738 )
739
740 OptionsInfo["RHFEVacuumParams"] = OpenFEUtil.ProcessOptionOpenFERelativeFreeEnergyVacuumParameters(
741 "--rhfeVacuumParams", Options["--rhfeVacuumParams"]
742 )
743
744 OptionsInfo["SolventParams"] = OpenFEUtil.ProcessOptionOpenFESolventParameters(
745 "--solventParams", Options["--solventParams"]
746 )
747
748 ProcessOutfilePrefixOption()
749 ProcessOutfileDirOption()
750
751 OptionsInfo["Overwrite"] = Options["--overwrite"]
752
753 # Track top level working directory...
754 OptionsInfo["TopWorkingDir"] = os.getcwd()
755
756
757 def RetrieveOptions():
758 """Retrieve command line arguments and options."""
759
760 # Get options...
761 global Options
762 Options = docopt(_docoptUsage_)
763
764 # Set current working directory to the specified directory...
765 WorkingDir = Options["--workingdir"]
766 if WorkingDir:
767 os.chdir(WorkingDir)
768
769 # Handle examples option...
770 if "--examples" in Options and Options["--examples"]:
771 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
772 sys.exit(0)
773
774
775 def ValidateOptions():
776 """Validate option values."""
777
778 MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
779 MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd")
780
781 MiscUtil.ValidateOptionDirPath("-o, --outfileDir", Options["--outfileDir"])
782 MiscUtil.ValidateOptionsOutputDirOverwrite(
783 "-o, --outfileDir", Options["--outfileDir"], "--overwrite", Options["--overwrite"]
784 )
785
786 MiscUtil.ValidateOptionTextValue("--loggingLevel", Options["--loggingLevel"], "Info Warning Error")
787
788 for Mapper in Options["--mapper"].split(","):
789 Mapper = Mapper.strip()
790 MiscUtil.ValidateOptionTextValue("--mapper", Mapper, "LOMAP Kartograf")
791
792 MiscUtil.ValidateOptionTextValue("--mapperScorer", Options["--mapperScorer"], "LOMAP")
793
794 MiscUtil.ValidateOptionTextValue("-m, --mode", Options["--mode"], "MoleculePairs MoleculeNetwork")
795 MiscUtil.ValidateOptionTextValue("--missingChargeMode", Options["--missingChargeMode"], "Calculate Stop")
796
797 MoleculePairs = Options["--moleculePairs"]
798 if not re.match("^auto$", MoleculePairs, re.I):
799 MoleculePairsList = MoleculePairs.split(",")
800 if len(MoleculePairsList) % 2:
801 MiscUtil.PrintError(
802 'The number of comma delimited values, %d, specified using "--moleculePairs" option must be an even number.'
803 % (len(MoleculePairsList))
804 )
805
806 MiscUtil.ValidateOptionTextValue("-n, --network", Options["--network"], "LOMAP MinimalSpanning Radial")
807
808 MiscUtil.ValidateOptionTextValue("--rhfeChargeCorrection", Options["--rhfeChargeCorrection"], "yes no")
809
810
811 # Setup a usage string for docopt...
812 _docoptUsage_ = """
813 OpenFECalculateRelativeHydrationFreeEnergy.py - Calculate relative hydration free energy
814
815 Usage:
816 OpenFECalculateRelativeHydrationFreeEnergy.py [--executeDAGParams <Name,Value,..>] [--infileParams <Name,Value,...>]
817 [--loggingLevel <Info, Warning or Error>] [--mapper <mapper1, mapper2,...>] [--mapperParams <Name,Value,..>]
818 [--mapperScorer <LOMAP>] [--mode <MoleculePairs or MoleculeNetwork>] [--missingChargeMode <Calculate or Stop>]
819 [--moleculePairs <MolName1,MolName2,..>] [--network <text>] [--networkParams <Name,Value,..>]
820 [--outfilePrefix <text>] [--overwrite] [--rhfeParams <Name,Value,...>] [--rhfeChargeCorrection <yes or no>]
821 [--rhfeChargeCorrectionParams <Name,Value,...>] [--rhfeVacuumParams <Name,Value,...>]
822 [--resultFileParams <Name,Value,..>] [--solventParams <Name,Value,...>]
823 [-w <dir>] -i <infile> -o <outifiledir>
824 OpenFECalculateRelativeHydrationFreeEnergy.py -l | --list
825 OpenFECalculateRelativeHydrationFreeEnergy.py -h | --help | -e | --examples
826
827 Description:
828 Calculate Relative Hydration Free Energy (RHFE) for a pair of molecules in a
829 small molecule input file. You may calculate RHFEs for specific pairs of
830 molecules or all molecule pairs corresponding to edges in a molecule network.
831
832 The small molecule input file must contain molecules already prepared for
833 simulation. It must contain appropriate 3D coordinates along with no missing
834 hydrogens.
835
836 The MD simulation workflow, employed for the calculation of RHFEs, involves the
837 following steps: initial minimization; NVT equilibration; NPT equilibration;
838 production NPT. The MD simulation protocol is repeated 3 times for each pair
839 pair of transformations, MolAToMolBSolvent and MolAToMolBVacuum,
840 and the results are analyzed to estimate RHFEs. The default time and step size
841 settings for the MD protocol are shown below:
842
843 Protocol repeats, 3
844
845 Time step size: 4.0 femtosecond
846
847 Max minimization steps: 5,000
848
849 NVT equilibration length: 1.0 nanosecond
850 NPT equilibration length: 1.0 nanosecond
851 NPT production length: 5.0 nanosecond
852
853 Each MD simulation, by default, may run for 7 nanosecond, for a total of 21
854 nanosecond to repeat it 3 times. The total MD simulation time for each pair
855 of transformations, MolAToMolBSolvent and MolAToMolBVacuum,
856 may correspond to more than 42 nanoseconds.
857
858 The supported small molecule input file format are : SD (.sdf, .sd)
859
860 Possible outfile prefix:
861
862 <OutfilePrefix> or <InfileRoot>
863
864 Possible output directories:
865
866 <OutfileDir>
867
868 <OutfileDir>/MoleculePairImages [ MoleculeNetwork mode ]
869 <OutfileDir>/NetworkEdgeImages [ MoleculePairs mode]
870
871 <OutfileDir>/Transformations
872 <OutfileDir>/Results
873
874 Possible output files and directories under <OutfileDir>:
875
876 <OutfilePrefix>_RHFE_Results.<csv or tsv>
877
878 MoleculePairImages/<MolAName>_To_<MolBName>_Molecule_Pair*.png
879 ... ... ...
880
881 <OutfilePrefix>_Network*.graphml
882 <OutfilePrefix>_Network*.svg
883 NetworkEdgeImages/<MolAName>_To_<MolBName>_Network*.png
884 ... ... ...
885
886 Transformations/<MolAName>_To_<MolBName>_Solvent.json
887 Transformations/<MolAName>_To_<MolBName>_Vacuum.json
888 ... ... ...
889
890 Results/shared_RelativeHybridTopologyProtocolUnit-*/
891 Results/scratch_RelativeHybridTopologyProtocolUnit-*/
892 ... ... ...
893
894 Options:
895 -e, --examples
896 Print examples.
897 --executeDAGParams <Name,Value,..> [default: auto]
898 A comma delimited list of parameter name and value pairs for executing
899 protocol DAGs (Directed Acyclic Graph) to run RHFE calculations.
900
901 The supported parameter names along with their default values are
902 are shown below:
903
904 keepShared, yes [ Possible values: yes or no ]
905 keepScratch, no [ Possible values: yes or no ]
906 nRetries, 2 [ Possible values: >= 0. A value of 0 implies only
907 1 try. ]
908
909 A brief description of parameters is provided below:
910
911 keepShared: Keep shared directories after the execution of DAG.
912 keepScratch: Keep scratch directories after the execution of DAG.
913 nRetries: Number of times to attempt the execution.
914
915 -h, --help
916 Print this help message.
917 -i, --infile <infile>
918 Input file containing small molecules.
919 --infileParams <Name,Value,...> [default: auto]
920 A comma delimited list of parameter name and value pairs for reading
921 molecules from files. The supported parameter names for different file
922 formats, along with their default values, are shown below:
923
924 SD: removeHydrogens,no,sanitize,yes,strictParsing,yes
925
926 -l, --list
927 List default RHFE protocol settings provided by OpenFE module
928 RelativeHybridTopologyProtocol.
929 --loggingLevel <Info, Warning or Error> [default: Error]
930 Logging level to configure the 'root logger' via logging.basicConfig()
931 function. The default logging level is changed from 'logging.INFO' to
932 'logging.ERROR'. Otherwise, OpenFE and its associated modules
933 may generate a lot of informational messages.
934 --mapper <mapper1, mapper2> [default: LOMAP]
935 A comma delimited names of atom mappers for generating atom mapping
936 corresponding to molecule pairs or edges in a molecule network. Possible
937 values: LOMAP [ Lead Optimization MAPer; Ref 176 ] or Kartograf [ Ref 177 ].
938 You may specify multiple mappers for generating mapping between pair of
939 molecules. All specified mappers are employed to identify the highest
940 scoring mapping between a pair of molecules.
941 --mapperParams <Name,Value,..> [default: auto]
942 A comma delimited list of parameter name and value pairs for atom mappers
943 employed to generate mapping between molecule pairs or edges in a molecule
944 network.
945
946 The supported parameter names along with their default values are
947 are shown below:
948
949 lomapTime, 20, [ Units: seconds ]
950 lomapThreeD, yes [ Possible values: yes or no ]
951 lomapMax3D, 1.0 [ Units: Angstrom ]
952 lomapElementChange, yes [ Possible values: yes or no]
953 lomapSeed, None [ Possible value: A string. An empty string causes
954 MCS search to start from scratch ]
955 lomapShift, no [ Possible values: yes or no]
956
957 kartografAtomMaxDistance, 0.95 [ Units: Angstrom ]
958 kartografAtomMapHydrogens, yes [ Possible values: yes or no ]
959 kartografMapHydrogensOnHydrogensOnly, No [ Possible values: yes or
960 no ]
961 kartografMapExactRingMatchesOnly, yes [ Possible values: yes or no ]
962 kartografAllowPartialFusedRings, yes [ Possible values: yes or no ]
963
964 A brief description of parameters is provided below:
965
966 lomapTime: Time out for MCS algorithm.
967 lomapThreeD: Use atom positions to prune symmetric mappings.
968 lomapMax3D: Forbid mapping between atoms with distance more than
969 specified value.
970 lomapElementChange: Allow mappings that change an atom element.
971 lomapSeed: An Empty SMARTS string causes MCS search to start from
972 scratch.
973 lomapShift: Keep pre-aligned atom positions for 3D position checks.
974
975 kartografAtomMaxDistance: Geometric criteria for two atoms
976 corresponding to maximum distance between them.
977 kartografAtomMapHydrogens: Map hydrogens.
978 kartografMapHydrogensOnHydrogensOnly: Map hydrogens only on
979 hydrogens.
980 kartografMapExactRingMatchesOnly: Map rings with only matching ring
981 size and bond orders. In addition, ring breaking is not
982 permitted.
983 kartografAllowPartialFusedRings: Allow mapping of partially fused
984 rings.
985
986 --mapperScorer <LOMAP> [default: LOMAP]
987 Atom mapper scorer to use for scoring mapping between molecule pairs or
988 edges in a molecule network. Possible value: LOMAP. The atom scorer is
989 not used during the generation of MinimalSpanning network.
990 -m, --mode <MoleculePairs or MoleculeNetwork> [default: MoleculePairs]
991 Calculate RHFEs for specified pairs of molecules or all molecule pairs
992 corresponding to edges in a molecule network.
993 --missingChargeMode <Calculate or Stop> [default: Stop]
994 Calculate missing partial charges for molecules before running RHFE
995 calculations or terminate the execution of the script. The missing
996 partial charges will be automatically calculated by OpenFE module
997 RelativeHybridTopologyProtocol during the calculation of RHFE. You
998 may control the calculation of partial charges by specifying values for
999 partialCharge* parameters using '--rhfeParams' option.
1000 --moleculePairs <MolName1,MolName2,..> [default: auto]
1001 A comma delimited list of molecule name pairs for calculating RHFEs.
1002 Default: the names of the first and second molecule in small molecule
1003 input file. This option is only used during 'MoleculePairs' value for
1004 '-m, --mode' option.
1005 -n, --network <text> [default: MinimalSpanning]
1006 Name of a molecule network to generate for calculating RHFEs. Possible
1007 values: LOMAP, MinimalSpanning or Radial. This option is only used during
1008 'MoleculeNetwork' value for '-m, --mode' option.
1009 --networkParams <Name,Value,..> [default: auto]
1010 A comma delimited list of parameter name and value pairs for generating
1011 a molecule network.
1012
1013 The supported parameter names along with their default values are
1014 are shown below:
1015
1016 lomapDistanceCutoff, 0.4
1017 lomapMaxPathLength, 6
1018 lomapRequireCycleCovering, yes [ Possible values: yes or no ]
1019
1020 minimalSpanningProgress, no [ Possible values: yes or no ]
1021
1022 radialCentralLigand, None [ Possible values: Valid ligand name ]
1023
1024 outputEdges, no [ Possible values: yes or no ]
1025 outputNetworkFormat, svg [ Possible values: Any valid format. ]
1026
1027 A brief description of parameters is provided below:
1028
1029 lomapDistanceCutoff: Maximum distance/dissimilarity between two
1030 molecules for an edge to be accepted.
1031 lomapMaxPathLength: Maximum distance between any two molecules in
1032 the resulting network
1033 lomapRequireCycleCovering: Add cycles into the network
1034
1035 minimalSpanningProgress: Show progress using tqdm.
1036
1037 radialCentralLigand: Name of central ligand. A valid ligand name
1038 must be specified to generate a radial molecule network.
1039
1040 outputEdges: Generate PNG image files for all edges in a molecule
1041 network.
1042 outputNetworkFormat: Valid image file format for molecule network.
1043 You must specify a valid format supported by Python module
1044 Matplotlib. For example: PNG (.png), SVG (.svg), PDF (.pdf),
1045 etc. In addition, the graphml file is always generated.
1046
1047 -o, --outfileDir <outfiledir>
1048 Output directory.
1049 --outfilePrefix <text> [default: auto]
1050 Prefix for generating output files under output directory.
1051 --overwrite
1052 Overwrite existing files.
1053 --resultFileParams <Name,Value,..> [default: auto]
1054 A comma delimited list of parameter name and value pairs for writing
1055 calculated RHFEs values to a results file.
1056
1057 The supported parameter names along with their default values are
1058 are shown below:
1059
1060 precision, 4 [ Possible values: > 0 ]
1061 delimiter, comma [ Possible values: comma or tab ]
1062
1063 -r, --rhfeParams <Name,Value,...> [default: auto]
1064 A comma delimited list of parameter name and value pairs for RHFE protocol
1065 settings employed during the calculation of RHFEs.
1066
1067 The default values are automatically updated to match settings provided by
1068 OpenFE module RelativeHybridTopologyProtocol.
1069
1070 You must specify valid OpenFE values for these parameters. An extensive
1071 validation is not performed.
1072
1073 The supported parameter names along with their default values are
1074 are shown below:
1075
1076 protocolRepeats, 3
1077
1078 Alchemical settings:
1079
1080 alchemicalEndstateDispersionCorrection, no [ Possible values:
1081 yes or no ]
1082 alchemicalExplicitChargeCorrection, no [ Possible values:
1083 yes or no ]
1084 alchemicalExplicitChargeCorrectionCutoff, 0.8 [ Units: nanometer ]
1085 alchemicalSoftcoreLJ, Gapsys [ Possible values: Gapsys or Beutler ]
1086 alchemicalSoftcoreAlpha, 0.85
1087 alchemicalTurnOffCoreUniqueExceptions, no [ Possible values:
1088 yes or no ]
1089 alchemicalUseDispersionCorrection, no [ Possible values: yes or no ]
1090
1091 Engine settings:
1092
1093 engineComputePlatform, CPU [ Possible values: CPU, CUDA, OpenCL,
1094 or Reference ]
1095 engineGpuDeviceIndex, None [ Possible values: 0, 0 1, etc. ]
1096
1097 Forcefield settings:
1098
1099 forcefieldConstraints, HBonds [ Possible values: HBonds, ALLBonds or
1100 HAngles ]
1101 forcefields, amber/ff14SB.xml amber/tip3p_standard.xml
1102 amber/tip3p_HFE_multivalent.xml amber/phosaa10.xml
1103 [ Possible values: A space delimited list of valid names. ]
1104 forcefieldHydrogenMass, 3.0 [ Units: amu ]
1105 forcefieldNonbondedCutoff, 0.9 [ Units: nanometer ]
1106 forcefieldNonbondedMethod, PME [ Possible values: PME or NoCutoff ]
1107 forcefieldRigidWater, yes [ Possible values: yes or no ]
1108 forcefieldSmallMoleculeForcefield, openff-2.1.1 [ Possible value:
1109 A valid forcefield name. ]
1110
1111 Integrator settings:
1112
1113 integratorBarostatFrequency, 25.0 * timestep [ The specified value
1114 is a multiple of integratorTimestep. ]
1115 integratorConstraintTolerance, 1e-06
1116 integratorLangevinCollisionRate, 1.0 [ Units: 1 / picosecond ]
1117 integratorNRestartAttempts, 20
1118 integratorReassignVelocities, no [ Possible values: yes or no ]
1119 integratorRemoveCom, no [ Possible values: yes or no ]
1120 integratorTimestep, 4.0 [ Units: femtosecond ]
1121
1122 Lambda settings:
1123
1124 lambdaFunctions, default [ Possible values: Default, namd, or
1125 quarters ]
1126 lambdaWindows, 11
1127
1128 Output settings:
1129
1130 outputCheckpointInterval, 1.0 [ Units: nanosecond ]
1131 outputCheckpointStorageFilename, checkpoint.chk
1132 outputForcefieldCache, db.json
1133 outputFilename, simulation.nc
1134 outputIndices, not water [ Possible value: Any valid selection. ]
1135 outputStructure, hybrid_system.pdb
1136 outputPositionsWriteFrequency, 100.0 [ Units: picosecond ]
1137 outputVelocitiesWriteFrequency, None [ Possible values: > 0;
1138 Units: picosecond ]
1139
1140 Partial charge settings:
1141
1142 partialChargeNaglModel, None [ Default: Production AM1BCC model for
1143 NAGL; Possible value: Any valid name. ]
1144 partialChargeNumberOfConformers, None [ Possible value: > 0 ]
1145 partialChargeOffToolkitBackend, AmberTools [ Possible values:
1146 AmberTools or RDKit ]
1147 partialChargeMethod, AM1BCC [ Possble values: AM1BCC, Espaloma,
1148 or NAGL ]
1149
1150 Simulation settings:
1151
1152 simulationEarlyTerminationTargetError, 0.0 [ Units:
1153 kilocalorie_per_mole ]
1154 simulationEquilibrationLength, 1.0 [ Units: nanosecond ]
1155 simulationMinimizationSteps, 5000
1156 simulationNReplicas, 11
1157 simulationProductionLength, 5.0 [ Units: nanosecond ]
1158 simulationRealTimeAnalysisInterval, 250.0 [ Units: picosecond ]
1159 simulationRealTimeAnalysisMinimumTime, 500.0 [ Units: picosecond ]
1160 simulationSamplerMethod, repex [ Possible values: repex, sams,
1161 or independent ]
1162 simulationSamsFlatnessCriteria, logZ-flatness [ Possible values:
1163 logZ-flatness, minimum-visits or histogram-flatness ]
1164 simulationSamsGamma0, 1.0
1165 simulationTimePerIteration, 2.5 [ Units: picosecond ]
1166
1167 Solvation settings:
1168
1169 solvationBoxShape, dodecahedron [ Possible values: cube,
1170 dodecahedron, or octahedron ]
1171 solvationBoxSize, None [ Possible value: A triplet of space
1172 X Y Z values; Units: nanometer ]
1173 solvationSolventModel, tip3p [ Possible values: tip3p, spce, tip4pew,
1174 or tip5p ]
1175 solvationSolventPadding, 1.5 [ Units: nanometer ]
1176
1177 Thermo settings:
1178
1179 thermoPh, None [ Possible values: > 0 ]
1180 thermoPressure, 1.0 [ Units: bar ]
1181 thermoRedoxPotential, None [ Possible values: A valid float.
1182 Units: millivolts (mV) ]
1183 thermoTemperature, 298.15 [ Units: kelvin ]
1184
1185 A brief description of parameters, taken from OpenFE documentation, is
1186 provided below:
1187
1188 protocolRepeats: Number of completely independent repeats of the
1189 entire sampling process.
1190
1191 Alchemical settings:
1192
1193 Parameters controlling the creation of the hybrid topology system,
1194 including various parameters ranging from softcore parameters to
1195 whether or not to apply an explicit charge correction for systems
1196 with net charge changes.
1197
1198 alchemicalEndstateDispersionCorrection: Employ extra unsampled
1199 endstate windows for long range correction.
1200 alchemicalExplicitChargeCorrection: Explicitly account for a charge
1201 difference during the alchemical transformation by transforming
1202 a water to a counterion of the opposite charge of the formal
1203 charge difference.
1204 alchemicalExplicitChargeCorrectionCutoff: Minimum distance from the
1205 system solutes from which an alchemical water can be chosen.
1206 alchemicalSoftcoreLJ: Use LJ softcore function as defined by Gapsys
1207 [ Ref 181 ] or Buetler [ Ref 182 ].
1208 alchemicalSoftcoreAlpha: Softcore alpha parameter.
1209 alchemicalTurnOffCoreUniqueExceptions: Turn off interactions for
1210 new exceptions (not just 1,4s) at lambda 0 and old exceptions at
1211 lambda 1 between unique atoms and core atoms.
1212 alchemicalUseDispersionCorrection: Use dispersion correction in the
1213 hybrid topology state.
1214
1215 Engine settings:
1216
1217 Parameters configuring the compute platform used by the OpenMM to
1218 perform the simulation.
1219
1220 engineComputePlatform: Platform to use for running OpenMM MD
1221 calculations.
1222 engineGpuDeviceIndex: Space delimited list of device indices to use
1223 for running OpenMM MD calculations.
1224
1225 Forcefield settings:
1226
1227 Parameters to set up the force field with OpenMM Force Fields,
1228 including the general force fields, the small molecule force field,
1229 the nonbonded method, and the nonbonded cutoff.
1230
1231 forcefieldConstraints: Constraints to use.
1232 forcefields: List of valid forcefield paths for all components
1233 except small molecules.
1234 forcefieldHydrogenMass: Mass to be repartitioned to hydrogens from
1235 neighboring heavy atoms.
1236 forcefieldNonbondedCutoff: Cutoff for short range nonbonded
1237 interactions.
1238 forcefieldNonbondedMethod: Method for treating nonbonded
1239 interactions.
1240 forcefieldRigidWater: Use a rigid water model.
1241 forcefieldSmallMoleculeForcefield: A valid forcefield name to use
1242 for small molecules.
1243
1244 Integrator settings
1245
1246 Parameters controlling the LangevinSplittingDynamicsMove integrator
1247 used for simulation.
1248
1249 integratorBarostatFrequency: Frequency at which volume scaling
1250 changes should be attempted.
1251 integratorConstraintTolerance: Tolerance for constraint solver.
1252 integratorLangevinCollisionRate: Collision frequency.
1253 integratorNRestartAttempts: Number of attempts to restart from
1254 Context in case there are NaNs in the energies after
1255 integration.
1256 integratorReassignVelocities: Reassign velocities from the
1257 Maxwell-Boltzmann distribution at the beginning of each
1258 Monte Carlo move.
1259 integratorRemoveCom: Remove the center of mass motion.
1260 integratorTimestep: Size of the simulation timestep.
1261
1262 Lambda settings:
1263
1264 Lambda protocol parameters, including number of lambda windows and
1265 lambda functions.
1266
1267 lambdaFunctions: Function name to use for alchemical mutation.
1268 lambdaWindows: Number of lambda windows to calculate.
1269
1270 Output settings:
1271
1272 Parameter controlling simulation output, including the frequency to
1273 write a checkpoint file, the selection string for writing selected
1274 coordinates, and the paths to the trajectory and output structure
1275 files.
1276
1277 outputCheckpointInterval: Frequency to write the checkpoint file.
1278 outputCheckpointStorageFilename: Checkpoint filename.
1279 outputForcefieldCache: Filename for caching small molecule residue
1280 templates.
1281 outputFilename: Trajectory filename.
1282 outputIndices: Selection string for selecting coordinates to write.
1283 outputStructure: Hybrid topology structure filename.
1284 outputPositionsWriteFrequency: Frequency for writing positions to
1285 trajectory file.
1286 outputVelocitiesWriteFrequency: Frequency for writing velocities to
1287 trajectory file.
1288
1289 Partial charge settings:
1290
1291 Parameters for automatically assigning missing partial charges to
1292 small molecules, including the partial charge method.
1293
1294 partialChargeNaglModel: Model to use for partial charge assignment.
1295 A value of None implies the use of the latest available
1296 production AM1BCC model.
1297 partialChargeNumberOfConformers: Number of conformers to generate
1298 as part of the partial charge assignment. A value of None
1299 implies the use of the existing conformer.
1300 partialChargeOffToolkitBackend: OpenFF toolkit registry backend to
1301 use for calculating partial charges.
1302 partialChargeMethod: Method to use for calculating partial charges.
1303
1304 Simulation settings:
1305
1306 Parameters controlling the simulation plan and the alchemical
1307 sampler, including the number of minimization steps, lengths of
1308 equilibration and production runs, the sampler method (e.g.
1309 Hamiltonian REPlica EXchange (repex), and the time interval at
1310 which to perform an analysis of the free energies.
1311
1312 simulationEarlyTerminationTargetError: Target error for the real
1313 time analysis measured in kcal/mol. Once the MBAR error of the
1314 free energy is at or below this value, the simulation will be
1315 considered complete. The suggested value of 0.12 has shown to
1316 be effective in both hydration and binding free energy
1317 benchmarks.
1318 simulationEquilibrationLength: Length of the equilibration phase.
1319 The specified value must be divisible by 'integratorTimestep'.
1320 simulationMinimizationSteps: Maximum number of minimization steps
1321 to perform.
1322 simulationNReplicas: Number of replicas to use.
1323 simulationProductionLength: Length of the production phase.
1324 The specified value must be divisible by 'integratorTimestep'.
1325 simulationRealTimeAnalysisInterval: Time interval for performing
1326 analysis of the free energies. At each interval, real time
1327 analysis data will be written to a yaml file named
1328 <outputFileName>_real_time_analysis.yaml. The current error
1329 in the estimate will also be assessed and the simulation will
1330 be terminated when it drops below
1331 'simulationEarlyTerminationTargetError'.
1332 simulationRealTimeAnalysisMinimumTime: Minimum simulation time
1333 after which the real time analysis is performed.
1334 simulationSamplerMethod: Alchemical sampling method to use:
1335 REPEX (Hamiltonian REPlica EXchange), SAMS (Self-Adjusted
1336 Mixture Sampling), or Independent (Independently sampled lambda
1337 windows).
1338 simulationSamsFlatnessCriteria:Method for assessing when to switch
1339 to asymptomatically optimal scheme for SAMS.
1340 simulationSamsGamma0: Initial weight adaptation rate for SAMS.
1341 simulationTimePerIteration: Simulation time between each MCMC move
1342 attempt
1343
1344 Solvation settings:
1345
1346 Solvation parameters for the system, including the solvent model and
1347 the solvent padding.
1348
1349 solvationBoxShape: Shape of the periodic solvent box to create.
1350 solvationBoxSize: Lengths of the unit cell for a solvent box.
1351 solvationSolventModel: Forcefield water model to use during
1352 solvation and defining the model properties.
1353 solvationSolventPadding: Minimum distance from any solute bounding
1354 sphere to the edge of the box.
1355
1356 Thermo settings:
1357
1358 Thermodynamic parameters, including the temperature and the pressure
1359 of the system.
1360
1361 thermoPh: Simulation pH
1362 thermoPressure: Simulation pressure.
1363 thermoRedoxPotential:Simulation redox potential.
1364 thermoTemperature: Simulation temperature.
1365
1366 --rhfeChargeCorrection <yes or no> [default: yes]
1367 Perform automatic charge correction for charge changing transformations
1368 during the calculation of RHFEs. The '--rhfeChargeCorrectionParams' are
1369 used during the automatic charge correction to override the corresponding
1370 values in '-r, --rhfeParams'.
1371 --rhfeChargeCorrectionParams <Name,Value,...> [default: auto]
1372 A comma delimited list of parameter name and value pairs to use for RHFE
1373 protocol settings during explicit charge correction for charge changing
1374 transformation between pair of molecules. These parameters override the
1375 corresponding values in '-r, --rhfeParams'.
1376
1377 The default parameter values for charge changing transformations are based
1378 on the industry benchmarking performed by OpenFE.
1379
1380 The supported parameter names along with their default values are
1381 are shown below:
1382
1383 alchemicalExplicitChargeCorrection, yes [ Possible values: yes or no ]
1384 simulationProductionLength, 20 [ Units: nanosecond ]
1385 simulationNReplicas, 22
1386 lambdaWindows, 22
1387
1388 A brief description of these parameters is available under the corresponding
1389 parameters in the section for '-r, --rhfeParams'.
1390 --rhfeVacuumParams <Name,Value,...> [default: auto]
1391 A comma delimited list of parameter name and value pairs to use for RHFE
1392 protocol settings during transformations in vacuum. These parameters
1393 override the corresponding values in '-r, --rhfeParams'.
1394
1395 The supported parameter names along with their default values are
1396 are shown below:
1397
1398 forcefieldNonbondedMethod, NoCutoff [ Possible values: PME or
1399 NoCutoff ]
1400
1401 A brief description of these parameters is available under the corresponding
1402 parameters in the section for '-r, --rhfeParams'.
1403 --solventParams <Name,Value,...> [default: auto]
1404 A comma delimited list of parameter name and value pairs for solvent
1405 component. You must specify valid OpenFE values. No extensive validation
1406 is performed. These parameters are used in conjunction with solvation*
1407 parameters available through '--rhfeParams' to perform solvation.
1408
1409 The supported parameter names along with their default values are
1410 are shown below:
1411
1412 positiveIon, Na+ [ Possible value: Li+, Na+, K+, Rb+, or Cs+ ]
1413 negativeIon, Cl- [ Possible values: Cl-, Br-, F-, or I- ]
1414 neutralize, yes [ Possible values: yes or no ]
1415 ionConcentration, 0.15 [ Units: molar ]
1416
1417 A brief description of parameters is provided below:
1418
1419 positiveIon, negativeion: Pair of ions used to neutralize and bring
1420 the solvent to required ionic concentration.
1421 neutralize: Neutralize the net charge on the chemical state by the
1422 ions in the solvent component.
1423 ionConcentration: Ionic concentration.
1424
1425 -w, --workingdir <dir>
1426 Location of working directory which defaults to the current directory.
1427
1428 Examples:
1429 The sample protein and ligand files for tyrosine kinase 2 (Tyk2) are
1430 distributed with MayaChemTools and are available in data directory. These
1431 files have been taken from OpenFE distribution for example notebooks. The
1432 AM1BCC partial charges have been calculated for the ligands in SD file to
1433 facilitate calculations. You may review OpenFE tutorial notebooks for the
1434 expected results.
1435
1436 To calculate RHFE for a pair molecules corresponding to the fist and second
1437 molecules in a SD file, performing 3 independent repeats of the entire MD sampling
1438 process to estimate RHFE for a pair of molecules, each MD repeat consisting of
1439 minimization (5,000 steps) followed by NVT and NPT equilibration (1 ns;
1440 250,000 steps) leading to NPT production (5s; 1,250,000 steps) using a step size
1441 of 4 fs, writing out appropriate trajectory and PDB files for each MD repeat
1442 in Results subdirectory under output directory, generating final results file
1443 along with appropriate graph and image files under output directory, type:
1444
1445 % OpenFECalculateRelativeHydrationFreeEnergy.py
1446 -i SampleTyk2Ligands.sdf -o SampleTyk2LigandsRHFE
1447
1448 To run the first example for calculating RHFE for a specific pair molecules
1449 using CUDA platform on your machine to perform MD simulations and generate
1450 various output files, type:
1451
1452 % OpenFECalculateRelativeHydrationFreeEnergy.py
1453 -i SampleTyk2Ligands.sdf -o SampleTyk2LigandsRHFE -m MoleculePairs
1454 --moleculePairs "lig_ejm_31, lig_ejm_47"
1455 --rhfeParams "engineComputePlatform,CUDA"
1456
1457 To run the second example to see all warning messages produced by OpenFE
1458 modules and write various output files, type;
1459
1460 % OpenFECalculateRelativeHydrationFreeEnergy.py
1461 -i SampleTyk2Ligands.sdf -o SampleTyk2LigandsRHFE -m MoleculePairs
1462 --moleculePairs "lig_ejm_31, lig_ejm_47"
1463 --rhfeParams "engineComputePlatform,CUDA"
1464 --loggingLevel Warning
1465
1466 To run the first example for calculating RHFE for all pairs of molecules
1467 corresponding to edges in a molecule network using CUDA platform on your
1468 machine to perform MD simulations and generate various output files, type:
1469
1470 % OpenFECalculateRelativeHydrationFreeEnergy.py
1471 -i SampleTyk2Ligands.sdf -o SampleTyk2LigandsRHFE -m MoleculeNetwork
1472 --network MinimalSpanning
1473 --rhfeParams "engineComputePlatform,CUDA"
1474
1475 To run the first example for calculating RHFE for a specific pair molecules
1476 using CUDA platform on your machine to perform MD simulations, automatically
1477 calculate missing partial charges for molecules, and generate various output
1478 files, type:
1479
1480 % OpenFECalculateRelativeHydrationFreeEnergy.py
1481 -i SampleTyk2LigandsNoCharges.sdf -o SampleTyk2LigandsRHFE
1482 -m MoleculePairs --moleculePairs "lig_ejm_31, lig_ejm_47"
1483 --rhfeParams "engineComputePlatform,CUDA"
1484 --missingChargeMode Calculate
1485
1486 To run the second example by specifying explict values for various parametres
1487 and generate various output files, type:
1488
1489 % OpenFECalculateRelativeHydrationFreeEnergy.py
1490 -i SampleTyk2Ligands.sdf -o SampleTyk2LigandsRHFE -m MoleculePairs
1491 --moleculePairs "lig_ejm_31, lig_ejm_47"
1492 --loggingLevel Error
1493 --executeDAGParams "keepShared, yes, nRetries, 2" --mapper LOMAP
1494 --mapperParams "lomapTime, 20, lomapThreeD, yes"
1495 --missingChargeMode Stop --rhfeParams "protocolRepeats,3,
1496 alchemicalSoftcoreLJ, Gapsys, engineComputePlatform,CUDA,
1497 forcefieldConstraints, HBonds, forcefieldHydrogenMass, 3.0,
1498 forcefieldNonbondedMethod, PME, integratorTimestep, 4.0,
1499 lambdaWindows, 11, outputCheckpointInterval, 250.0,
1500 simulationMinimizationSteps, 5000, simulationEquilibrationLength, 1.0,
1501 simulationProductionLength, 5.0, solvationBoxShape, cube,
1502 solvationSolventPadding, 1.2, thermoPressure, 0.98692327,
1503 thermoTemperature, 298.15"
1504 --solventParams "positiveIon, Na+, negativeIon, Cl-"
1505
1506 Author:
1507 Manish Sud(msud@san.rr.com)
1508
1509 See also:
1510 OpenFECalculateAbsoluteBindingFreeEnergy.py,
1511 OpenFECalculateAbsoluteHydrationFreeEnergy.py, OpenFECalculatePartialCharges.py,
1512 OpenFECalculateRelativeBindingFreeEnergy.py, OpenFEGenerateLigandNetwork.py
1513
1514 Copyright:
1515 Copyright (C) 2026 Manish Sud. All rights reserved.
1516
1517 The functionality available in this script is implemented using OpenFE, an
1518 open source molecuar for alchemical free energy calculations.
1519
1520 This file is part of MayaChemTools.
1521
1522 MayaChemTools is free software; you can redistribute it and/or modify it under
1523 the terms of the GNU Lesser General Public License as published by the Free
1524 Software Foundation; either version 3 of the License, or (at your option) any
1525 later version.
1526
1527 """
1528
1529 if __name__ == "__main__":
1530 main()