def _createSireSystem(molecules): """Create a Sire system from a Molecules object or a list of Molecule objects. Parameters ---------- molecules : :class:`Molecules <BioSimSpace._SireWrappers.Molecules>` \ [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`] Returns ------- system : Sire.System.System A Sire system object. """ # Create an empty Sire System. system = _SireSystem.System("BioSimSpace System") # Create a new "all" molecule group. molgrp = _SireMol.MoleculeGroup("all") # Add the molecules to the group. if type(molecules) is _Molecules: molgrp.add(molecules._sire_object) else: for mol in molecules: molgrp.add(mol._sire_object) # Add the molecule group to the system. system.add(molgrp) return system
def _createSireSystem(molecules): """Create a Sire system from a list of molecules. Parameters ---------- molecules : [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`] Returns ------- system : Sire.System.System A Sire system object. """ # Create an empty Sire System. system = _SireSystem.System("BioSimSpace System") # Create a new "all" molecule group. molgrp = _SireMol.MoleculeGroup("all") # Add the molecules to the group. for mol in molecules: molgrp.add(mol._sire_molecule) # Add the molecule group to the system. system.add(molgrp) return system
def molecule(self, index=0, gui=True): """View a specific molecule. Parameters ---------- index : int The molecule index. gui : bool Whether to display the gui. """ # Make sure we're running inside a Jupyter notebook. if not _is_notebook: return None # Check that the index is an integer. if type(index) is not int: raise TypeError("'index' must be of type 'int'") # Get the latest system from the process. if self._is_process: system = self._handle.getSystem()._getSireObject() # No system. if system is None: return else: system = self._handle # Extract the molecule numbers. molnums = system.molNums() # Make sure the index is valid. if index < 0 or index > len(molnums): raise ValueError("Molecule index is out of range!") # Create a new system and add a single molecule. s = _SireSystem.System("BioSimSpace System") m = _SireMol.MoleculeGroup("all") m.add(system[molnums[index]]) s.add(m) # Create and return the view. return self._create_view(s, gui=gui)
def run(self, molecule, work_dir=None, queue=None): """Run the parameterisation protocol. Parameters ---------- molecule : BioSimSpace._SireWrappers.Molecule The molecule to apply the parameterisation protocol to. work_dir : str The working directory. queue : queue.Queue The thread queue is which this method has been run. Returns ------- molecule : BioSimSpace._SireWrappers.Molecule The parameterised molecule. """ if type(molecule) is not _Molecule: raise TypeError( "'molecule' must be of type 'BioSimSpace._SireWrappers.Molecule'" ) if type(work_dir) is not None and type(work_dir) is not str: raise TypeError("'work_dir' must be of type 'str'") if type(queue) is not None and type(queue) is not _queue.Queue: raise TypeError("'queue' must be of type 'queue.Queue'") # Set work_dir to the current directory. if work_dir is None: work_dir = _os.getcwd() # Create the file prefix. prefix = work_dir + "/" # Create a copy of the molecule. new_mol = molecule.copy() # Use the net molecular charge passed as an option. if self._net_charge is not None: charge = self._net_charge else: # The user will likely have passed a bare PDB or Mol2 file. # Antechamber expects the molecule to be uncharged, or integer # charged (where the charge, or number of electrons, is passed with # the -nc flag). # Get the total charge on the molecule. if "charge" in self._property_map: _property_map = {"charge": self._property_map["charge"]} prop = self._property_map["charge"] else: _property_map = {"charge": "charge"} prop = "charge" # The molecule has a charge property. if new_mol._getSireObject().hasProperty(prop): charge = new_mol.charge(property_map=_property_map).magnitude() # Charge is non-integer, try to fix it. if abs(round(charge) - charge) > 0: new_mol._fixCharge(property_map=_property_map) charge = round(charge) else: charge = None # Only try "formal_charge" when "charge" is missing. Unlikely to have # both if this is a bare molecule, but the user could be re-parameterising # an existing molecule. if charge is None: # Get the total formal charge on the molecule. if "formal_charge" in self._property_map: _property_map = { "charge": self._property_map["formal_charge"] } prop = self._property_map["charge"] else: _property_map = {"charge": "formal_charge"} prop = "formal_charge" if new_mol._getSireObject().hasProperty(prop): charge = new_mol.charge( property_map=_property_map).magnitude() # Compute the formal charge ourselves to check that it is consistent. formal_charge = _formalCharge(molecule).magnitude() if charge != formal_charge: _warnings.warn( "The formal charge on the molecule is %d " "but we estimate it to be %d" % (charge, formal_charge)) else: msg = ( "The molecule has no 'charge' or 'formal_charge' information, and " "no 'net_charge' option has been passed. You can use the " "'BioSimSpace.Parameters.formalCharge' function to compute the " "formal charge") raise _ParameterisationError(msg) # Create a new system and molecule group. s = _SireSystem.System("BioSimSpace System") m = _SireMol.MoleculeGroup("all") # Add the molecule. m.add(new_mol._getSireObject()) s.add(m) # Write the system to a PDB file. try: pdb = _SireIO.PDB2(s) pdb.writeToFile(prefix + "antechamber.pdb") except Exception as e: msg = "Failed to write system to 'PDB' format." if _isVerbose(): raise IOError(msg) from e else: raise IOError(msg) from None # Generate the Antechamber command. command = ("%s -at %d -i antechamber.pdb -fi pdb " + "-o antechamber.mol2 -fo mol2 -c %s -s 2 -nc %d") % ( _protocol._antechamber_exe, self._version, self._charge_method.lower(), charge) with open(prefix + "README.txt", "w") as file: # Write the command to file. file.write("# Antechamber was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open(prefix + "antechamber.out", "w") stderr = open(prefix + "antechamber.err", "w") # Run Antechamber as a subprocess. proc = _subprocess.run(command, cwd=work_dir, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # Antechamber doesn't return sensible error codes, so we need to check that # the expected output was generated. if _os.path.isfile(prefix + "antechamber.mol2"): # Run parmchk to check for missing parameters. command = ("%s -s %d -i antechamber.mol2 -f mol2 " + "-o antechamber.frcmod") % (_protocol._parmchk_exe, self._version) with open(prefix + "README.txt", "a") as file: # Write the command to file. file.write("\n# ParmChk was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open(prefix + "parmchk.out", "w") stderr = open(prefix + "parmchk.err", "w") # Run parmchk as a subprocess. proc = _subprocess.run(command, cwd=work_dir, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # The frcmod file was created. if _os.path.isfile(prefix + "antechamber.frcmod"): # Now call tLEaP using the partially parameterised molecule and the frcmod file. # tLEap will run in the same working directory, using the Mol2 file generated by # Antechamber. # Try to find a force field file. if self._version == 1: ff = _protocol._find_force_field("gaff") else: ff = _protocol._find_force_field("gaff2") # Write the LEaP input file. with open(prefix + "leap.txt", "w") as file: file.write("source %s\n" % ff) file.write("mol = loadMol2 antechamber.mol2\n") file.write("loadAmberParams antechamber.frcmod\n") file.write("saveAmberParm mol leap.top leap.crd\n") file.write("quit") # Generate the tLEaP command. command = "%s -f leap.txt" % _protocol._tleap_exe with open(prefix + "README.txt", "a") as file: # Write the command to file. file.write( "\n# tLEaP was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open(prefix + "tleap.out", "w") stderr = open(prefix + "tleap.err", "w") # Run tLEaP as a subprocess. proc = _subprocess.run(command, cwd=work_dir, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # tLEaP doesn't return sensible error codes, so we need to check that # the expected output was generated. if _os.path.isfile(prefix + "leap.top") and _os.path.isfile(prefix + "leap.crd"): # Load the parameterised molecule. try: par_mol = _Molecule( _IO.readMolecules([ prefix + "leap.top", prefix + "leap.crd" ])._getSireObject()[_SireMol.MolIdx(0)]) except Exception as e: msg = "Failed to read molecule from: 'leap.top', 'leap.crd'" if _isVerbose(): raise IOError(msg) from e else: raise IOError(msg) from None # Make the molecule 'mol' compatible with 'par_mol'. This will create # a mapping between atom indices in the two molecules and add all of # the new properties from 'par_mol' to 'mol'. new_mol._makeCompatibleWith( par_mol, property_map=self._property_map, overwrite=True, verbose=False) # Record the forcefield used to parameterise the molecule. new_mol._forcefield = ff else: raise _ParameterisationError("tLEaP failed!") else: raise _ParameterisationError("Parmchk failed!") else: raise _ParameterisationError("Antechamber failed!") if queue is not None: queue.put(new_mol) return new_mol
def molecules(self, indices=None, gui=True): """View specific molecules. Parameters ---------- indices : [int], range A list of molecule indices. gui : bool Whether to display the gui. """ # Make sure we're running inside a Jupyter notebook. if not _is_notebook: return None # Return a view of the entire system. if indices is None: return self.system(gui=gui) # Convert single indices to a list. if isinstance(indices, range): indices = list(indices) elif type(indices) is not list: indices = [indices] # Check that the indices is a list of integers. if not all(isinstance(x, int) for x in indices): raise TypeError("'indices' must be a 'list' of type 'int'") # Get the latest system from the process. if self._is_process: system = self._handle.getSystem()._getSireObject() # No system. if system is None: return else: system = self._handle # Extract the molecule numbers. molnums = system.molNums() # Create a new system. s = _SireSystem.System("BioSimSpace System") m = _SireMol.MoleculeGroup("all") # Loop over all of the indices. for index in indices: if index < 0 or index > len(molnums): raise ValueError("Molecule index is out of range!") # Add the molecule. m.add(system[molnums[index]]) # Add all of the molecules to the system. s.add(m) # Create and return the view. return self._create_view(s, gui=gui)
def saveMolecules(filebase, system, fileformat, property_map={}): """Save a molecular system to file. Parameters ---------- filebase : str The base name of the output file. system : :class:`System <BioSimSpace._SireWrappers.System>`, \ :class:`Molecule< BioSimSpace._SireWrappers.Molecule>` [ BioSimSpace._SireWrappers.Molecule ] The molecular system. fileformat : str, [str] The file format (or formats) to save to. property_map : dict A dictionary that maps system "properties" to their user defined values. This allows the user to refer to properties with their own naming scheme, e.g. { "charge" : "my-charge" } Returns ------- files : [str] The list of files that were generated. Examples -------- Load a molecular system from AMBER coordinate and topology files then try to save it to all supported file formats. >>> import BioSimSpace as BSS >>> system = BSS.IO.readMolecules(["ala.rst7", "ala.prm7"]) >>> for format in BSS.IO.fileFormats(): ... try: ... BSS.IO.saveMolecules("test", system, format) ... except: ... print("Could not convert to format: '%s'" % format) Load a molecular system from AMBER coordinate and topology files then try to save it to GROMACS format, mapping and un-mapping the charge property along the way. >>> import BioSimSpace as BSS >>> system = BSS.IO.readMolecules(["ala.rst7", "ala.prm7"], property_map={"charge" : "my-charge"}) >>> BSS.IO.saveMolecules("test", system, ["gro87", "grotop"], property_map={"charge" : "my-charge"}) """ global _has_gmx_warned if _gromacs_path is None and not _has_gmx_warned: _warn( "BioSimSpace.IO: Please install GROMACS (http://www.gromacs.org) " "for GROMACS topology file support.") _has_gmx_warned = True # Check that the filebase is a string. if type(filebase) is not str: raise TypeError("'filebase' must be of type 'str'") # Check that that the system is of the correct type. # A System object. if type(system) is _System: pass # A Molecule object. elif type(system) is _Molecule: system = [system] # A list of Molecule objects. elif type(system) is list and all( isinstance(x, _Molecule) for x in system): pass # Invalid type. else: raise TypeError( "'system' must be of type 'BioSimSpace.SireWrappers.System', " "'BioSimSpace._SireWrappers.Molecule, or a list of " "'BiSimSpace._SireWrappers.Molecule' types.") # Check that fileformat argument is of the correct type. # Convert to a list if a single string is passed. # We split on ',' since the user might pass system.fileFormats() as the argument. if type(fileformat) is str: fileformat = fileformat.split(",") # Lists and tuples are okay! elif type(fileformat) is list: pass elif type(fileformat) is tuple: pass # Invalid. else: raise TypeError( "'fileformat' must be a 'str' or a 'list' of 'str' types.") # Make sure all items in list or tuple are strings. if not all(isinstance(x, str) for x in fileformat): raise TypeError( "'fileformat' must be a 'str' or a 'list' of 'str' types.") # Make a list of the matched file formats. formats = [] # Make sure that all of the formats are valid. for format in fileformat: try: f = _formats_dict[format.replace(" ", "").upper()][0] formats.append(f) except KeyError: raise ValueError("Unsupported file format '%s'. Supported formats " "are: %s." % (format, str(_formats))) # Validate the map. if type(property_map) is not dict: raise TypeError("'property_map' must be of type 'dict'") # Copy the map. _property_map = property_map.copy() # Add the GROMACS topology file path. if _gromacs_path is not None and ("GROMACS_PATH" not in _property_map): _property_map["GROMACS_PATH"] = _gromacs_path # We have a list of molecules. Create a new system and add each molecule. if type(system) is list: # Create a Sire system and molecule group. s = _SireSystem.System("BioSimSpace System") m = _SireMol.MoleculeGroup("all") # Add all of the molecules to the group. for molecule in system: m.add(molecule._getSireMolecule()) # Add the molecule group to the system. s.add(m) # Wrap the system. system = _System(s) # Get the directory name. dirname = _os.path.dirname(filebase) # If the user has passed a directory, make sure that is exists. if _os.path.basename(filebase) != filebase: # Create the directory if it doesn't already exist. if not _os.path.isdir(dirname): _os.makedirs(dirname, exist_ok=True) # Store the current working directory. dir = _os.getcwd() # Change to the working directory for the process. # This avoid problems with relative paths. if dirname != "": _os.chdir(dirname) # A list of the files that have been written. files = [] # Save the system using each file format. for format in formats: # Add the file format to the property map. _property_map["fileformat"] = _SireBase.wrap(format) # Write the file. try: file = _SireIO.MoleculeParser.save(system._getSireSystem(), filebase, _property_map) files += file except: if dirname != "": _os.chdir(dir) raise IOError("Failed to save system to format: '%s'" % format) from None # Change back to the original directory. if dirname != "": _os.chdir(dir) # Return the list of files. return files
def _run_pdb2gmx(self, molecule, work_dir): """Run using pdb2gmx. Parameters ---------- molecule : BioSimSpace._SireWrappers.Molecule The molecule to apply the parameterisation protocol to. work_dir : str The working directory. """ # A list of supported force fields, mapping to their GROMACS ID string. # GROMACS supports a sub-set of the AMBER force fields. supported_ff = { "ff99": "amber99", "ff99SB": "amber99sb", "ff03": "amber03" } if self._forcefield not in supported_ff: raise _IncompatibleError( "'pdb2gmx' does not support the '%s' force field." % self._forcefield) # Create a new system and molecule group. s = _SireSystem.System("BioSimSpace System") m = _SireMol.MoleculeGroup("all") # Add the molecule. m.add(molecule._getSireObject()) s.add(m) # Create the file prefix. prefix = work_dir + "/" # Write the system to a PDB file. try: pdb = _SireIO.PDB2(s, self._property_map) pdb.writeToFile(prefix + "input.pdb") except Exception as e: msg = "Failed to write system to 'PDB' format." if _isVerbose(): raise IOError(msg) from e else: raise IOError(msg) from None # Generate the pdb2gmx command. command = "%s pdb2gmx -f input.pdb -o output.gro -p output.top -ignh -ff %s -water none" \ % (_gmx_exe, supported_ff[self._forcefield]) with open(prefix + "README.txt", "w") as file: # Write the command to file. file.write("# pdb2gmx was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open(prefix + "pdb2gmx.out", "w") stderr = open(prefix + "pdb2gmx.err", "w") # Run pdb2gmx as a subprocess. proc = _subprocess.run(command, cwd=work_dir, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # Check for the expected output. if _os.path.isfile(prefix + "output.gro") and _os.path.isfile(prefix + "output.top"): return ["output.gro", "output.top"] else: raise _ParameterisationError("pdb2gmx failed!")
def _run_tleap(self, molecule, work_dir): """Run using tLEaP. Parameters ---------- molecule : BioSimSpace._SireWrappers.Molecule The molecule to apply the parameterisation protocol to. work_dir : str The working directory. """ # Create a new system and molecule group. s = _SireSystem.System("BioSimSpace System") m = _SireMol.MoleculeGroup("all") # Add the molecule. m.add(molecule._getSireObject()) s.add(m) # Create the file prefix. prefix = work_dir + "/" # Write the system to a PDB file. try: pdb = _SireIO.PDB2(s, self._property_map) pdb.writeToFile(prefix + "leap.pdb") except Exception as e: msg = "Failed to write system to 'PDB' format." if _isVerbose(): raise IOError(msg) from e else: raise IOError(msg) from None # Try to find a force field file. ff = _find_force_field(self._forcefield) # Write the LEaP input file. with open(prefix + "leap.txt", "w") as file: file.write("source %s\n" % ff) file.write("mol = loadPdb leap.pdb\n") file.write("saveAmberParm mol leap.top leap.crd\n") file.write("quit") # Generate the tLEaP command. command = "%s -f leap.txt" % _tleap_exe with open(prefix + "README.txt", "w") as file: # Write the command to file. file.write("# tLEaP was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open(prefix + "tleap.out", "w") stderr = open(prefix + "tleap.err", "w") # Run tLEaP as a subprocess. proc = _subprocess.run(command, cwd=work_dir, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # tLEaP doesn't return sensible error codes, so we need to check that # the expected output was generated. if _os.path.isfile(prefix + "leap.top") and _os.path.isfile(prefix + "leap.crd"): return ["leap.top", "leap.crd"] else: raise _ParameterisationError("tLEaP failed!")