MayaChemTools

    1 #!/bin/env python
    2 # File: RDKitUtil.py
    3 # Author: Manish Sud <msud@san.rr.com>
    4 #
    5 # Copyright (C) 2024 Manish Sud. All rights reserved.
    6 #
    7 # The functionality available in this file is implemented using RDKit, an
    8 # open source toolkit for cheminformatics developed by Greg Landrum.
    9 #
   10 # This file is part of MayaChemTools.
   11 #
   12 # MayaChemTools is free software; you can redistribute it and/or modify it under
   13 # the terms of the GNU Lesser General Public License as published by the Free
   14 # Software Foundation; either version 3 of the License, or (at your option) any
   15 # later version.
   16 #
   17 # MayaChemTools is distributed in the hope that it will be useful, but without
   18 # any warranty; without even the implied warranty of merchantability of fitness
   19 # for a particular purpose.  See the GNU Lesser General Public License for more
   20 # details.
   21 #
   22 # You should have received a copy of the GNU Lesser General Public License
   23 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
   24 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
   25 # Boston, MA, 02111-1307, USA.
   26 #
   27 
   28 from __future__ import print_function
   29 
   30 import os
   31 import sys
   32 import re
   33 import base64
   34 import pickle
   35 
   36 from rdkit import Chem
   37 from rdkit.Chem import AllChem
   38 from rdkit.Chem import Draw
   39 
   40 import MiscUtil
   41 
   42 __all__ = ["AreAtomIndicesSequentiallyConnected", "AreAtomMapNumbersPresentInMol", "AreHydrogensMissingInMolecule", "CalculateFormalCharge", "CalculateSpinMultiplicity", "ClearAtomMapNumbers", "ConstrainAndEmbed", "FilterSubstructureMatchByAtomMapNumbers", "FilterSubstructureMatchesByAtomMapNumbers", "GetAtomIndices", "GetAtomMapIndices", "GetAtomMapIndicesAndMapNumbers", "GetAtomSymbols", "GetAtomPositions", "GetFormalCharge", "GetHeavyAtomNeighbors", "GetInlineSVGForMolecule", "GetInlineSVGForMolecules", "GetMolName", "GetNumFragments", "GetNumHeavyAtomNeighbors", "GetSpinMultiplicity", "GetSVGForMolecule", "GetSVGForMolecules", "GetPsi4XYZFormatString", "GetTorsionsAroundRotatableBonds", "GenerateBase64EncodedMolStrings", "GenerateBase64EncodedMolStringWithConfIDs", "IsAtomSymbolPresentInMol", "IsMolEmpty", "IsValidElementSymbol", "IsValidAtomIndex", "MolFromBase64EncodedMolString", "GenerateBase64EncodedMolStringsWithIDs", "MolToBase64EncodedMolString", "MolFromSubstructureMatch", "MolsFromSubstructureMatches", "ReadMolecules", "ReadAndValidateMolecules", "ReadMoleculesFromSDFile", "ReadMoleculesFromMolFile", "ReadMoleculesFromMol2File", "ReadMoleculesFromPDBFile", "ReadMoleculesFromSMILESFile", "ReorderAtomIndicesInSequentiallyConnectedManner", "SetAtomPositions", "SetWriterMolProps", "ValidateElementSymbols", "WriteMolecules"]
   43 
   44 def GetMolName(Mol, MolNum = None):
   45     """Get molecule name.
   46     
   47     Arguments:
   48         Mol (object): RDKit molecule object.
   49         MolNum (int or None): Molecule number in input file.
   50 
   51     Returns:
   52         str : Molname corresponding to _Name property of a molecule, generated
   53             from specieid MolNum using the format "Mol%d" % MolNum, or an
   54             empty string.
   55 
   56     """
   57     
   58     MolName = ''
   59     if Mol.HasProp("_Name"):
   60         MolName = Mol.GetProp("_Name")
   61 
   62     if not len(MolName):
   63         if MolNum is not None:
   64             MolName = "Mol%d" % MolNum
   65     
   66     return MolName
   67 
   68 def GetInlineSVGForMolecule(Mol, Width, Height, Legend = None, AtomListToHighlight = None, BondListToHighlight = None, BoldText = True, Base64Encoded = True):
   69     """Get SVG image text for a molecule suitable for inline embedding into a HTML page.
   70     
   71     Arguments:
   72         Mol (object): RDKit molecule object.
   73         Width (int): Width of a molecule image in pixels.
   74         Height (int): Height of a molecule image in pixels.
   75         Legend (str): Text to display under the image.
   76         AtomListToHighlight (list): List of atoms to highlight.
   77         BondListToHighlight (list): List of bonds to highlight.
   78         BoldText (bool): Flag to make text bold in the image of molecule. 
   79         Base64Encoded (bool): Flag to return base64 encoded string. 
   80 
   81     Returns:
   82         str : SVG image text for inline embedding into a HTML page using "img"
   83             tag: <img src="data:image/svg+xml;charset=UTF-8,SVGImageText> or
   84             tag: <img src="data:image/svg+xml;base64,SVGImageText>
   85 
   86     """
   87 
   88     SVGText = GetSVGForMolecule(Mol, Width, Height, Legend, AtomListToHighlight, BondListToHighlight, BoldText)
   89     return _ModifySVGForInlineEmbedding(SVGText, Base64Encoded)
   90     
   91 def GetInlineSVGForMolecules(Mols, MolsPerRow, MolWidth, MolHeight, Legends = None, AtomListsToHighlight = None, BondListsToHighLight = None, BoldText = True, Base64Encoded = True):
   92     """Get SVG image text for  molecules suitable for inline embedding into a HTML page.
   93     
   94     Arguments:
   95         Mols (list): List of RDKit molecule objects.
   96         MolsPerRow (int): Number of molecules per row.
   97         Width (int): Width of a molecule image in pixels.
   98         Height (int): Height of a molecule image in pixels.
   99         Legends (list): List containing strings to display under images.
  100         AtomListsToHighlight (list): List of lists containing atoms to highlight
  101             for molecules.
  102         BondListsToHighlight (list): List of lists containing bonds to highlight
  103             for molecules
  104         BoldText (bool): Flag to make text bold in the image of molecules. 
  105         Base64Encoded (bool): Flag to return base64 encoded string. 
  106 
  107     Returns:
  108         str : SVG image text for inline embedding into a HTML page using "img"
  109             tag: <img src="data:image/svg+xml;charset=UTF-8,SVGImageText> or
  110             tag: <img src="data:image/svg+xml;base64,SVGImageText>
  111 
  112     """
  113     
  114     SVGText = GetSVGForMolecules(Mols, MolsPerRow, MolWidth, MolHeight, Legends, AtomListsToHighlight, BondListsToHighLight, BoldText)
  115     return _ModifySVGForInlineEmbedding(SVGText, Base64Encoded)
  116 
  117 def _ModifySVGForInlineEmbedding(SVGText, Base64Encoded):
  118     """Modify SVG for inline embedding into a HTML page using "img" tag
  119     along with performing base64 encoding.
  120     """
  121     
  122     # Take out all tags till the start of '<svg' tag...
  123     Pattern = re.compile("^.*<svg", re.I | re.S)
  124     SVGText = Pattern.sub("<svg", SVGText)
  125     
  126     # Add an extra space before the "width=..." tag. Otherwise, inline embedding may
  127     # cause the following XML error on some browsers due to start of the "width=..."
  128     # at the begining of the line in <svg ...> tag:
  129     #
  130     #  XML5607: Whitespace expected.
  131     #
  132     SVGText = re.sub("width='", " width='", SVGText, flags = re.I)
  133     
  134     # Take out trailing new line...
  135     SVGText = SVGText.strip()
  136 
  137     # Perform base64 encoding by turning text into byte stream using string
  138     # encode and transform byte stream returned by b64encode into a string
  139     # by string decode...
  140     #
  141     if Base64Encoded:
  142         SVGText = base64.b64encode(SVGText.encode()).decode()
  143 
  144     return SVGText
  145 
  146 def GetSVGForMolecule(Mol, Width, Height, Legend = None, AtomListToHighlight = None, BondListToHighlight = None, BoldText = True):
  147     """Get SVG image text for a molecule suitable for viewing in a browser.
  148     
  149     Arguments:
  150         Mol (object): RDKit molecule object.
  151         Width (int): Width of a molecule image in pixels.
  152         Height (int): Height of a molecule image in pixels.
  153         Legend (str): Text to display under the image.
  154         AtomListToHighlight (list): List of atoms to highlight.
  155         BondListToHighlight (list): List of bonds to highlight.
  156         BoldText (bool): Flag to make text bold in the image of molecule. 
  157 
  158     Returns:
  159         str : SVG image text for writing to a SVG file for viewing in a browser.
  160 
  161     """
  162     
  163     Mols = [Mol]
  164     
  165     MolsPerRow = 1
  166     MolWidth = Width
  167     MolHeight = Height
  168     
  169     Legends = [Legend] if Legend is not None else None
  170     AtomListsToHighlight = [AtomListToHighlight] if AtomListToHighlight is not None else None
  171     BondListsToHighLight = [BondListsToHighLight] if BondListToHighlight is not None else None
  172     
  173     return GetSVGForMolecules(Mols, MolsPerRow, MolWidth, MolHeight, Legends, AtomListsToHighlight, BondListsToHighLight, BoldText)
  174 
  175 def GetSVGForMolecules(Mols, MolsPerRow, MolWidth, MolHeight, Legends = None, AtomListsToHighlight = None, BondListsToHighlight = None, BoldText = True):
  176     """Get SVG image text for molecules suitable for viewing in a browser.
  177     
  178     Arguments:
  179         Mols (list): List of RDKit molecule objects.
  180         MolsPerRow (int): Number of molecules per row.
  181         Width (int): Width of a molecule image in pixels.
  182         Height (int): Height of a molecule image in pixels.
  183         Legends (list): List containing strings to display under images.
  184         AtomListsToHighlight (list): List of lists containing atoms to highlight
  185             for molecules.
  186         BondListsToHighlight (list): List of lists containing bonds to highlight
  187             for molecules
  188         BoldText (bool): Flag to make text bold in the image of molecules. 
  189 
  190     Returns:
  191         str : SVG image text for writing to a SVG file for viewing in a browser.
  192 
  193     """
  194     
  195     SVGText = Draw.MolsToGridImage(Mols, molsPerRow = MolsPerRow, subImgSize = (MolWidth,MolHeight), legends = Legends, highlightAtomLists = AtomListsToHighlight, highlightBondLists = BondListsToHighlight, useSVG = True)
  196     
  197     return _ModifySVGForBrowserViewing(SVGText, BoldText)
  198 
  199 def _ModifySVGForBrowserViewing(SVGText, BoldText = True):
  200     """Modify SVG for loading into a browser."""
  201     
  202     # It appears that the string 'xmlns:svg' needs to be replaced with 'xmlns' in the
  203     # SVG image string generated by older versions of RDKit. Otherwise, the image
  204     # doesn't load in web browsers.
  205     #
  206     if re.search("xmlns:svg", SVGText, re.I):
  207         SVGText = re.sub("xmlns:svg", "xmlns", SVGText, flags = re.I)
  208     
  209     # Make text bold...
  210     if BoldText:
  211         SVGText = re.sub("font-weight:normal;", "font-weight:bold;", SVGText, flags = re.I)
  212     
  213     return SVGText
  214 
  215 def IsMolEmpty(Mol):
  216     """Check for the presence of atoms in a molecule.
  217     
  218     Arguments:
  219         Mol (object): RDKit molecule object.
  220 
  221     Returns:
  222         bool : True - No atoms in molecule; Otherwise, false. 
  223 
  224     """
  225 
  226     Status = False if Mol.GetNumAtoms() else True
  227     
  228     return Status
  229 
  230 def IsAtomSymbolPresentInMol(Mol, AtomSymbol, IgnoreCase = True):
  231     """ Check for the presence of an atom symbol in a molecule.
  232     
  233     Arguments:
  234         Mol (object): RDKit molecule object.
  235         AtomSymbol (str): Atom symbol.
  236 
  237     Returns:
  238         bool : True - Atom symbol in molecule; Otherwise, false. 
  239 
  240     """
  241     
  242     for Atom in Mol.GetAtoms():
  243         Symbol = Atom.GetSymbol()
  244         if IgnoreCase:
  245             if re.match("^%s$" % AtomSymbol, Symbol, re.I):
  246                 return True
  247         else:
  248             if re.match("^%s$" % AtomSymbol, Symbol):
  249                 return True
  250     
  251     return False
  252 
  253 def ValidateElementSymbols(ElementSymbols):
  254     """Validate element symbols.
  255     
  256     Arguments:
  257         ElementSymbols (list): List of element symbols to validate.
  258 
  259     Returns:
  260         bool : True - All element symbols are valid; Otherwise, false. 
  261 
  262     """
  263     for ElementSymbol in ElementSymbols:
  264         if not IsValidElementSymbol(ElementSymbol):
  265             return False
  266     
  267     return True
  268 
  269 def GetAtomPositions(Mol, ConfID = -1):
  270     """Retrieve a list of lists containing coordinates of all atoms in a
  271     molecule.
  272     
  273     Arguments:
  274         Mol (object): RDKit molecule object.
  275         ConfID (int): Conformer number.
  276 
  277     Returns:
  278         list : List of lists containing atom positions.
  279 
  280     Examples:
  281 
  282         for AtomPosition in RDKitUtil.GetAtomPositions(Mol):
  283             print("X: %s; Y: %s; Z: %s" % (AtomPosition[0], AtomPosition[1], AtomPosition[2]))
  284 
  285     """
  286 
  287     return Mol.GetConformer(id = ConfID).GetPositions().tolist()
  288 
  289 def SetAtomPositions(Mol, AtomPositions, ConfID = -1):
  290     """Set atom positions of all atoms in a molecule.
  291     
  292     Arguments:
  293         Mol (object): RDKit molecule object.
  294         AtomPositions (object): List of lists containing atom positions.
  295         ConfID (int): Conformer number.
  296 
  297     Returns:
  298         object : RDKit molecule object.
  299 
  300     """
  301     
  302     MolConf = Mol.GetConformer(ConfID)
  303 
  304     for Index in range(len(AtomPositions)):
  305             MolConf.SetAtomPosition(Index, tuple(AtomPositions[Index]))
  306     
  307     return Mol
  308 
  309 def GetAtomSymbols(Mol):
  310     """Retrieve a list containing atom symbols of all atoms a molecule.
  311     
  312     Arguments:
  313         Mol (object): RDKit molecule object.
  314 
  315     Returns:
  316         list : List of atom symbols.
  317 
  318     """
  319 
  320     return [Atom.GetSymbol() for Atom in Mol.GetAtoms()]
  321 
  322 def GetAtomIndices(Mol):
  323     """Retrieve a list containing atom indices of all atoms a molecule.
  324     
  325     Arguments:
  326         Mol (object): RDKit molecule object.
  327 
  328     Returns:
  329         list : List of atom indices.
  330 
  331     """
  332 
  333     return [Atom.GetIdx() for Atom in Mol.GetAtoms()]
  334 
  335 def GetFormalCharge(Mol, CheckMolProp = True):
  336     """Get formal charge of a molecule. The formal charge is either retrieved
  337     from 'FormalCharge' molecule property or calculated using RDKit function
  338     Chem.GetFormalCharge(Mol).
  339     
  340     The 'FormalCharge' molecule property may contain multiple space delimited
  341     values. The total formal charge corresponds to the sum of the specified formal
  342     charge values.
  343 
  344     Arguments:
  345         Mol (object): RDKit molecule object.
  346         CheckMolProp (bool): Check 'FormalCharge' molecule property to
  347             retrieve formal charge.
  348 
  349     Returns:
  350         int : Formal charge.
  351 
  352     """
  353     
  354     Name = 'FormalCharge'
  355     if (CheckMolProp and Mol.HasProp(Name)):
  356         FormalCharge = Mol.GetProp(Name)
  357         Values = FormalCharge.split()
  358         if len(Values) > 1:
  359             MiscUtil.PrintWarning("RDKitUtil.GetFormalCharge: Molecule property, %s, contains multiple values, %s. Formal charge corresponds to sum of the specified values..." % (Name, FormalCharge))
  360             FormalCharge = 0.0
  361             for Value in Values:
  362                 FormalCharge += float(Value)
  363             FormalCharge = int(FormalCharge)
  364         else:
  365             FormalCharge = int(float(FormalCharge))
  366     else:
  367         FormalCharge =  CalculateFormalCharge(Mol)
  368 
  369     return int(FormalCharge)
  370 
  371 def CalculateFormalCharge(Mol):
  372     """Calculate formal charge of a molecule. The formal charge is calculated
  373     using RDKit function Chem.GetFormalCharge(Mol).
  374 
  375     Arguments:
  376         Mol (object): RDKit molecule object.
  377             retrieve formal charge.
  378 
  379     Returns:
  380         int : Formal charge.
  381 
  382     """
  383     
  384     return int(Chem.GetFormalCharge(Mol))
  385     
  386 def GetSpinMultiplicity(Mol, CheckMolProp = True):
  387     """Get spin multiplicity of a molecule. The spin multiplicity is either
  388     retrieved from 'SpinMultiplicity' molecule property or calculated
  389     from the number of free radical electrons using Hund's rule of maximum
  390     multiplicity defined as 2S + 1 where S is the total electron spin. The
  391     total spin is 1/2 the number of free radical electrons in a molecule.
  392     
  393     The 'SpinMultiplicity' molecule property may contain multiple space delimited
  394     values. The total spin multiplicity corresponds to the total number of free radical
  395     electrons which are calculated for each specified value.
  396 
  397     Arguments:
  398         Mol (object): RDKit molecule object.
  399         CheckMolProp (bool): Check 'SpinMultiplicity' molecule property to
  400             retrieve spin multiplicity.
  401 
  402     Returns:
  403         int : Spin multiplicity.
  404 
  405     """
  406     
  407     Name = 'SpinMultiplicity'
  408     if (CheckMolProp and Mol.HasProp(Name)):
  409         SpinMultiplicity = Mol.GetProp(Name)
  410         Values = SpinMultiplicity.split()
  411         if len(Values) > 1:
  412             MiscUtil.PrintWarning("RDKitUtil.GetSpinMultiplicity: Molecule property, %s, contains multiple values, %s. Calculating spin multiplicity corresponding to total number of free radical electrons for each specified value..." % (Name, SpinMultiplicity))
  413             NumRadicalElectrons = 0
  414             for Value in Values:
  415                 NumRadicalElectrons += int(float(Value)) - 1
  416             
  417             TotalElectronicSpin = NumRadicalElectrons/2
  418             SpinMultiplicity = 2 * TotalElectronicSpin + 1
  419         else:
  420             SpinMultiplicity = int(float(SpinMultiplicity))
  421     else:
  422         SpinMultiplicity = CalculateSpinMultiplicity(Mol)
  423 
  424     return int(SpinMultiplicity)
  425 
  426 def CalculateSpinMultiplicity(Mol):
  427     """Calculate spin multiplicity of a molecule. The spin multiplicity is calculated
  428     from the number of free radical electrons using Hund's rule of maximum
  429     multiplicity defined as 2S + 1 where S is the total electron spin. The
  430     total spin is 1/2 the number of free radical electrons in a molecule.
  431 
  432     Arguments:
  433         Mol (object): RDKit molecule object.
  434 
  435     Returns:
  436         int : Spin multiplicity.
  437 
  438     """
  439     
  440     # Calculate spin multiplicity using Hund's rule of maximum multiplicity...
  441     NumRadicalElectrons = 0
  442     for Atom in Mol.GetAtoms():
  443         NumRadicalElectrons += Atom.GetNumRadicalElectrons()
  444 
  445     TotalElectronicSpin = NumRadicalElectrons/2
  446     SpinMultiplicity = 2 * TotalElectronicSpin + 1
  447 
  448     return int(SpinMultiplicity)
  449     
  450 def GetPsi4XYZFormatString(Mol, ConfID = -1, FormalCharge = "auto", SpinMultiplicity = "auto", Symmetry = "auto", NoCom = False, NoReorient = False, CheckFragments = False):
  451     """Retrieve geometry string of a molecule in Psi4ish XYZ format to perform
  452     Psi4 quantum chemistry calculations.
  453     
  454     You may explicit specify multiple space delimited values for formal charge
  455     and spin multiplicity. Otherwise, these values are either automatically
  456     retrieved from 'FormalCharge' and 'SpinMultiplicity' molecule properties or
  457     calculated using RDKit. The number of specified values for these properties
  458     must match the number of fragments in the molecule during the processing
  459     of the fragments.
  460 
  461     Arguments:
  462         Mol (object): RDKit molecule object.
  463         ConfID (int): Conformer number.
  464         FormalCharge (str): Specified formal charge or 'auto' to calculate
  465            its value by RDKit.
  466         SpinMultiplicity (str): Specified spin multiplicity or 'auto' to calculate
  467            its value by RDKit.
  468         Symmetry (str): Specified symmetry or 'auto' to calculate its value by
  469            Psi4.
  470         NoCom (bool): Flag to disable recentering of a molecule by Psi4.
  471         NoReorient (bool): Flag to disable reorientation of a molecule by Psi4.
  472         CheckFragments (bool): Check for fragments and setup geometry string
  473            using  -- separator between fragments.
  474 
  475     Returns:
  476         str : Geometry string of a molecule in Psi4ish XYZ format.
  477 
  478     """
  479 
  480     # Check for fragments...
  481     Mols = [Mol]
  482     if CheckFragments:
  483         Fragments = list(Chem.rdmolops.GetMolFrags(Mol, asMols = True))
  484         if len(Fragments) > 1:
  485             Mols = Fragments
  486     
  487     FragMolFormalCharges = _SetFormalChargesForPsi4XYZFormatString(Mol, Mols, FormalCharge, CheckFragments)
  488     FragMolSpinMultiplicities = _SetSpinMultiplicitiesForPsi4XYZFormatString(Mol, Mols, SpinMultiplicity, CheckFragments)
  489     
  490     # Setup geometry string for Ps4...
  491     GeometryList = []
  492     FragMolCount = 0
  493     
  494     for FragMolIndex, FragMol in enumerate(Mols):
  495         FragMolCount += 1
  496         if FragMolCount > 1:
  497             GeometryList.append("--")
  498         
  499         FragMolFormalCharge = FragMolFormalCharges[FragMolIndex]
  500         FragMolSpinMultiplicity = FragMolSpinMultiplicities[FragMolIndex]
  501         if FragMolFormalCharge is None or FragMolSpinMultiplicity is None:
  502             MiscUtil.PrintInfo("")
  503             MiscUtil.PrintWarning("RDKitUtil.GetPsi4XYZFormatString: Failed to set formal charge and spin multiplicity values. Both formal charge, %s, and spin multiplicity, %s, must be valid values. These values are either specified explicitly or automatically calculated..." % (FragMolFormalCharge, FragMolSpinMultiplicity))
  504         else:
  505             GeometryList.append("%s %s" % (FragMolFormalCharge, FragMolSpinMultiplicity))
  506         
  507         AtomSymbols = GetAtomSymbols(FragMol)
  508         AtomPositions = GetAtomPositions(FragMol, ConfID)
  509         
  510         for AtomSymbol, AtomPosition in zip(AtomSymbols, AtomPositions):
  511             GeometryList.append("%s %s %s %s" % (AtomSymbol, AtomPosition[0], AtomPosition[1], AtomPosition[2]))
  512 
  513     GeometryList.append("units angstrom")
  514     
  515     if not re.match("^auto$", Symmetry, re.I):
  516         Name = 'Symmetry'
  517         if (Mol.HasProp(Name)):
  518             Symmetry =  Mol.GetProp(Name)
  519         GeometryList.append("symmetry %s" % Symmetry)
  520     
  521     if NoCom:
  522         GeometryList.append("no_com")
  523         
  524     if NoReorient:
  525         GeometryList.append("no_reorient")
  526     
  527     Geometry = "\n".join(GeometryList)
  528     
  529     return Geometry
  530 
  531 def _SetFormalChargesForPsi4XYZFormatString(Mol, FragMols, FormalCharge, CheckFragments):
  532     """Setup formal charges for Psi4 XYZ format string. """
  533 
  534     if not CheckFragments:
  535         if re.match("^auto$", FormalCharge, re.I):
  536             MolFormalCharge = GetFormalCharge(Mol)
  537         else:
  538             MolFormalCharge = int(FormalCharge)
  539         return [MolFormalCharge]
  540     
  541     FragMolsCount = len(FragMols)
  542     FormalCharges = [None] * FragMolsCount
  543     
  544     if re.match("^auto$", FormalCharge, re.I):
  545         PropName = "FormalCharge"
  546         if Mol.HasProp(PropName):
  547             FormalCharge = Mol.GetProp(PropName)
  548             FormalChargeWords = FormalCharge.split()
  549             if len(FormalChargeWords) == FragMolsCount:
  550                 FormalCharges = [int(float(FormalCharge)) for FormalCharge in FormalChargeWords]
  551             else:
  552                 MiscUtil.PrintWarning("RDKitUtil.GetPsi4XYZFormatString: Ignoring specified value, %s, for FormalCharge molecule property. The number of space delimted specified values, %s,  must match number of fragments, %s, in the molecule..." % (FormalCharge, len(FormalChargeWords), FragMolsCount))
  553         else:
  554             FormalCharges = [CalculateFormalCharge(FragMol) for FragMol in FragMols]
  555     else:
  556         FormalChargeWords = FormalCharge.split()
  557         if len(FormalChargeWords) != FragMolsCount:
  558             MiscUtil.PrintWarning("RDKitUtil.GetPsi4XYZFormatString: Ignoring specified value, %s, for FormalCharge paramater. The number of space delimted specified values, %s,  must match number of fragments, %s, in the molecule..." % (FormalCharge, len(FormalChargeWords), FragMolsCount))
  559         else:
  560             FormalCharges = [int(FormalCharge) for FormalCharge in FormalChargeWords]
  561 
  562     return FormalCharges
  563 
  564 def _SetSpinMultiplicitiesForPsi4XYZFormatString(Mol, FragMols, SpinMultiplicity, CheckFragments):
  565     """Setup spin multiplicites for Psi4 XYZ format string. """
  566     
  567     if not CheckFragments:
  568         if re.match("^auto$", SpinMultiplicity, re.I):
  569             MolSpinMultiplicity = GetSpinMultiplicity(Mol)
  570         else:
  571             MolSpinMultiplicity = int(SpinMultiplicity)
  572         return [MolSpinMultiplicity]
  573         
  574     FragMolsCount = len(FragMols)
  575     SpinMultiplicities = [None] * FragMolsCount
  576     
  577     if re.match("^auto$", SpinMultiplicity, re.I):
  578         PropName = "SpinMultiplicity"
  579         if Mol.HasProp(PropName):
  580             SpinMultiplicity = Mol.GetProp(PropName)
  581             SpinMultiplicityWords = SpinMultiplicity.split()
  582             if len(SpinMultiplicityWords) == FragMolsCount:
  583                 SpinMultiplicities = [int(float(SpinMultiplicity)) for SpinMultiplicity in SpinMultiplicityWords]
  584             else:
  585                 MiscUtil.PrintWarning("RDKitUtil.GetPsi4XYZFormatString: Ignoring specified value, %s, for SpinMultiplicity molecule property. The number of space delimted specified values, %s,  must match number of fragments, %s, in the molecule..." % (SpinMultiplicity, len(SpinMultiplicityWords), FragMolsCount))
  586         else:
  587             SpinMultiplicities = [CalculateSpinMultiplicity(FragMol) for FragMol in FragMols]
  588     else:
  589         SpinMultiplicityWords = SpinMultiplicity.split()
  590         if len(SpinMultiplicityWords) != FragMolsCount:
  591             MiscUtil.PrintWarning("RDKitUtil.GetPsi4XYZFormatString: Ignoring specified value, %s, for SpinMultiplicity paramater. The number of space delimted specified values, %s,  must match number of fragments, %s, in the molecule..." % (SpinMultiplicity, len(SpinMultiplicityWords), FragMolsCount))
  592         else:
  593             SpinMultiplicities = [int(SpinMultiplicity) for SpinMultiplicity in SpinMultiplicityWords]
  594 
  595     return SpinMultiplicities
  596 
  597 def GetTorsionsAroundRotatableBonds(Mol, RotBondsPatternMol, IgnoreHydrogens = True):
  598     """Identify torsions around rotatable bonds and return a list of lists
  599     containing atom indices of torsions.
  600     
  601     Arguments:
  602         Mol (object): RDKit molecule object.
  603         PatternMol (object): RDKit molecule object corresponding to SMARTS
  604             pattern to identify rotatable bonds.
  605         IgnoreHydrogens (bool): Flag to include torsions around rotatable bonds
  606             containing hydrogens.
  607 
  608     Returns:
  609         list : List of lists containing atom indices of torsions around
  610             rotatable bonds.
  611 
  612     """
  613     
  614     # Match rotatable bonds...
  615     RotBondsMatches = FilterSubstructureMatchesByAtomMapNumbers(Mol, RotBondsPatternMol, Mol.GetSubstructMatches(RotBondsPatternMol, useChirality = False))
  616     if not len(RotBondsMatches):
  617         return None
  618     
  619     # Identify torsions...
  620     TorsioAtomIndicesList = []
  621     for RotBondMatch in RotBondsMatches:
  622         if len(RotBondMatch) != 2:
  623             continue
  624         
  625         Atom1Index, Atom2Index = RotBondMatch
  626         Atom1NbrIndices = _GetRotBondAtomNeighbors(Mol, Atom1Index, Atom2Index, IgnoreHydrogens)
  627         Atom2NbrIndices = _GetRotBondAtomNeighbors(Mol, Atom2Index, Atom1Index, IgnoreHydrogens)
  628         
  629         TorsionAtomIndices = []
  630         for Atom1NbrIndex in Atom1NbrIndices:
  631             if Atom1NbrIndex in Atom2NbrIndices:
  632                 continue
  633             for Atom2NbrIndex in Atom2NbrIndices:
  634                 if Atom2NbrIndex in Atom1NbrIndices:
  635                     continue
  636                 TorsionAtomIndices.append([Atom1NbrIndex, Atom1Index, Atom2Index, Atom2NbrIndex])
  637         
  638         TorsioAtomIndicesList.extend(TorsionAtomIndices)
  639     
  640     return TorsioAtomIndicesList
  641 
  642 def _GetRotBondAtomNeighbors(Mol, AtomIndex, BondedAtomIndex, IgnoreHydrogens):
  643     """Get atom neigbors around a rotatable bond."""
  644 
  645     Atom = Mol.GetAtomWithIdx(AtomIndex)
  646     AtomNeighbors = []
  647     
  648     for AtomNbr in Atom.GetNeighbors():
  649         AtomNbrIndex = AtomNbr.GetIdx()
  650         if AtomNbrIndex == BondedAtomIndex:
  651             continue
  652         if IgnoreHydrogens:
  653             if AtomNbr.GetAtomicNum() == 1:
  654                 continue
  655         
  656         AtomNeighbors.append(AtomNbrIndex)
  657     
  658     return AtomNeighbors
  659 
  660 def GetNumFragments(Mol):
  661     """Get number of fragment in a molecule.
  662 
  663     Arguments:
  664         Atom (object): RDKit molecule object.
  665 
  666     Returns:
  667         int : Number of fragments.
  668 
  669     """
  670     
  671     Fragments = Chem.rdmolops.GetMolFrags(Mol, asMols = False)
  672     
  673     return len(Fragments) if Fragments is not None else 0
  674 
  675 def GetNumHeavyAtomNeighbors(Atom):
  676     """Get number of heavy atom neighbors.
  677 
  678     Arguments:
  679         Atom (object): RDKit atom object.
  680 
  681     Returns:
  682         int : Number of neighbors.
  683 
  684     """
  685     
  686     NbrCount = 0
  687     for AtomNbr in Atom.GetNeighbors():
  688         if AtomNbr.GetAtomicNum() > 1:
  689             NbrCount += 1
  690     
  691     return NbrCount
  692 
  693 def GetHeavyAtomNeighbors(Atom):
  694     """Get a list of heavy atom neighbors.
  695 
  696     Arguments:
  697         Atom (object): RDKit atom object.
  698 
  699     Returns:
  700         list : List of heavy atom neighbors.
  701 
  702     """
  703     
  704     AtomNeighbors = []
  705     for AtomNbr in Atom.GetNeighbors():
  706         if AtomNbr.GetAtomicNum() > 1:
  707             AtomNeighbors.append(AtomNbr)
  708     
  709     return AtomNeighbors
  710 
  711 def IsValidElementSymbol(ElementSymbol):
  712     """Validate element symbol.
  713     
  714     Arguments:
  715         ElementSymbol (str): Element symbol
  716 
  717     Returns:
  718         bool : True - Valid element symbol; Otherwise, false. 
  719 
  720     """
  721 
  722     try:
  723         AtomicNumber = Chem.GetPeriodicTable().GetAtomicNumber(ElementSymbol)
  724         Status = True if AtomicNumber > 0  else False
  725     except Exception as ErrMsg:
  726         Status = False
  727     
  728     return Status
  729 
  730 def IsValidAtomIndex(Mol, AtomIndex):
  731     """Validate presence  atom index in a molecule.
  732     
  733     Arguments:
  734         Mol (object): RDKit molecule object.
  735         AtomIndex (int): Atom index.
  736 
  737     Returns:
  738         bool : True - Valid atom index; Otherwise, false. 
  739 
  740     """
  741     for Atom in Mol.GetAtoms():
  742         if AtomIndex == Atom.GetIdx():
  743             return True
  744     
  745     return False
  746 
  747 def AreHydrogensMissingInMolecule(Mol):
  748     """Check for any missing hydrogens in  in a molecue.
  749 
  750     Arguments:
  751         Mol (object): RDKit molecule object.
  752 
  753     Returns:
  754         bool : True - Missing hydrogens; Otherwise, false. 
  755 
  756     """
  757 
  758     for Atom in Mol.GetAtoms():
  759         NumExplicitAndImplicitHs = Atom.GetNumExplicitHs() + Atom.GetNumImplicitHs()
  760         if NumExplicitAndImplicitHs > 0:
  761             return True
  762 
  763     return False
  764 
  765 def AreAtomIndicesSequentiallyConnected(Mol, AtomIndices):
  766     """Check for the presence bonds between sequential pairs of atoms in a
  767     molecule.
  768     
  769     Arguments:
  770         Mol (object): RDKit molecule object.
  771         AtomIndices (list): List of atom indices.
  772 
  773     Returns:
  774         bool : True - Sequentially connected; Otherwise, false. 
  775 
  776     """
  777 
  778     for Index in range(0, (len(AtomIndices) -1)):
  779         Bond = Mol.GetBondBetweenAtoms(AtomIndices[Index], AtomIndices[Index + 1])
  780         if Bond is None:
  781             return False
  782         
  783         if Bond.GetIdx() is None:
  784             return False
  785     
  786     return True
  787     
  788 def ReorderAtomIndicesInSequentiallyConnectedManner(Mol, AtomIndices):
  789     """Check for the presence of sequentially connected list of atoms in an
  790     arbitray list of atoms in molecule.
  791    
  792     Arguments:
  793         Mol (object): RDKit molecule object.
  794         AtomIndices (list): List of atom indices.
  795 
  796     Returns:
  797         bool : True - Sequentially connected list found; Otherwise, false. 
  798         list : List of seqeuntially connected atoms or None.
  799 
  800     """
  801     
  802     # Count the number of neighbors for specified atom indices ensuring
  803     # that the neighbors are also part of atom indices...
  804     AtomNbrsCount = {}
  805     for AtomIndex in AtomIndices:
  806         Atom = Mol.GetAtomWithIdx(AtomIndex)
  807         
  808         AtomNbrsCount[AtomIndex] = 0
  809         for AtomNbr in Atom.GetNeighbors():
  810             AtomNbrIndex = AtomNbr.GetIdx()
  811             if AtomNbrIndex not in AtomIndices:
  812                 continue
  813             AtomNbrsCount[AtomIndex] += 1
  814     
  815     # Number of neighbors for each specified atom indices must be 1 or 2
  816     # for sequentially connected list of atom indices...
  817     AtomsWithOneNbr = []
  818     for AtomIndex, NbrsCount  in AtomNbrsCount.items():
  819         if not (NbrsCount == 1 or NbrsCount ==2):
  820             return (False, None)
  821         
  822         if NbrsCount == 1:
  823             AtomsWithOneNbr.append(AtomIndex)
  824 
  825     # A sequentially connected list of indices must have two atom indices with
  826     # exactly # one neighbor...
  827     if len(AtomsWithOneNbr) != 2:
  828             return (False, None)
  829 
  830     # Setup a reordered list of sequentially connected atoms...
  831     ReorderedAtomIndices = []
  832     
  833     AtomIndex1, AtomIndex2 = AtomsWithOneNbr
  834     AtomIndex = AtomIndex1 if AtomIndex1 < AtomIndex2 else AtomIndex2
  835     ReorderedAtomIndices.append(AtomIndex)
  836 
  837     while (len(ReorderedAtomIndices) < len(AtomIndices)):
  838         Atom = Mol.GetAtomWithIdx(AtomIndex)
  839         
  840         for AtomNbr in Atom.GetNeighbors():
  841             AtomNbrIndex = AtomNbr.GetIdx()
  842             if AtomNbrIndex not in AtomIndices:
  843                 continue
  844             
  845             if AtomNbrIndex in ReorderedAtomIndices:
  846                 continue
  847             
  848             # Treat neighbor as next connected atom...
  849             AtomIndex = AtomNbrIndex
  850             ReorderedAtomIndices.append(AtomIndex)
  851             break
  852 
  853     # Check reorderd list size...
  854     if (len(ReorderedAtomIndices) != len(AtomIndices)):
  855         return (False, None)
  856 
  857     # A final check to validate reorderd list...
  858     if not AreAtomIndicesSequentiallyConnected(Mol, ReorderedAtomIndices):
  859         return (False, None)
  860     
  861     return (True, ReorderedAtomIndices)
  862 
  863 def MolToBase64EncodedMolString(Mol, PropertyPickleFlags = Chem.PropertyPickleOptions.AllProps):
  864     """Encode RDkit molecule object into a base64 encoded string. The properties
  865     can be optionally excluded.
  866     
  867     The molecule is pickled using RDKit Mol.ToBinary() function before
  868     their encoding.
  869    
  870     Arguments:
  871         Mol (object): RDKit molecule object.
  872         PropertyPickleFlags: RDKit property pickle options.
  873 
  874     Returns:
  875         str : Base64 encode molecule string or None.
  876 
  877     Notes:
  878         The following property pickle flags are currently available in RDKit:
  879             
  880             Chem.PropertyPickleOptions.NoProps
  881             Chem.PropertyPickleOptions.MolProps
  882             Chem.PropertyPickleOptions.AtomProps
  883             Chem.PropertyPickleOptions.BondProps
  884             Chem.PropertyPickleOptions.PrivateProps
  885             Chem.PropertyPickleOptions.AllProps
  886 
  887     """
  888 
  889     return None if Mol is None else base64.b64encode(Mol.ToBinary(PropertyPickleFlags)).decode()
  890 
  891 def MolFromBase64EncodedMolString(EncodedMol):
  892     """Generate a RDKit molecule object from a base64 encoded string.
  893     
  894     Arguments:
  895         str: Base64 encoded molecule string.
  896 
  897     Returns:
  898         object : RDKit molecule object or None.
  899 
  900     """
  901 
  902     return None if EncodedMol is None else Chem.Mol(base64.b64decode(EncodedMol))
  903 
  904 def GenerateBase64EncodedMolStrings(Mols, PropertyPickleFlags = Chem.PropertyPickleOptions.AllProps):
  905     """Setup an iterator for generating base64 encoded molecule string
  906     from a RDKit molecule iterator. The iterator returns a list containing
  907     a molecule index and encoded molecule string or None.
  908     
  909     The molecules are pickled using RDKit Mol.ToBinary() function
  910     before their encoding.
  911     
  912     Arguments:
  913         iterator: RDKit molecules iterator.
  914         PropertyFlags: RDKit property pickle options.
  915 
  916     Returns:
  917         object : Base64 endcoded molecules iterator. The iterator returns a
  918             list containing a molecule index and an encoded molecule string
  919             or None.
  920 
  921     Notes:
  922         The following property pickle flags are currently available in RDKit:
  923             
  924             Chem.PropertyPickleOptions.NoProps
  925             Chem.PropertyPickleOptions.MolProps
  926             Chem.PropertyPickleOptions.AtomProps
  927             Chem.PropertyPickleOptions.BondProps
  928             Chem.PropertyPickleOptions.PrivateProps
  929             Chem.PropertyPickleOptions.AllProps
  930 
  931     Examples:
  932 
  933         EncodedMolsInfo = GenerateBase64EncodedMolStrings(Mols)
  934         for MolIndex, EncodedMol in EncodedMolsInfo:
  935             if EncodeMol is not None:
  936                 Mol = MolFromBase64EncodedMolString(EncodedMol)
  937 
  938     """
  939     for MolIndex, Mol in enumerate(Mols):
  940         yield [MolIndex, None] if Mol is None else [MolIndex, MolToBase64EncodedMolString(Mol, PropertyPickleFlags)]
  941 
  942 def GenerateBase64EncodedMolStringsWithIDs(Mols, MolIDs, PropertyPickleFlags = Chem.PropertyPickleOptions.AllProps):
  943     """Setup an iterator for generating base64 encoded molecule string
  944     from a RDKit molecule iterator. The iterator returns a list containing
  945     a molecule ID and encoded molecule string or None.
  946     
  947     The molecules are pickled using RDKit Mol.ToBinary() function
  948     before their encoding.
  949     
  950     Arguments:
  951         iterator: RDKit molecules iterator.
  952         MolIDs (list): Molecule IDs.
  953         PropertyFlags: RDKit property pickle options.
  954 
  955     Returns:
  956         object : Base64 endcoded molecules iterator. The iterator returns a
  957             list containing a molecule ID and an encoded molecule string
  958             or None.
  959 
  960     Notes:
  961         The following property pickle flags are currently available in RDKit:
  962             
  963             Chem.PropertyPickleOptions.NoProps
  964             Chem.PropertyPickleOptions.MolProps
  965             Chem.PropertyPickleOptions.AtomProps
  966             Chem.PropertyPickleOptions.BondProps
  967             Chem.PropertyPickleOptions.PrivateProps
  968             Chem.PropertyPickleOptions.AllProps
  969 
  970     Examples:
  971 
  972         EncodedMolsInfo = GenerateBase64EncodedMolStringsWithIDs(Mols)
  973         for MolID, EncodedMol in EncodedMolsInfo:
  974             if EncodeMol is not None:
  975                 Mol = MolFromBase64EncodedMolString(EncodedMol)
  976 
  977     """
  978     for MolIndex, Mol in enumerate(Mols):
  979         yield [MolIDs[MolIndex], None] if Mol is None else [MolIDs[MolIndex], MolToBase64EncodedMolString(Mol, PropertyPickleFlags)]
  980 
  981 def GenerateBase64EncodedMolStringWithConfIDs(Mol, MolIndex, ConfIDs, PropertyPickleFlags = Chem.PropertyPickleOptions.AllProps):
  982     """Setup an iterator generating base64 encoded molecule string for a 
  983     molecule. The iterator returns a list containing a molecule index, an encoded
  984     molecule string, and conf ID.
  985     
  986     The molecules are pickled using RDKit Mol.ToBinary() function
  987     before their encoding.
  988     
  989     Arguments:
  990         Mol (object): RDKit molecule object.
  991         MolIndex (int): Molecule index.
  992         ConfIDs (list): Conformer IDs.
  993         PropertyFlags: RDKit property pickle options.
  994 
  995     Returns:
  996         object : Base64 endcoded molecules iterator. The iterator returns a
  997             list containing a molecule index, an encoded molecule string, and
  998             conf ID.
  999 
 1000     Notes:
 1001         The following property pickle flags are currently available in RDKit:
 1002             
 1003             Chem.PropertyPickleOptions.NoProps
 1004             Chem.PropertyPickleOptions.MolProps
 1005             Chem.PropertyPickleOptions.AtomProps
 1006             Chem.PropertyPickleOptions.BondProps
 1007             Chem.PropertyPickleOptions.PrivateProps
 1008             Chem.PropertyPickleOptions.AllProps
 1009 
 1010     Examples:
 1011 
 1012         EncodedMolsInfo = GenerateBase64EncodedMolStringWithConfIDs(Mol, MolIndex, ConfIDs)
 1013         for MolIndex, EncodedMol, ConfID in EncodedMolsInfo:
 1014             if EncodeMol is not None:
 1015                 Mol = MolFromBase64EncodedMolString(EncodedMol)
 1016 
 1017     """
 1018     for ConfID in ConfIDs:
 1019         yield [MolIndex, None, ConfID] if Mol is None else [MolIndex, MolToBase64EncodedMolString(Mol, PropertyPickleFlags), ConfID]
 1020 
 1021 def AreAtomMapNumbersPresentInMol(Mol):
 1022     """Check for the presence of atom map numbers in a molecue.
 1023     
 1024     Arguments:
 1025         Mol (object): RDKit molecule object.
 1026 
 1027     Returns:
 1028         bool : True - Atom map numbers present; Otherwise, false. 
 1029 
 1030     """
 1031 
 1032     return False if _GetAtomMapIndices(Mol) is None else True
 1033 
 1034 def ClearAtomMapNumbers(Mol, AllowImplicitValence = True, ClearRadicalElectrons = True):
 1035     """Check and clear atom map numbers in a molecule. In addition, allow implicit
 1036     valence and clear radical electrons for atoms with associated map numbers.
 1037     
 1038     For example, the following atomic properties are assigned by RDKit to atom
 1039     map number 1 in a molecule corresponding to SMILES C[C:1](C)C:
 1040     
 1041     NoImplicit: True; ImplicitValence: 0; ExplicitValence: 3; NumExplicitHs: 0;
 1042     NumImplicitHs: 0; NumRadicalElectrons: 1
 1043     
 1044     This function clears atoms map numbers in the molecule leading to SMILES 
 1045     CC(C)C, along with optionally updating atomic properties as shown below:
 1046     
 1047     NoImplicit: False; ImplicitValence: 1; ExplicitValence: 3; NumExplicitHs: 0;
 1048     NumImplicitHs: 1; NumRadicalElectrons: 0
 1049     
 1050     Arguments:
 1051         Mol (object): RDKit molecule object.
 1052 
 1053     Returns:
 1054         Mol (object): RDKit molecule object.
 1055 
 1056     """
 1057     
 1058     AtomMapIndices = GetAtomMapIndices(Mol)
 1059     
 1060     if AtomMapIndices is None:
 1061         return Mol
 1062     
 1063     for AtomMapIndex in AtomMapIndices:
 1064         Atom = Mol.GetAtomWithIdx(AtomMapIndex)
 1065         
 1066         # Clear map number property 'molAtomMapNumber'...
 1067         Atom.SetAtomMapNum(0)
 1068         
 1069         # Allow implit valence...
 1070         if AllowImplicitValence:
 1071             Atom.SetNoImplicit(False)
 1072         
 1073         # Set number of electrons to 0...
 1074         if ClearRadicalElectrons:
 1075             Atom.SetNumRadicalElectrons(0)
 1076         
 1077         Atom.UpdatePropertyCache()
 1078     
 1079     Mol.UpdatePropertyCache()
 1080 
 1081     return Mol
 1082 
 1083 def GetAtomMapIndices(Mol):
 1084     """Get a list of available atom indices corresponding to atom map numbers
 1085     present in a SMILES/SMARTS pattern used for creating a molecule. The list of
 1086     atom indices is sorted in ascending order by atom map numbers.
 1087     
 1088     Arguments:
 1089         Mol (object): RDKit molecule object.
 1090 
 1091     Returns:
 1092         list : List of atom indices sorted in the ascending order of atom map
 1093             numbers or None.
 1094 
 1095     """
 1096     
 1097     return _GetAtomMapIndices(Mol)
 1098 
 1099 def GetAtomMapIndicesAndMapNumbers(Mol):
 1100     """Get lists of available atom indices and atom map numbers present in a
 1101     SMILES/SMARTS pattern used for creating a molecule. Both lists are sorted
 1102     in ascending order by atom map numbers.
 1103     
 1104     Arguments:
 1105         Mol (object): RDKit molecule object.
 1106 
 1107     Returns:
 1108         list : List of atom indices sorted in the ascending order of atom map
 1109             numbers or None.
 1110         list : List of atom map numbers sorted in the ascending order or None.
 1111 
 1112     """
 1113     
 1114     return (_GetAtomMapIndicesAndMapNumbers(Mol))
 1115 
 1116 def MolFromSubstructureMatch(Mol, PatternMol, AtomIndices, FilterByAtomMapNums = False):
 1117     """Generate a RDKit molecule object for a list of matched atom indices
 1118     present in a pattern molecule. The list of atom indices correspond to a
 1119     list retrieved by RDKit function GetSubstructureMatches using SMILES/SMARTS
 1120     pattern. The atom indices are optionally filtered by mapping atom numbers
 1121     to appropriate atom indices during the generation of the molecule.
 1122     For example: [O:1]=[S:2](=[O])[C:3][C:4].
 1123 
 1124     Arguments:
 1125         Mol (object): RDKit molecule object.
 1126         PatternMol (object): RDKit molecule object for a SMILES/SMARTS pattern.
 1127         AtomIndices (list): Atom indices.
 1128         FilterByAtomMapNums (bool): Filter matches by atom map numbers.
 1129 
 1130     Returns:
 1131         object : RDKit molecule object or None.
 1132 
 1133     """
 1134 
 1135     AtomMapIndices = _GetAtomMapIndices(PatternMol) if FilterByAtomMapNums else None
 1136 
 1137     return (_MolFromSubstructureMatch(Mol, PatternMol, AtomIndices, AtomMapIndices))
 1138 
 1139 def MolsFromSubstructureMatches(Mol, PatternMol, AtomIndicesList, FilterByAtomMapNums = False):
 1140     """Generate  a list of RDKit molecule objects for a list containing lists of
 1141     matched atom indices present in a pattern molecule. The list of atom indices
 1142     correspond to a list retrieved by RDKit function GetSubstructureMatches using
 1143     SMILES/SMARTS pattern. The atom indices are optionally filtered by mapping
 1144     atom numbers to appropriate atom indices during the generation of the
 1145     molecule. For example: [O:1]=[S:2](=[O])[C:3][C:4].
 1146 
 1147     Arguments:
 1148         Mol (object): RDKit molecule object.
 1149         PatternMol (object): RDKit molecule object for a SMILES/SMARTS pattern.
 1150         AtomIndicesList (list): A list of lists containing atom indices.
 1151         FilterByAtomMapNums (bool): Filter matches by atom map numbers.
 1152 
 1153     Returns:
 1154         list : A list of lists containg RDKit molecule objects or None.
 1155 
 1156     """
 1157 
 1158     AtomMapIndices = _GetAtomMapIndices(PatternMol) if FilterByAtomMapNums else None
 1159 
 1160     Mols = []
 1161     for AtomIndices in AtomIndicesList:
 1162         Mols.append(_MolFromSubstructureMatch(Mol, PatternMol, AtomIndices, AtomMapIndices))
 1163     
 1164     return Mols if len(Mols) else None
 1165 
 1166 def FilterSubstructureMatchByAtomMapNumbers(Mol, PatternMol, AtomIndices):
 1167     """Filter a list of matched atom indices by map atom numbers present in a
 1168     pattern molecule. The list of atom indices correspond to a list retrieved by
 1169     RDKit function GetSubstructureMatches using SMILES/SMARTS pattern. The
 1170     atom map numbers are mapped to appropriate atom indices during the generation
 1171     of molecules. For example: [O:1]=[S:2](=[O])[C:3][C:4].
 1172     
 1173     Arguments:
 1174         Mol (object): RDKit molecule object.
 1175         PatternMol (object): RDKit molecule object for a SMILES/SMARTS pattern.
 1176         AtomIndices (list): Atom indices.
 1177 
 1178     Returns:
 1179         list : A list of filtered atom indices.
 1180 
 1181     """
 1182     AtomMapIndices = _GetAtomMapIndices(PatternMol)
 1183 
 1184     return _FilterSubstructureMatchByAtomMapNumbers(Mol, PatternMol, AtomIndices, AtomMapIndices)
 1185 
 1186 def FilterSubstructureMatchesByAtomMapNumbers(Mol, PatternMol, AtomIndicesList):
 1187     """Filter a list of lists containing matched atom indices by map atom numbers
 1188     present in a pattern molecule. The list of atom indices correspond to a list retrieved by
 1189     RDKit function GetSubstructureMatches using SMILES/SMARTS pattern. The
 1190     atom map numbers are mapped to appropriate atom indices during the generation
 1191     of molecules. For example: [O:1]=[S:2](=[O])[C:3][C:4].
 1192      
 1193     Arguments:
 1194         Mol (object): RDKit molecule object.
 1195         PatternMol (object): RDKit molecule object for a SMILES/SMARTS pattern.
 1196         AtomIndicesList (list): A list of lists containing atom indices.
 1197 
 1198     Returns:
 1199         list : A list of lists containing filtered atom indices.
 1200 
 1201     """
 1202     AtomMapIndices = _GetAtomMapIndices(PatternMol)
 1203 
 1204     MatchedAtomIndicesList = []
 1205     for AtomIndices in AtomIndicesList:
 1206         MatchedAtomIndicesList.append(_FilterSubstructureMatchByAtomMapNumbers(Mol, PatternMol, AtomIndices, AtomMapIndices))
 1207     
 1208     return MatchedAtomIndicesList
 1209 
 1210 def _MolFromSubstructureMatch(Mol, PatternMol, AtomIndices, AtomMapIndices):
 1211     """Generate a RDKit molecule object for a list of matched atom indices and available
 1212    atom map indices.
 1213     """
 1214 
 1215     if AtomMapIndices is not None:
 1216         MatchedAtomIndices = [AtomIndices[Index] for Index in AtomMapIndices]
 1217     else:
 1218         MatchedAtomIndices = list(AtomIndices)
 1219 
 1220     return _GetMolFromAtomIndices(Mol, MatchedAtomIndices)
 1221 
 1222 def _GetAtomMapIndices(Mol):
 1223     """Get a list of available atom indices corresponding to sorted atom map
 1224     numbers present in a SMILES/SMARTS pattern used for creating a molecule.
 1225     """
 1226     
 1227     AtomMapIndices, AtomMapNumbers = _GetAtomMapIndicesAndMapNumbers(Mol)
 1228     
 1229     return AtomMapIndices
 1230     
 1231 def _GetAtomMapIndicesAndMapNumbers(Mol):
 1232     """Get a list of available atom indices and atom map numbers present
 1233     in  a SMILES/SMARTS pattern used for creating a molecule. Both lists
 1234     are sorted in ascending order by atom map numbers.
 1235     """
 1236 
 1237     # Setup a atom map number to atom indices map..
 1238     AtomMapNumToIndices = {}
 1239     for Atom in Mol.GetAtoms():
 1240         AtomMapNum = Atom.GetAtomMapNum()
 1241         
 1242         if AtomMapNum:
 1243             AtomMapNumToIndices[AtomMapNum] = Atom.GetIdx()
 1244     
 1245     # Setup atom indices corresponding to sorted atom map numbers...
 1246     AtomMapIndices = None
 1247     AtomMapNumbers = None
 1248     if len(AtomMapNumToIndices):
 1249         AtomMapNumbers = sorted(AtomMapNumToIndices)
 1250         AtomMapIndices = [AtomMapNumToIndices[AtomMapNum] for AtomMapNum in AtomMapNumbers]
 1251 
 1252     return (AtomMapIndices, AtomMapNumbers)
 1253 
 1254 def _FilterSubstructureMatchByAtomMapNumbers(Mol, PatternMol, AtomIndices, AtomMapIndices):
 1255     """Filter substructure match atom indices by atom map indices corresponding to
 1256     atom map numbers.
 1257     """
 1258     
 1259     if AtomMapIndices is None:
 1260         return list(AtomIndices)
 1261                                                
 1262     return [AtomIndices[Index] for Index in AtomMapIndices]
 1263 
 1264 def _GetMolFromAtomIndices(Mol, AtomIndices):
 1265     """Generate a RDKit molecule object from atom indices returned by
 1266    substructure search.
 1267     """
 1268 
 1269     BondIndices = []
 1270     for AtomIndex in AtomIndices:
 1271         Atom = Mol.GetAtomWithIdx(AtomIndex)
 1272         
 1273         for AtomNbr in Atom.GetNeighbors():
 1274             AtomNbrIndex = AtomNbr.GetIdx()
 1275             if AtomNbrIndex not in AtomIndices:
 1276                 continue
 1277             
 1278             BondIndex = Mol.GetBondBetweenAtoms(AtomIndex, AtomNbrIndex).GetIdx()
 1279             if BondIndex in BondIndices:
 1280                 continue
 1281                 
 1282             BondIndices.append(BondIndex)
 1283             
 1284     MatchedMol = Chem.PathToSubmol(Mol, BondIndices) if len(BondIndices) else None
 1285     
 1286     return MatchedMol
 1287 
 1288 def ConstrainAndEmbed(mol, core, coreMatchesMol=None, useTethers=True, coreConfId=-1, randomseed=2342, getForceField=AllChem.UFFGetMoleculeForceField, **kwargs):
 1289     """
 1290     The function is a local copy of RDKit fucntion AllChem.ConstrainedEmbed().
 1291     It has been enhanced to support an explicit list of core matches corresponding
 1292     to the matched atom indices in the molecule. The number of matched atom indices
 1293     must be equal to the number of atoms in core molecule.
 1294 
 1295     Arguments:
 1296         mol (object): RDKit molecule object to embed.
 1297         core (object): RDKit molecule to use as a source of constraints.
 1298         coreMatchesMol (list): A list matches atom indices in mol.
 1299         useTethers: (bool) if True, the final conformation will be optimized
 1300             subject to a series of extra forces that pull the matching atoms to
 1301             the positions of the core atoms. Otherwise simple distance
 1302             constraints based on the core atoms will be used in the
 1303             optimization.
 1304         coreConfId (int): ID of the core conformation to use.
 1305         randomSeed (int): Seed for the random number generator
 1306 
 1307     Returns:
 1308         mol (object): RDKit molecule object.
 1309 
 1310     """
 1311     if coreMatchesMol is None:
 1312         match = mol.GetSubstructMatch(core)
 1313         if not match:
 1314             raise ValueError("Molecule doesn't match the core.")
 1315     else:
 1316         if core.GetNumAtoms() != len(coreMatchesMol):
 1317             raise ValueError("Number of atoms, %s, in core molecule must match number of atom indices, %s, specified in the list coreMatchesMol." % (core.GetNumAtoms(), len(coreMatchesMol)))
 1318         # Check specified matched atom indices in  coreMatchesMol and use the match atom
 1319         # indices returned by GetSubstructMatches() for embedding...
 1320         coreMatch = None
 1321         matches = mol.GetSubstructMatches(core)
 1322         for match in matches:
 1323             if len(match) != len(coreMatchesMol):
 1324                 continue
 1325             matchFound = True
 1326             for atomIndex in match:
 1327                 if atomIndex not in coreMatchesMol:
 1328                     matchFound = False
 1329                     break
 1330             if matchFound:
 1331                 coreMatch = match
 1332                 break
 1333         if coreMatch is None:
 1334             raise ValueError("Molecule doesn't match the atom indices specified in the list coreMatchesMol.")
 1335         match = coreMatch
 1336     
 1337     coordMap = {}
 1338     coreConf = core.GetConformer(coreConfId)
 1339     for i, idxI in enumerate(match):
 1340         corePtI = coreConf.GetAtomPosition(i)
 1341         coordMap[idxI] = corePtI
 1342     
 1343     ci = AllChem.EmbedMolecule(mol, coordMap=coordMap, randomSeed=randomseed, **kwargs)
 1344     if ci < 0:
 1345         raise ValueError('Could not embed molecule.')
 1346     
 1347     algMap = [(j, i) for i, j in enumerate(match)]
 1348     
 1349     if not useTethers:
 1350         # clean up the conformation
 1351         ff = getForceField(mol, confId=0)
 1352         for i, idxI in enumerate(match):
 1353             for j in range(i + 1, len(match)):
 1354                 idxJ = match[j]
 1355                 d = coordMap[idxI].Distance(coordMap[idxJ])
 1356                 ff.AddDistanceConstraint(idxI, idxJ, d, d, 100.)
 1357         ff.Initialize()
 1358         n = 4
 1359         more = ff.Minimize()
 1360         while more and n:
 1361             more = ff.Minimize()
 1362             n -= 1
 1363         # rotate the embedded conformation onto the core:
 1364         rms = AllChem.AlignMol(mol, core, atomMap=algMap)
 1365     else:
 1366         # rotate the embedded conformation onto the core:
 1367         rms = AllChem.AlignMol(mol, core, atomMap=algMap)
 1368         ff = getForceField(mol, confId=0)
 1369         conf = core.GetConformer()
 1370         for i in range(core.GetNumAtoms()):
 1371             p = conf.GetAtomPosition(i)
 1372             pIdx = ff.AddExtraPoint(p.x, p.y, p.z, fixed=True) - 1
 1373             ff.AddDistanceConstraint(pIdx, match[i], 0, 0, 100.)
 1374         ff.Initialize()
 1375         n = 4
 1376         more = ff.Minimize(energyTol=1e-4, forceTol=1e-3)
 1377         while more and n:
 1378             more = ff.Minimize(energyTol=1e-4, forceTol=1e-3)
 1379             n -= 1
 1380         # realign
 1381         rms = AllChem.AlignMol(mol, core, atomMap=algMap)
 1382 
 1383     mol.SetProp('EmbedRMS', str(rms))
 1384     return mol
 1385 
 1386 def ReadAndValidateMolecules(FileName, **KeyWordArgs):
 1387     """Read molecules from an input file, validate all molecule objects, and return
 1388     a list of valid and non-valid molecule objects along with their counts.
 1389     
 1390     Arguments:
 1391         FileName (str): Name of a file with complete path.
 1392         **KeyWordArgs (dictionary) : Parameter name and value pairs for reading and
 1393             processing molecules.
 1394 
 1395     Returns:
 1396         list : List of valid RDKit molecule objects.
 1397         int : Number of total molecules in input file. 
 1398         int : Number of valid molecules in input file. 
 1399 
 1400     Notes:
 1401         The file extension is used to determine type of the file and set up an appropriate
 1402         file reader.
 1403 
 1404     """
 1405 
 1406     AllowEmptyMols = True
 1407     if "AllowEmptyMols" in KeyWordArgs:
 1408         AllowEmptyMols = KeyWordArgs["AllowEmptyMols"]
 1409     
 1410     Mols = ReadMolecules(FileName, **KeyWordArgs)
 1411 
 1412     if AllowEmptyMols:
 1413         ValidMols = [Mol for Mol in Mols if Mol is not None]
 1414     else:
 1415         ValidMols = []
 1416         MolCount = 0
 1417         for Mol in Mols:
 1418             MolCount += 1
 1419             if Mol is None:
 1420                 continue
 1421             
 1422             if IsMolEmpty(Mol):
 1423                 MolName = GetMolName(Mol, MolCount)
 1424                 MiscUtil.PrintWarning("Ignoring empty molecule: %s" % MolName)
 1425                 continue
 1426             
 1427             ValidMols.append(Mol)
 1428             
 1429     MolCount = len(Mols)
 1430     ValidMolCount = len(ValidMols)
 1431 
 1432     return (ValidMols, MolCount, ValidMolCount)
 1433 
 1434 def ReadMolecules(FileName, **KeyWordArgs):
 1435     """Read molecules from an input file without performing any validation
 1436     and creation of molecule objects.
 1437     
 1438     Arguments:
 1439         FileName (str): Name of a file with complete path.
 1440         **KeyWordArgs (dictionary) : Parameter name and value pairs for reading and
 1441             processing molecules.
 1442 
 1443     Returns:
 1444         list : List of RDKit molecule objects.
 1445 
 1446     Notes:
 1447         The file extension is used to determine type of the file and set up an appropriate
 1448         file reader.
 1449 
 1450     """
 1451 
 1452     # Set default values for possible arguments...
 1453     ReaderArgs = {"Sanitize": True, "RemoveHydrogens": True, "StrictParsing": True,  "SMILESDelimiter" : ' ', "SMILESColumn": 1, "SMILESNameColumn": 2, "SMILESTitleLine": True }
 1454 
 1455     # Set specified values for possible arguments...
 1456     for Arg in ReaderArgs:
 1457         if Arg in KeyWordArgs:
 1458             ReaderArgs[Arg] = KeyWordArgs[Arg]
 1459 
 1460     # Modify specific valeus for SMILES...
 1461     if MiscUtil.CheckFileExt(FileName, "smi csv tsv txt"):
 1462         Args = ["Sanitize", "SMILESTitleLine"]
 1463         for Arg in Args:
 1464             if ReaderArgs[Arg] is True:
 1465                 ReaderArgs[Arg] = 1
 1466             else:
 1467                 ReaderArgs[Arg] = 0
 1468     
 1469     Mols = []
 1470     if MiscUtil.CheckFileExt(FileName, "sdf sd"):
 1471         return ReadMoleculesFromSDFile(FileName, ReaderArgs["Sanitize"], ReaderArgs["RemoveHydrogens"], ReaderArgs['StrictParsing'])
 1472     elif MiscUtil.CheckFileExt(FileName, "mol"):
 1473         return ReadMoleculesFromMolFile(FileName, ReaderArgs["Sanitize"], ReaderArgs["RemoveHydrogens"], ReaderArgs['StrictParsing'])
 1474     elif MiscUtil.CheckFileExt(FileName, "mol2"):
 1475         return ReadMoleculesFromMol2File(FileName, ReaderArgs["Sanitize"], ReaderArgs["RemoveHydrogens"])
 1476     elif MiscUtil.CheckFileExt(FileName, "pdb"):
 1477         return ReadMoleculesFromPDBFile(FileName, ReaderArgs["Sanitize"], ReaderArgs["RemoveHydrogens"])
 1478     elif MiscUtil.CheckFileExt(FileName, "smi txt csv tsv"):
 1479         SMILESColumnIndex = ReaderArgs["SMILESColumn"] - 1
 1480         SMILESNameColumnIndex = ReaderArgs["SMILESNameColumn"] - 1
 1481         return ReadMoleculesFromSMILESFile(FileName, ReaderArgs["SMILESDelimiter"], SMILESColumnIndex, SMILESNameColumnIndex, ReaderArgs["SMILESTitleLine"], ReaderArgs["Sanitize"])
 1482     else:
 1483         MiscUtil.PrintWarning("RDKitUtil.ReadMolecules: Non supported file type: %s" % FileName)
 1484     
 1485     return Mols
 1486 
 1487 def ReadMoleculesFromSDFile(FileName, Sanitize = True, RemoveHydrogens = True, StrictParsing = True):
 1488     """Read molecules from a SD file.
 1489     
 1490     Arguments:
 1491         FileName (str): Name of a file with complete path.
 1492         Sanitize (bool): Sanitize molecules.
 1493         RemoveHydrogens (bool): Remove hydrogens from molecules.
 1494         StrictParsing (bool): Perform strict parsing.
 1495 
 1496     Returns:
 1497         list : List of RDKit molecule objects.
 1498 
 1499     """
 1500     return  Chem.SDMolSupplier(FileName, sanitize = Sanitize, removeHs = RemoveHydrogens, strictParsing = StrictParsing)
 1501 
 1502 def ReadMoleculesFromMolFile(FileName, Sanitize = True, RemoveHydrogens = True, StrictParsing = True):
 1503     """Read molecule from a MDL Mol file.
 1504     
 1505     Arguments:
 1506         FileName (str): Name of a file with complete path.
 1507         Sanitize (bool): Sanitize molecules.
 1508         RemoveHydrogens (bool): Remove hydrogens from molecules.
 1509         StrictParsing (bool): Perform strict parsing.
 1510 
 1511     Returns:
 1512         list : List of RDKit molecule objects.
 1513 
 1514     """
 1515     
 1516     Mols = []
 1517     Mols.append(Chem.MolFromMolFile(FileName, sanitize = Sanitize, removeHs = RemoveHydrogens, strictParsing = StrictParsing))
 1518     return Mols
 1519 
 1520 
 1521 def ReadMoleculesFromMol2File(FileName, Sanitize = True, RemoveHydrogens = True):
 1522     """Read molecules from a Tripos Mol2  file. The first call to the function
 1523     creates and returns  a generator object using Python yield statement. The
 1524     molecules are created during the subsequent iteration by the generator object.
 1525 
 1526     Arguments:
 1527         FileName (str): Name of a file with complete path.
 1528         Sanitize (bool): Sanitize molecules.
 1529         RemoveHydrogens (bool): Remove hydrogens from molecules.
 1530 
 1531     Returns:
 1532         list : A Python generator object for iterating over the molecules.
 1533 
 1534     """
 1535 
 1536     return _Mol2MolSupplier(FileName, Sanitize, RemoveHydrogens)
 1537 
 1538 def _Mol2MolSupplier(FileName, Sanitize = True, RemoveHydrogens = True):
 1539     """Read molecules from a Tripos Mol2  file."""
 1540 
 1541     fh = open(FileName, 'r')
 1542     
 1543     FirstMol = True
 1544     ProcessingMol = False
 1545     
 1546     for Line in fh:
 1547         if re.match("^#", Line, re.I):
 1548             continue
 1549         
 1550         if re.match("^@<TRIPOS>MOLECULE", Line, re.I):
 1551             ProcessingMol = True
 1552             
 1553             if FirstMol:
 1554                 FirstMol = False
 1555                 
 1556                 MolLines = []
 1557                 MolLines.append(Line)
 1558                 continue
 1559             
 1560             # Process lines for existing molecule...
 1561             MolBlock = "".join(MolLines)
 1562             
 1563             Mol = Chem.MolFromMol2Block(MolBlock, sanitize = Sanitize, removeHs = RemoveHydrogens)
 1564             yield Mol
 1565             
 1566             # Track lines for next molecule...
 1567             MolLines = []
 1568             MolLines.append(Line)
 1569             continue
 1570         
 1571         if not ProcessingMol:
 1572             continue
 1573         
 1574         MolLines.append(Line)
 1575         
 1576     fh.close
 1577 
 1578     # Process last molecule...
 1579     if len(MolLines):
 1580         MolBlock = "".join(MolLines)
 1581         Mol = Chem.MolFromMol2Block(MolBlock, sanitize = Sanitize, removeHs = RemoveHydrogens)
 1582         yield Mol
 1583     
 1584 def ReadMoleculesFromPDBFile(FileName, Sanitize = True, RemoveHydrogens = True):
 1585     """Read molecule from a PDB  file.
 1586     
 1587     Arguments:
 1588         FileName (str): Name of a file with complete path.
 1589         Sanitize (bool): Sanitize molecules.
 1590         RemoveHydrogens (bool): Remove hydrogens from molecules.
 1591 
 1592     Returns:
 1593         list : List of RDKit molecule objects.
 1594 
 1595     """
 1596     
 1597     Mols = []
 1598     Mols.append(Chem.MolFromPDBFile(FileName,  sanitize = Sanitize, removeHs = RemoveHydrogens))
 1599     return Mols
 1600 
 1601 def ReadMoleculesFromSMILESFile(FileName, SMILESDelimiter = ' ', SMILESColIndex = 0, SMILESNameColIndex = 1, SMILESTitleLine = 1, Sanitize = 1):
 1602     """Read molecules from a SMILES file.
 1603     
 1604     Arguments:
 1605         SMILESDelimiter (str): Delimiter for parsing SMILES line
 1606         SMILESColIndex (int): Column index containing SMILES string.
 1607         SMILESNameColIndex (int): Column index containing molecule name.
 1608         SMILESTitleLine (int): Flag to indicate presence of title line.
 1609         Sanitize (int): Sanitize molecules.
 1610 
 1611     Returns:
 1612         list : List of RDKit molecule objects.
 1613 
 1614     """
 1615     
 1616     return  Chem.SmilesMolSupplier(FileName, delimiter = SMILESDelimiter, smilesColumn = SMILESColIndex, nameColumn = SMILESNameColIndex, titleLine = SMILESTitleLine, sanitize = Sanitize)
 1617 
 1618 def MoleculesWriter(FileName, **KeyWordArgs):
 1619     """Set up a molecule writer.
 1620     
 1621     Arguments:
 1622         FileName (str): Name of a file with complete path.
 1623         **KeyWordArgs (dictionary) : Parameter name and value pairs for writing and
 1624             processing molecules.
 1625 
 1626     Returns:
 1627         RDKit object : Molecule writer.
 1628 
 1629     Notes:
 1630         The file extension is used to determine type of the file and set up an appropriate
 1631         file writer.
 1632 
 1633     """
 1634     
 1635     # Set default values for possible arguments...
 1636     WriterArgs = {"Compute2DCoords" : False, "Kekulize": True, "ForceV3000": False, "SMILESKekulize": False, "SMILESDelimiter" : ' ', "SMILESIsomeric": True, "SMILESTitleLine": True, "SMILESMolName": True}
 1637 
 1638     # Set specified values for possible arguments...
 1639     for Arg in WriterArgs:
 1640         if Arg in KeyWordArgs:
 1641             WriterArgs[Arg] = KeyWordArgs[Arg]
 1642     
 1643     Writer = None
 1644     if MiscUtil.CheckFileExt(FileName, "sdf sd"):
 1645         Writer = Chem.SDWriter(FileName)
 1646         Writer.SetKekulize(WriterArgs["Kekulize"])
 1647         Writer.SetForceV3000(WriterArgs["ForceV3000"])
 1648     elif MiscUtil.CheckFileExt(FileName, "pdb"):
 1649         Writer = Chem.PDBWriter(FileName)
 1650     elif MiscUtil.CheckFileExt(FileName, "smi"):
 1651         # Text for the name column in the title line. Blank indicates not to include name column
 1652         # in the output file...
 1653         NameHeader = 'Name' if WriterArgs["SMILESMolName"] else ''
 1654         Writer = Chem.SmilesWriter(FileName, delimiter = WriterArgs["SMILESDelimiter"], nameHeader = NameHeader, includeHeader = WriterArgs["SMILESTitleLine"],  isomericSmiles = WriterArgs["SMILESIsomeric"], kekuleSmiles = WriterArgs["SMILESKekulize"])
 1655     else:
 1656         MiscUtil.PrintWarning("RDKitUtil.WriteMolecules: Non supported file type: %s" % FileName)
 1657     
 1658     return Writer
 1659     
 1660 def WriteMolecules(FileName, Mols, **KeyWordArgs):
 1661     """Write molecules to an output file.
 1662     
 1663     Arguments:
 1664         FileName (str): Name of a file with complete path.
 1665         Mols (list): List of RDKit molecule objects. 
 1666         **KeyWordArgs (dictionary) : Parameter name and value pairs for writing and
 1667             processing molecules.
 1668 
 1669     Returns:
 1670         int : Number of total molecules.
 1671         int : Number of processed molecules written to output file.
 1672 
 1673     Notes:
 1674         The file extension is used to determine type of the file and set up an appropriate
 1675         file writer.
 1676 
 1677     """
 1678     
 1679     Compute2DCoords = False
 1680     if "Compute2DCoords" in KeyWordArgs:
 1681         Compute2DCoords = KeyWordArgs["Compute2DCoords"]
 1682     
 1683     SetSMILESMolProps = KeyWordArgs["SetSMILESMolProps"] if "SetSMILESMolProps" in KeyWordArgs else False
 1684         
 1685     MolCount = 0
 1686     ProcessedMolCount = 0
 1687     
 1688     Writer = MoleculesWriter(FileName, **KeyWordArgs)
 1689     
 1690     if Writer is None:
 1691         return (MolCount, ProcessedMolCount)
 1692     
 1693     FirstMol = True
 1694     for Mol in Mols:
 1695         MolCount += 1
 1696         if Mol is None:
 1697             continue
 1698 
 1699         if FirstMol:
 1700             FirstMol = False
 1701             if SetSMILESMolProps:
 1702                 SetWriterMolProps(Writer, Mol)
 1703                 
 1704         ProcessedMolCount += 1
 1705         if Compute2DCoords:
 1706             AllChem.Compute2DCoords(Mol)
 1707         
 1708         Writer.write(Mol)
 1709     
 1710     Writer.close()
 1711     
 1712     return (MolCount, ProcessedMolCount)
 1713 
 1714 def SetWriterMolProps(Writer, Mol):
 1715     """Setup molecule properties for a writer to output.
 1716     
 1717     Arguments:
 1718         Writer (object): RDKit writer object.
 1719         Mol (object): RDKit molecule object.
 1720 
 1721     Returns:
 1722         object : Writer object.
 1723 
 1724     """
 1725     PropNames = list(Mol.GetPropNames())
 1726     if len(PropNames):
 1727         Writer.SetProps(PropNames)
 1728         
 1729     return Writer
 1730