MayaChemTools

   1 #!/bin/env python
   2 #
   3 # File: OpenFEGenerateLigandNetwork.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 
  37 # OpenFE imports...
  38 try:
  39     import openfe
  40 except ImportError as ErrMsg:
  41     sys.stderr.write("\nFailed to import OpenFE related module/package: %s\n" % ErrMsg)
  42     sys.stderr.write("Check/update your OpenFE environment and try again.\n\n")
  43     sys.exit(1)
  44 
  45 # RDKit imports...
  46 try:
  47     from rdkit import rdBase
  48 except ImportError as ErrMsg:
  49     sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
  50     sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
  51     sys.exit(1)
  52 
  53 # MayaChemTools imports...
  54 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
  55 try:
  56     from docopt import docopt
  57     import MiscUtil
  58     import OpenFEUtil
  59 except ImportError as ErrMsg:
  60     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
  61     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
  62     sys.exit(1)
  63 
  64 ScriptName = os.path.basename(sys.argv[0])
  65 Options = {}
  66 OptionsInfo = {}
  67 
  68 
  69 def main():
  70     """Start execution of the script."""
  71 
  72     MiscUtil.PrintInfo(
  73         "\n%s (OpenFE v%s; OpenMM v%s; RDKit v%s; MayaChemTools v%s; %s): Starting...\n"
  74         % (
  75             ScriptName,
  76             openfe.version("openfe"),
  77             openfe.version("openmm"),
  78             rdBase.rdkitVersion,
  79             MiscUtil.GetMayaChemToolsVersion(),
  80             time.asctime(),
  81         )
  82     )
  83 
  84     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
  85 
  86     # Retrieve command line arguments and options...
  87     RetrieveOptions()
  88 
  89     # Process and validate command line arguments and options...
  90     ProcessOptions()
  91 
  92     # Perform actions required by the script...
  93     GenerateLigandNetwork()
  94 
  95     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
  96     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
  97 
  98 
  99 def GenerateLigandNetwork():
 100     """Generate ligand network for molecules and write them out."""
 101 
 102     # Process ligand molecules...
 103     Mols = ProcessLigandMolecules()
 104 
 105     # Validate central ligand name for radial network...
 106     ValidateRadialCentralLigandName(Mols)
 107 
 108     # Initialize atom mappers...
 109     Mappers = InitializeAtomMappers()
 110 
 111     # Initialize atom scorer...
 112     MapperScorer = InitializeAtomMapperScorer()
 113 
 114     # Generate network...
 115     GenerateNetwork(Mols, Mappers, MapperScorer)
 116 
 117 
 118 def GenerateNetwork(Mols, Mappers, MapperScorer):
 119     """Generate network and write it out."""
 120 
 121     NetworkName = OptionsInfo["Network"]
 122     NetworkParams = OptionsInfo["NetworkParams"]
 123 
 124     MiscUtil.PrintInfo("\nChanging directory to %s..." % OptionsInfo["OutfileDir"])
 125     os.chdir(OptionsInfo["OutfileDirPath"])
 126 
 127     MiscUtil.PrintInfo("\nGenerating ligand network (%s)..." % NetworkName)
 128 
 129     LigandNetwork = OpenFEUtil.GenerateLigandNetwork(Mols, NetworkName, NetworkParams, Mappers, MapperScorer)
 130 
 131     # Write out ligand network graphml and image files...
 132     WriteLigandNetworkOutputFiles(LigandNetwork, NetworkName)
 133 
 134     #  Write out image files for edges...
 135     WriteLigandNetworkEdgesOutputFiles(LigandNetwork, NetworkName)
 136 
 137 
 138 def WriteLigandNetworkOutputFiles(LigandNetwork, NetworkName):
 139     """Write ligand network output files."""
 140 
 141     GraphMLOutfile, ImageOutfile = SetupLigandNetworkOutputFilePaths(NetworkName)
 142 
 143     # Write graphml file...
 144     MiscUtil.PrintInfo("Writing %s..." % GraphMLOutfile)
 145     OpenFEUtil.WriteLigandNetworkGraphMLFile(LigandNetwork, GraphMLOutfile)
 146 
 147     # Write image file...
 148     MiscUtil.PrintInfo("Writing %s..." % ImageOutfile)
 149     OpenFEUtil.WriteLigandNetworkImageFile(LigandNetwork, ImageOutfile)
 150 
 151 
 152 def WriteLigandNetworkEdgesOutputFiles(LigandNetwork, NetworkName):
 153     """Write ligand network edges output files."""
 154 
 155     if not OptionsInfo["NetworkParams"]["OutputEdges"]:
 156         return
 157 
 158     NetworkEdges = [Edge for Edge in LigandNetwork.edges]
 159 
 160     if len(NetworkEdges):
 161         MiscUtil.PrintInfo(
 162             "Writing %s edge output files <MolName1>_To_<MolName2>_*.png to %s subdirectory..."
 163             % (len(NetworkEdges), OptionsInfo["EdgesOutfileDir"])
 164         )
 165 
 166     for Edge in NetworkEdges:
 167         EdgeOutfile = SetupLigandNetworkEdgeOutputFilePath(Edge, NetworkName)
 168         OpenFEUtil.WriteMappingImageFile(Edge, EdgeOutfile)
 169 
 170 
 171 def SetupLigandNetworkOutputFilePaths(NetworkName):
 172     """Setup ligand network output file paths."""
 173 
 174     NetworkOutfilePrefix = "%s_Network_%s_Mapper_%s" % (OptionsInfo["OutfilePrefix"], NetworkName, SetupMapperLabel())
 175 
 176     GraphMLOutfile = "%s.graphml" % NetworkOutfilePrefix
 177     ImageOutfile = "%s.%s" % (NetworkOutfilePrefix, OptionsInfo["NetworkParams"]["OutputNetworkFormat"])
 178 
 179     return (GraphMLOutfile, ImageOutfile)
 180 
 181 
 182 def SetupLigandNetworkEdgeOutputFilePath(Edge, NetworkName):
 183     """Setup ligand network edge outfile file path."""
 184 
 185     EdgeOutfile = "%s_To_%s_Network_%s_Mapper_%s.png" % (
 186         Edge.componentA.name,
 187         Edge.componentB.name,
 188         NetworkName,
 189         SetupMapperLabel(),
 190     )
 191     EdgeOutfile = re.sub(" ", "_", EdgeOutfile)
 192 
 193     EdgeOutfilePath = os.path.join(OptionsInfo["EdgesOutfileDir"], EdgeOutfile)
 194 
 195     return EdgeOutfilePath
 196 
 197 
 198 def SetupMapperLabel():
 199     """Setup mapper label."""
 200 
 201     return "_".join(OptionsInfo["MapperList"])
 202 
 203 
 204 def InitializeAtomMappers():
 205     """Initialize atom mappers.."""
 206 
 207     MiscUtil.PrintInfo("\nInitializing atom mappers (%s)..." % " ".join(OptionsInfo["MapperList"]))
 208     Mappers = OpenFEUtil.InitializeAtomMappers(OptionsInfo["MapperList"], OptionsInfo["MapperParams"])
 209 
 210     return Mappers
 211 
 212 
 213 def InitializeAtomMapperScorer():
 214     """Initialize atom mapper scorer."""
 215 
 216     MiscUtil.PrintInfo("\nInitializing atom mapper scorer (%s)..." % OptionsInfo["MapperScorer"])
 217     MapperScorer = OpenFEUtil.InitializeAtomMapperScorer(OptionsInfo["MapperScorer"])
 218 
 219     return MapperScorer
 220 
 221 
 222 def ProcessLigandMolecules():
 223     """Process ligand molecules."""
 224 
 225     MiscUtil.PrintInfo("\nProcessing file %s..." % OptionsInfo["Infile"])
 226     Mols, MolCount, ValidMolCount = OpenFEUtil.ReadAndValidateMolecules(
 227         OptionsInfo["InfilePath"], **OptionsInfo["InfileParams"]
 228     )
 229 
 230     MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
 231     MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
 232     MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount))
 233 
 234     if ValidMolCount == 0:
 235         MiscUtil.PrintInfo("")
 236         MiscUtil.PrintError("No valid ligand molecules found in input file.\n")
 237 
 238     return Mols
 239 
 240 
 241 def ValidateRadialCentralLigandName(Mols):
 242     """Validate central ligand name for radial network."""
 243 
 244     if not OptionsInfo["RadialNetworkStatus"]:
 245         return
 246 
 247     if not OpenFEUtil.IsMolNamePresent(Mols, OptionsInfo["NetworkParams"]["RadialCentralLigand"]):
 248         MiscUtil.PrintError(
 249             'The value specified, %s, for parameter name, radialCentralLigand, using option "-n, --networkParams" is not valid. Failed to find the specified molecule name in input file.'
 250             % (OptionsInfo["NetworkParams"]["RadialCentralLigand"])
 251         )
 252 
 253     if OpenFEUtil.IsMolNamePresentMultipleTimes(Mols, OptionsInfo["NetworkParams"]["RadialCentralLigand"]):
 254         MiscUtil.PrintError(
 255             'The value specified, %s, for parameter name, radialCentralLigand, using option "-n, --networkParams" is not valid. Found multiple occurrences of the specified molecule name in input file.'
 256             % (OptionsInfo["NetworkParams"]["RadialCentralLigand"])
 257         )
 258 
 259 
 260 def ProcessOutfilePrefixOption():
 261     """Process outfile prefix option."""
 262 
 263     OutfilePrefix = Options["--outfilePrefix"]
 264 
 265     if re.match("^auto$", OutfilePrefix, re.I):
 266         OutfilePrefix = OptionsInfo["InfileRoot"]
 267 
 268     OptionsInfo["OutfilePrefix"] = OutfilePrefix
 269 
 270 
 271 def ProcessOutfileDirOption():
 272     """Process outfile directory Option."""
 273 
 274     OutfileDir = Options["--outfileDir"]
 275     OutfileDirPath = os.path.abspath(OutfileDir)
 276 
 277     if not os.path.exists(OutfileDir):
 278         MiscUtil.PrintInfo("\nCreating output directory %s..." % (OutfileDir))
 279         os.mkdir(OutfileDirPath)
 280 
 281     OptionsInfo["OutfileDir"] = OutfileDir
 282     OptionsInfo["OutfileDirPath"] = OutfileDirPath
 283 
 284     # Setup edges output directory...
 285     EdgesOutfileDir = "EdgeImages"
 286     EdgesOutfileDirPath = os.path.join(OptionsInfo["OutfileDirPath"], EdgesOutfileDir)
 287     if OptionsInfo["NetworkParams"]["OutputEdges"]:
 288         if not os.path.exists(EdgesOutfileDirPath):
 289             os.mkdir(EdgesOutfileDirPath)
 290 
 291     OptionsInfo["EdgesOutfileDir"] = EdgesOutfileDir
 292     OptionsInfo["EdgesOutfileDirPath"] = EdgesOutfileDirPath
 293 
 294 
 295 def ConfigureLogging():
 296     """Configure logging."""
 297 
 298     OptionsInfo["LoggingLevel"] = Options["--loggingLevel"]
 299 
 300     if re.match("^Warning$", OptionsInfo["LoggingLevel"], re.I):
 301         LoggingLevel = logging.WARNING
 302     else:
 303         LoggingLevel = logging.INFO
 304 
 305     logging.basicConfig(format="%(levelname)s: %(message)s", level=LoggingLevel)
 306 
 307 
 308 def ProcessOptions():
 309     """Process and validate command line arguments and options."""
 310 
 311     MiscUtil.PrintInfo("Processing options...")
 312 
 313     # Validate options...
 314     ValidateOptions()
 315 
 316     # Configure logging...
 317     ConfigureLogging()
 318 
 319     OptionsInfo["Infile"] = Options["--infile"]
 320     OptionsInfo["InfilePath"] = os.path.abspath(OptionsInfo["Infile"])
 321 
 322     FileDir, FileName, FileExt = MiscUtil.ParseFileName(OptionsInfo["Infile"])
 323     OptionsInfo["InfileRoot"] = FileName
 324 
 325     OptionsInfo["LoggingLevel"] = Options["--loggingLevel"]
 326 
 327     ParamsDefaultInfoOverride = {"RemoveHydrogens": False}
 328     OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters(
 329         "--infileParams",
 330         Options["--infileParams"],
 331         InfileName=Options["--infile"],
 332         ParamsDefaultInfo=ParamsDefaultInfoOverride,
 333     )
 334 
 335     OptionsInfo["MapperList"] = OpenFEUtil.ProcessOptionOpenFEMapper("-m, --mapper", Options["--mapper"])
 336     OptionsInfo["MapperParams"] = OpenFEUtil.ProcessOptionOpenFEMapperParameters(
 337         "-m, --mapperParams", Options["--mapperParams"]
 338     )
 339     OptionsInfo["MapperScorer"] = Options["--mapperScorer"]
 340 
 341     OptionsInfo["Network"] = OpenFEUtil.ProcessOptionOpenFENetwork("-n, --network", Options["--network"])
 342     OptionsInfo["RadialNetworkStatus"] = True if re.match("^Radial$", OptionsInfo["Network"], re.I) else False
 343     OptionsInfo["NetworkParams"] = OpenFEUtil.ProcessOptionOpenFENetworkParameters(
 344         "--networkParams", Options["--networkParams"], RadialNetworkStatus=OptionsInfo["RadialNetworkStatus"]
 345     )
 346 
 347     ProcessOutfilePrefixOption()
 348     ProcessOutfileDirOption()
 349 
 350     OptionsInfo["Overwrite"] = Options["--overwrite"]
 351 
 352     # Track top level working directory...
 353     OptionsInfo["TopWorkingDir"] = os.getcwd()
 354 
 355 
 356 def RetrieveOptions():
 357     """Retrieve command line arguments and options."""
 358 
 359     # Get options...
 360     global Options
 361     Options = docopt(_docoptUsage_)
 362 
 363     # Set current working directory to the specified directory...
 364     WorkingDir = Options["--workingdir"]
 365     if WorkingDir:
 366         os.chdir(WorkingDir)
 367 
 368     # Handle examples option...
 369     if "--examples" in Options and Options["--examples"]:
 370         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
 371         sys.exit(0)
 372 
 373 
 374 def ValidateOptions():
 375     """Validate option values."""
 376 
 377     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
 378     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd")
 379 
 380     MiscUtil.ValidateOptionDirPath("-o, --outfileDir", Options["--outfileDir"])
 381     MiscUtil.ValidateOptionsOutputDirOverwrite(
 382         "-o, --outfileDir", Options["--outfileDir"], "--overwrite", Options["--overwrite"]
 383     )
 384 
 385     MiscUtil.ValidateOptionTextValue(" --loggingLevel", Options["--loggingLevel"], "Info Warning")
 386 
 387     for Mapper in Options["--mapper"].split(","):
 388         Mapper = Mapper.strip()
 389         MiscUtil.ValidateOptionTextValue("-m, --mapper", Mapper, "LOMAP Kartograf")
 390 
 391     MiscUtil.ValidateOptionTextValue("--mapperScorer", Options["--mapperScorer"], "LOMAP")
 392 
 393     MiscUtil.ValidateOptionTextValue("-n, --network", Options["--network"], "LOMAP MinimalSpanning Radial")
 394 
 395 
 396 # Setup a usage string for docopt...
 397 _docoptUsage_ = """
 398 OpenFEGenerateLigandNetwork.py - Generate ligand network
 399 
 400 Usage:
 401     OpenFEGenerateLigandNetwork.py [--infileParams <Name,Value,...>] [--loggingLevel <Info or Warning>]
 402                                     [--mapper <mapper1, mapper2,...>] [--mapperParams <Name,Value,..>] [--mapperScorer <LOMAP>]
 403                                     [--network <text>] [--networkParams <Name,Value,..>] [--outfilePrefix <text>]
 404                                     [--overwrite] [-w <dir>] -i <infile> -o <outifiledir>
 405     OpenFEGenerateLigandNetwork.py -h | --help | -e | --examples
 406 
 407 Description:
 408     Generate a ligand network for molecules in an input file and write it out
 409     as graphml and image files under the output directory. You may optionally
 410     generate image files for all edges in a ligand network.
 411 
 412     You must specify a valid input file containing 3D coordinates for all
 413     molecules. In addition, the hydrogens must be present for all molecules
 414     in the input file.
 415 
 416     The supported input file format is:  SD (.sdf, .sd)
 417 
 418     The supported output file formats are:  GraphML (.graphml), SVG (.svg),
 419     PNG (.png), etc.
 420 
 421     Possible output directories:
 422         
 423         <OutfileDir>
 424         <OutfileDir>/Edges
 425         
 426     Possible outfile prefixe:
 427         
 428         <OutfilePrefix> or <InfileRoot>
 429         
 430     Possible output files:
 431         
 432         <OutfilePrefix>_Network_<NetworkName>_Mapper_<MapperNames>.graphml
 433         <OutfilePrefix>_Network_<NetworkName>_Mapper_<MapperNames>.<ImgExt>
 434         
 435         Edges:
 436         
 437         <MolName1>_To_<MolName2>_<NetworkName>_Mapper_<MapperNames>.png
 438          ... ... ...
 439 
 440 Options:
 441     -e, --examples
 442         Print examples.
 443     -h, --help
 444         Print this help message.
 445     -i, --infile <infile>
 446         Input file name.
 447     --infileParams <Name,Value,...>  [default: auto]
 448         A comma delimited list of parameter name and value pairs for reading
 449         molecules from files. The supported parameter names for different file
 450         formats, along with their default values, are shown below:
 451             
 452             SD: removeHydrogens,no,sanitize,yes,strictParsing,yes
 453             
 454     --loggingLevel <Info or Warning>  [default: Warning]
 455         Logging level to configure the 'root logger' via logging.basicConfig()
 456         function. The default logging level is changed from 'logging.INFO' to
 457         'logging.WARNING'. Otherwise, OpenFE and its associated modules
 458         may generate a lot of informational messages.
 459     -m, --mapper <mapper1, mapper2>  [default: LOMAP]
 460         A comma delimited names of atom mappers to use for generating a ligand
 461         network. Possible values: LOMAP [ Lead Optimization MAPer; Ref 176 ] or
 462         Kartograf [ Ref 177 ]. You may specify multiple mappers for generating
 463         mapping between two molecules. All specified mappers are employed to
 464         identify the highest scoring edges for generating a ligand network.
 465     --mapperParams <Name,Value,..>  [default: auto]
 466         A comma delimited list of parameter name and value pairs for atom
 467         mappers used during the generation of a ligand network.
 468         
 469         The supported parameter names along with their default values are
 470         are shown below:
 471             
 472             lomapTime, 20, [ Units: seconds ]
 473             lomapThreeD, yes [ Possible values: yes or no ]
 474             lomapMax3D, 1.0 [ Units: Angstrom ]
 475             lomapElementChange, yes [ Possible values: yes or no]
 476             lomapSeed, None [ Possible value: A string. An empty string causes
 477                 MCS search to start from scratch ]
 478             lomapShift, no [  Possible values: yes or no]
 479             
 480             kartografAtomMaxDistance, 0.95 [ Units: Angstrom ]
 481             kartografAtomMapHydrogens, yes [ Possible values: yes or no ]
 482             kartografMapHydrogensOnHydrogensOnly, No [ Possible values: yes or
 483                 no ]
 484             kartografMapExactRingMatchesOnly, yes [ Possible values: yes or no ]
 485             kartografAllowPartialFusedRings, yes [ Possible values: yes or no ]
 486             
 487         A brief description of parameters is provided below:
 488             
 489             lomapTime: Time out for MCS algorithm.
 490             lomapThreeD: Use atom positions to prune symmetric mappings.
 491             lomapMax3D: Forbid mapping between atoms with distance more than
 492                 specified value.
 493             lomapElementChange: Allow mappings that change an atom element.
 494             lomapSeed: An Empty SMARTS string causes MCS search to start from
 495                 scratch.
 496             lomapShift: Keep pre-aligned atom positions for 3D position checks.
 497             
 498             kartografAtomMaxDistance: Geometric criteria for two atoms
 499                 corresponding to maximum distance between them.
 500             kartografAtomMapHydrogens: Map hydrogens.
 501             kartografMapHydrogensOnHydrogensOnly: Map hydrogens only on
 502                 hydrogens.
 503             kartografMapExactRingMatchesOnly: Map rings with only matching ring
 504                 size and bond orders. In addition, ring breaking is not
 505                 permitted.
 506             kartografAllowPartialFusedRings: Allow mapping of partially fused
 507                 rings.
 508             
 509     --mapperScorer <LOMAP>  [default: LOMAP]
 510         Atom mapper scorer to use for scoring edge mappings during generation
 511         of a ligand network. Possible value: LOMAP. The atom scorer is not used
 512         during the generation of MinimalSpanning network.
 513     -n, --network <text>  [default: MinimalSpanning]
 514         Name of a ligand network to generate. Possible values: LOMAP,
 515         MinimalSpanning or Radial. 
 516     --networkParams <Name,Value,..>  [default: auto]
 517         A comma delimited list of parameter name and value pairs for generating
 518         a ligand network.
 519         
 520         The supported parameter names along with their default values are
 521         are shown below:
 522             
 523             lomapDistanceCutoff, 0.4
 524             lomapMaxPathLength, 6
 525             lomapRequireCycleCovering, yes  [ Possible values: yes or no ]
 526             
 527             minimalSpanningProgress, no  [ Possible values: yes or no ]
 528             
 529             radialCentralLigand, None  [ Possible values: Valid ligand name ]
 530             
 531             outputEdges, no  [ Possible values: yes or no ]
 532             outputNetworkFormat, svg  [ Possible values: Any valid format. ]
 533             
 534         A brief description of parameters is provided below:
 535             
 536             lomapDistanceCutoff: Maximum distance/dissimilarity between two
 537                 molecules for an edge to be accepted.
 538             lomapMaxPathLength: Maximum distance between any two molecules in
 539                 the resulting network
 540             lomapRequireCycleCovering: Add cycles into the network
 541             
 542             minimalSpanningProgress: Show progress using tqdm.
 543             
 544             radialCentralLigand: Name of central ligand. A valid ligand name
 545                 must be specified to generate a radial ligand network.
 546             
 547             outputEdges: Generate PNG image files for all edges in a ligand
 548                 network.
 549             outputNetworkFormat: Valid image file format for ligand network.
 550                 You must specify a valid format supported by Python module
 551                 Matplotlib. For example: PNG (.png), SVG (.svg), PDF (.pdf),
 552                 etc. In addition, the graphml file is always generated.
 553             
 554     -o, --outfileDir <outfiledir>
 555         Output directory.
 556     --outfilePrefix <text>  [default: auto]
 557         Prefix for generating output files under output directory.
 558     --overwrite
 559         Overwrite existing files.
 560     -w, --workingdir <dir>
 561         Location of working directory which defaults to the current directory.
 562 
 563 Examples:
 564     To generate a minimal spanning ligand network for molecules in a SD file with
 565     3D structures, employing LOMAP atom mapper to map egdes, and write out
 566     network GraphML, SVG and edge image files to output directory, type:
 567 
 568         % OpenFEGenerateLigandNetwork.py -i SampleTyk2Ligands.sdf
 569           -o SampleTyk2LigandsMSTNetwork
 570 
 571     To run the first example along with generating PNG image files for all edges,
 572     type:
 573 
 574         % OpenFEGenerateLigandNetwork.py -i SampleTyk2Ligands.sdf
 575           -o SampleTyk2LigandsMSTNetwork --networkParams "outputEdges, yes"
 576 
 577     To run the first example employing Kartograf atom mapper to map edges,
 578     and write out various output files under to directory, type:
 579 
 580         % OpenFEGenerateLigandNetwork.py -i SampleTyk2Ligands.sdf
 581           -o SampleTyk2LigandsMSTNetwork -m Kartograf
 582 
 583     To run the first example for generating a radial ligand network centered
 584     around the ligand lig_ejm_31, and write out various output files to output
 585     directory, type:
 586 
 587         % OpenFEGenerateLigandNetwork.py -i SampleTyk2Ligands.sdf
 588           -o SampleTyk2LigandsRadialNetwork -n Radial --networkParams
 589           "RadialCentralLigand,lig_ejm_31"
 590 
 591     To run the previous example along with generating PNG image files for all edges,
 592     type:
 593 
 594         % OpenFEGenerateLigandNetwork.py -i SampleTyk2Ligands.sdf
 595           -o SampleTyk2LigandsRadialNetwork -n Radial --networkParams
 596           "RadialCentralLigand,lig_ejm_31,outputEdges, yes"
 597 
 598     To run the first example by specifying explicit values for various parameters
 599     and write out various output files to output directory, type:
 600 
 601         % OpenFEGenerateLigandNetwork.py -i SampleTyk2Ligands.sdf
 602           -o SampleTyk2LigandsLOMAPNetwork
 603           --loggingLevel Warning -m LOMAP  --mapperParams "lomapTime, 20,
 604           lomapThreeD, yes, lomapElementChange, yes, lomapElementChange, yes,
 605           lomapSeed, None, lomapShift, no" -n LOMAP --networkParams
 606           "lomapDistanceCutoff, 0.4, lomapMaxPathLength, 6,
 607           lomapRequireCycleCovering, yes, outputEdges, yes"
 608 
 609 Author:
 610     Manish Sud(msud@san.rr.com)
 611 
 612 See also:
 613     OpenFECalculateAbsoluteHydrationFreeEnergy.py,
 614     OpenFECalculateRelativeBindingFreeEnergy.py,
 615     OpenFECalculateRelativeHydrationFreeEnergy.py,
 616     OpenFECalculatePartialCharges.py
 617 
 618 Copyright:
 619     Copyright (C) 2026 Manish Sud. All rights reserved.
 620 
 621     The functionality available in this script is implemented using OpenFE, an
 622     open source molecuar for alchemical free energy calculations.
 623 
 624     This file is part of MayaChemTools.
 625 
 626     MayaChemTools is free software; you can redistribute it and/or modify it under
 627     the terms of the GNU Lesser General Public License as published by the Free
 628     Software Foundation; either version 3 of the License, or (at your option) any
 629     later version.
 630 
 631 """
 632 
 633 if __name__ == "__main__":
 634     main()