def _versionString(): """Return a nicely formatted string that describes the current Sire version""" import Sire.Base as Base return """Sire %s [%s|%s, %s]""" % \ (Base.getReleaseVersion(), Base.getRepositoryBranch(), Config.sire_repository_version[0:7], ["unclean", "clean"][Base.getRepositoryVersionIsClean()])
def help(name): """Print the help message for the named node. Parameters ---------- name : str The name of the node. """ if type(name) is not str: raise TypeError("'name' must be of type 'str'.") # Apped the node directory name. full_name = _node_dir + "/" + name # Make sure the node exists. if not _os.path.isfile(full_name): if not _os.path.isfile(full_name + ".py"): raise ValueError("Cannot find node: '%s'. " % name + "Run 'Node.list()' to see available nodes!") else: full_name += ".py" # Create the command. command = "%s/python %s --help" % (_SireBase.getBinDir(), full_name) # Run the node as a subprocess. proc = _subprocess.run(command, shell=True)
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>` \ :class:`Molecule< BioSimSpace._SireWrappers.Molecules>` 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(system) elif type(system) is _Molecules: system = system.toSystem() # A list of Molecule objects. elif type(system) is list and all( isinstance(x, _Molecule) for x in system): system = _System(system) # Invalid type. else: raise TypeError( "'system' must be of type 'BioSimSpace.SireWrappers.System', " "'BioSimSpace._SireWrappers.Molecule, 'BioSimSpace._SireWrappers.Molecules' " "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 # 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._getSireObject(), filebase, _property_map) files += file except Exception as e: if dirname != "": _os.chdir(dir) msg = "Failed to save system to format: '%s'" % format if _isVerbose(): raise IOError(msg) from e else: raise IOError(msg) from None # Change back to the original directory. if dirname != "": _os.chdir(dir) # Return the list of files. return files
from Sire import Maths as _SireMaths from Sire import Mol as _SireMol from Sire import Units as _SireUnits from BioSimSpace import _isVerbose from BioSimSpace._Exceptions import AlignmentError as _AlignmentError from BioSimSpace._Exceptions import MissingSoftwareError as _MissingSoftwareError from BioSimSpace._SireWrappers import Molecule as _Molecule from BioSimSpace import IO as _IO from BioSimSpace import Units as _Units from BioSimSpace import _Utils as _Utils # Try to find the FKCOMBU program from KCOMBU: http://strcomp.protein.osaka-u.ac.jp/kcombu try: _fkcombu_exe = _SireBase.findExe("fkcombu").absoluteFilePath() except: _fkcombu_exe = None def matchAtoms(molecule0, molecule1, scoring_function="rmsd_align", matches=1, return_scores=False, prematch={}, timeout=5 * _Units.Time.second, property_map0={}, property_map1={}): """Find mappings between atom indices in molecule0 to those in molecule1. Molecules are aligned using a Maximum Common Substructure (MCS) search.
def _solvate(molecule, box, shell, model, num_point, ion_conc, is_neutral, work_dir=None, property_map={}): """Internal function to add solvent using 'gmx solvate'. Parameters ---------- molecule : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`, \ :class:`System <BioSimSpace._SireWrappers.System>` A molecule, or system of molecules. box : [:class:`Length <BioSimSpace.Types.Length>`] A list containing the box size in each dimension. shell : :class:`Length` <BioSimSpace.Types.Length>` Thickness of the water shell around the solute. model : str The name of the water model. num_point : int The number of atoms in the water model. ion_conc : float The ion concentration in (mol per litre). is_neutral : bool Whether to neutralise the system. work_dir : str The working directory for the process. 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 ------- system : :class:`System <BioSimSpace._SireWrappers.System>` The solvated system. """ if molecule is not None: # Store the centre of the molecule. center = molecule._getAABox(property_map).center() # Work out the vector from the centre of the molecule to the centre of the # water box, converting the distance in each direction to Angstroms. vec = [] for x, y in zip(box, center): vec.append(0.5 * x.angstroms().magnitude() - y) # Translate the molecule. This allows us to create a water box # around the molecule. molecule.translate(vec, property_map) if type(molecule) is _System: # Reformat all of the water molecules so that they match the # expected GROMACS topology template. waters = _SireIO.setGromacsWater( molecule._sire_object.search("water"), model) # Convert to a BioSimSpace molecules container. waters = _Molecules(waters.toMolecules()) # Remove the old water molecules then add those with the updated topology. molecule.removeWaterMolecules() molecule.addMolecules(waters) # Create a temporary working directory and store the directory name. if work_dir is None: tmp_dir = _tempfile.TemporaryDirectory() work_dir = tmp_dir.name # Run the solvation in the working directory. with _Utils.cd(work_dir): # Create the gmx command. if num_point == 3: mod = "spc216" else: mod = model command = "%s solvate -cs %s" % (_gmx_exe, mod) if molecule is not None: # Write the molecule/system to a GRO files. _IO.saveMolecules("input", molecule, "gro87") _os.rename("input.gro87", "input.gro") # Update the command. command += " -cp input.gro" # Add the box information. if box is not None: command += " -box %f %f %f" % (box[0].nanometers().magnitude(), box[1].nanometers().magnitude(), box[2].nanometers().magnitude()) # Add the shell information. if shell is not None: command += " -shell %f" % shell.nanometers().magnitude() # Just add box information. else: command += " -box %f %f %f" % (box[0].nanometers().magnitude(), box[1].nanometers().magnitude(), box[2].nanometers().magnitude()) # Add the output file. command += " -o output.gro" with open("README.txt", "w") as file: # Write the command to file. file.write("# gmx solvate was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open("solvate.out", "w") stderr = open("solvate.err", "w") # Run gmx solvate as a subprocess. proc = _subprocess.run(command, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # gmx doesn't return sensible error codes, so we need to check that # the expected output was generated. if not _os.path.isfile("output.gro"): raise RuntimeError("'gmx solvate failed to generate output!") # Extract the water lines from the GRO file. water_lines = [] with open("output.gro", "r") as file: for line in file: if _re.search("SOL", line): # Store the SOL atom record. water_lines.append(line) # Add any box information. This is the last line in the GRO file. water_lines.append(line) # Write a GRO file that contains only the water atoms. if len(water_lines) - 1 > 0: with open("water.gro", "w") as file: file.write("BioSimSpace %s water box\n" % model.upper()) file.write("%d\n" % (len(water_lines) - 1)) for line in water_lines: file.write("%s" % line) else: raise ValueError( "No water molecules were generated. Try increasing " "the 'box' size or 'shell' thickness.") # Create a TOP file for the water model. By default we use the Amber03 # force field to generate a dummy topology for the water model. with open("water_ions.top", "w") as file: file.write("#define FLEXIBLE 1\n\n") file.write("; Include AmberO3 force field\n") file.write('#include "amber03.ff/forcefield.itp"\n\n') file.write("; Include %s water topology\n" % model.upper()) file.write('#include "amber03.ff/%s.itp"\n\n' % model) file.write("; Include ions\n") file.write('#include "amber03.ff/ions.itp"\n\n') file.write("[ system ] \n") file.write("BioSimSpace %s water box\n\n" % model.upper()) file.write("[ molecules ] \n") file.write(";molecule name nr.\n") file.write("SOL %d\n" % ((len(water_lines) - 1) / num_point)) # Load the water box. water = _IO.readMolecules(["water.gro", "water_ions.top"]) # Create a new system by adding the water to the original molecule. if molecule is not None: # Translate the molecule and water back to the original position. vec = [-x for x in vec] molecule.translate(vec, property_map) water.translate(vec) if type(molecule) is _System: # Extract the non-water molecules from the original system. non_waters = _Molecules( molecule.search("not water")._sire_object.toMolecules()) # Create a system by adding these to the water molecules from # gmx solvate, which will include the original waters. system = non_waters.toSystem() + water else: system = molecule.toSystem() + water # Add all of the water box properties to the new system. for prop in water._sire_object.propertyKeys(): prop = property_map.get(prop, prop) # Add the space property from the water system. system._sire_object.setProperty( prop, water._sire_object.property(prop)) else: system = water # Now we add ions to the system and neutralise the charge. if ion_conc > 0 or is_neutral: # Write the molecule + water system to file. _IO.saveMolecules("solvated", system, "gro87") _IO.saveMolecules("solvated", system, "grotop") _os.rename("solvated.gro87", "solvated.gro") _os.rename("solvated.grotop", "solvated.top") # First write an mdp file. with open("ions.mdp", "w") as file: file.write("; Neighbour searching\n") file.write("cutoff-scheme = Verlet\n") file.write("rlist = 1.1\n") file.write("pbc = xyz\n") file.write("verlet-buffer-tolerance = -1\n") file.write("\n; Electrostatics\n") file.write("coulombtype = cut-off\n") file.write("\n; VdW\n") file.write("rvdw = 1.0\n") # Create the grompp command. command = "%s grompp -f ions.mdp -po ions.out.mdp -c solvated.gro -p solvated.top -o ions.tpr" % _gmx_exe with open("README.txt", "a") as file: # Write the command to file. file.write( "\n# gmx grompp was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open("grommp.out", "w") stderr = open("grommp.err", "w") # Run grompp as a subprocess. proc = _subprocess.run(command, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # Flag whether to break out of the ion adding stage. is_break = False # Check for the tpr output file. if not _os.path.isfile("ions.tpr"): if shell is None: raise RuntimeError( "'gmx grommp' failed to generate output! " "Perhaps your box is too small?") else: is_break = True _warnings.warn( "Unable to achieve target ion concentration, try using " "'box' option instead of 'shell'.") # Only continue if grommp was successful. This allows us to skip the remainder # of the code if the ion addition failed when the 'shell' option was chosen, i.e. # because the estimated simulation box was too small. if not is_break: is_break = False # The ion concentration is unset. if ion_conc == 0: # Get the current molecular charge. charge = system.charge() # Round to the nearest integer value. charge = round(charge.magnitude()) # Create the genion command. command = "echo SOL | %s genion -s ions.tpr -o solvated_ions.gro -p solvated.top -neutral" % _gmx_exe # Add enough counter ions to neutralise the charge. if charge > 0: command += " -nn %d" % abs(charge) else: command += " -np %d" % abs(charge) else: # Create the genion command. command = "echo SOL | %s genion -s ions.tpr -o solvated_ions.gro -p solvated.top -%s -conc %f" \ % (_gmx_exe, "neutral" if is_neutral else "noneutral", ion_conc) with open("README.txt", "a") as file: # Write the command to file. file.write( "\n# gmx genion was run with the following command:\n") file.write("%s\n" % command) # Create files for stdout/stderr. stdout = open("genion.out", "w") stderr = open("genion.err", "w") # Run genion as a subprocess. proc = _subprocess.run(command, shell=True, stdout=stdout, stderr=stderr) stdout.close() stderr.close() # Check for the output GRO file. if not _os.path.isfile("solvated_ions.gro"): if shell is None: raise RuntimeError( "'gmx genion' failed to add ions! Perhaps your box is too small?" ) else: is_break = True _warnings.warn( "Unable to achieve target ion concentration, try using " "'box' option instead of 'shell'.") if not is_break: # Counters for the number of SOL, NA, and CL atoms. num_sol = 0 num_na = 0 num_cl = 0 # We now need to loop through the GRO file to extract the lines # corresponding to water or ion atoms. water_ion_lines = [] with open("solvated_ions.gro", "r") as file: for line in file: # This is a Sodium atom. if _re.search("NA", line): water_ion_lines.append(line) num_na += 1 # This is a Chlorine atom. if _re.search("CL", line): water_ion_lines.append(line) num_cl += 1 # This is a water atom. elif _re.search("SOL", line): water_ion_lines.append(line) num_sol += 1 # Add any box information. This is the last line in the GRO file. water_ion_lines.append(line) # Write a GRO file that contains only the water and ion atoms. if len(water_ion_lines) - 1 > 0: with open("water_ions.gro", "w") as file: file.write("BioSimSpace %s water box\n" % model.upper()) file.write("%d\n" % (len(water_ion_lines) - 1)) for line in water_ion_lines: file.write("%s" % line) # Ions have been added. Update the TOP file fo the water model # with the new atom counts. if num_na > 0 or num_cl > 0: with open("water_ions.top", "w") as file: file.write("#define FLEXIBLE 1\n\n") file.write("; Include AmberO3 force field\n") file.write( '#include "amber03.ff/forcefield.itp"\n\n') file.write("; Include %s water topology\n" % model.upper()) file.write('#include "amber03.ff/%s.itp"\n\n' % model) file.write("; Include ions\n") file.write('#include "amber03.ff/ions.itp"\n\n') file.write("[ system ] \n") file.write("BioSimSpace %s water box\n\n" % model.upper()) file.write("[ molecules ] \n") file.write(";molecule name nr.\n") file.write("SOL %d\n" % (num_sol / num_point)) if num_na > 0: file.write("NA %d\n" % num_na) if num_cl > 0: file.write("CL %d\n" % num_cl) # Load the water/ion box. water_ions = _IO.readMolecules( ["water_ions.gro", "water_ions.top"]) # Create a new system by adding the water to the original molecule. if molecule is not None: if type(molecule) is _System: # Extract the non-water molecules from the original system. non_waters = _Molecules( molecule.search( "not water")._sire_object.toMolecules()) # Create a system by adding these to the water and ion # molecules from gmx solvate, which will include the # original waters. system = non_waters.toSystem() + water_ions else: system = molecule.toSystem() + water_ions # Add all of the water molecules' properties to the new system. for prop in water_ions._sire_object.propertyKeys(): prop = property_map.get(prop, prop) # Add the space property from the water system. system._sire_object.setProperty( prop, water_ions._sire_object.property(prop)) else: system = water_ions # Store the name of the water model as a system property. system._sire_object.setProperty("water_model", _SireBase.wrap(model)) return system
def __init__(self, system, protocol, exe=None, name="somd", platform="CPU", work_dir=None, seed=None, property_map={}): """Constructor. Parameters ---------- system : :class:`System <BioSimSpace._SireWrappers.System>` The molecular system. protocol : :class:`Protocol <BioSimSpace.Protocol>` The protocol for the SOMD process. exe : str The full path to the SOMD executable. name : str The name of the process. platform : str The platform for the simulation: "CPU", "CUDA", or "OPENCL". work_dir : The working directory for the process. seed : int A random number seed. 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" } """ # Call the base class constructor. super().__init__(system, protocol, name, work_dir, seed, property_map) # Set the package name. self._package_name = "SOMD" # This process can generate trajectory data. self._has_trajectory = True if type(platform) is not str: raise TypeError("'platform' must be of type 'str'.") else: # Strip all whitespace and convert to upper case. platform = platform.replace(" ", "").upper() # Check for platform support. if platform not in self._platforms: raise ValueError("Supported platforms are: %s" % self._platforms.keys()) else: self._platform = self._platforms[platform] # If the path to the executable wasn't specified, then use the bundled SOMD # executable. if exe is None: # Generate the name of the SOMD exe. if _sys.platform != "win32": somd_path = _SireBase.getBinDir() somd_suffix = "" else: somd_path = _os.path.join( _os.path.normpath(_SireBase.getShareDir()), "scripts") somd_interpreter = _os.path.join( _os.path.normpath(_SireBase.getBinDir()), "sire_python.exe") somd_suffix = ".py" if type(self._protocol) is _Protocol.FreeEnergy: somd_exe = "somd-freenrg" else: somd_exe = "somd" somd_exe = _os.path.join(somd_path, somd_exe) + somd_suffix if not _os.path.isfile(somd_exe): raise _MissingSoftwareError( "'Cannot find SOMD executable in expected location: '%s'" % somd_exe) if _sys.platform != "win32": self._exe = somd_exe else: self._exe = somd_interpreter self._script = somd_exe else: # Make sure executable exists. if _os.path.isfile(exe): self._exe = exe else: raise IOError("SOMD executable doesn't exist: '%s'" % exe) # The names of the input files. self._rst_file = "%s/%s.rst7" % (self._work_dir, name) self._top_file = "%s/%s.prm7" % (self._work_dir, name) # The name of the trajectory file. self._traj_file = "%s/traj000000001.dcd" % self._work_dir # The name of the binary restart file. self._restart_file = "%s/latest.rst" % self._work_dir # Set the path for the SOMD configuration file. self._config_file = "%s/%s.cfg" % (self._work_dir, name) # Set the path for the perturbation file. self._pert_file = "%s/%s.pert" % (self._work_dir, name) # Set the path for the gradient file and create the gradient list. self._gradient_file = "%s/gradients.dat" % self._work_dir self._gradients = [] # Create the list of input files. self._input_files = [self._config_file, self._rst_file, self._top_file] # Initalise the number of moves per cycle. self._num_moves = 10000 # Initialise the buffering frequency. self._buffer_freq = 0 # Now set up the working directory for the process. self._setup()
def run(name, args={}): """Run a node. Parameters ---------- name : str The name of the node. args : dict A dictionary of arguments to be passed to the node. Returns ------- output : dict A dictionary containing the output of the node. """ # Validate the input. if type(name) is not str: raise TypeError("'name' must be of type 'str'.") if type(args) is not dict: raise TypeError("'args' must be of type 'dict'.") # Apped the node directory name. full_name = _node_dir + "/" + name # Make sure the node exists. if not _os.path.isfile(full_name): if not _os.path.isfile(full_name + ".py"): raise ValueError("Cannot find node: '%s'. " % name + "Run 'Node.list()' to see available nodes!") else: full_name += ".py" # Write a YAML configuration file for the BioSimSpace node. if len(args) > 0: with open("input.yaml", "w") as file: _yaml.dump(args, file, default_flow_style=False) # Create the command. command = "%s/python %s --config input.yaml" % (_SireBase.getBinDir(), full_name) # No arguments. else: command = "%s/python %s" % (_SireBase.getBinDir(), full_name) # Run the node as a subprocess. proc = _subprocess.run(command, shell=True) if proc.returncode == 0: # Read the output YAML file into a dictionary. with open("output.yaml", "r") as file: output = _yaml.safe_load(file) # Delete the redundant YAML files. _os.remove("input.yaml") _os.remove("output.yaml") return output
# Check to see if AMBERHOME is set. if "AMBERHOME" in _environ: _amber_home = _environ.get("AMBERHOME") else: _amber_home = None # Check to see if GROMACS is installed. import Sire.Base as _SireBase from os import path as _path # First, let the user tell us where to find GROMACS. This # assumes that gromacs is installed in $GROMACSHOME/bin/gmx. _gmx_exe = None if "GROMACSHOME" in _environ: try: _gmx_exe = _SireBase.findExe("%s/bin/gmx" % _environ.get("GROMACSHOME")) \ .absoluteFilePath() except: try: _gmx_exe = _SireBase.findExe("%s/bin/gmx_mpi" % _environ.get("GROMACSHOME")) \ .absoluteFilePath() except: pass if _gmx_exe is None: # The user has not told us where it is, so need to look in $PATH. try: _gmx_exe = _SireBase.findExe("gmx").absoluteFilePath() except: try: _gmx_exe = _SireBase.findExe("gmx_mpi").absoluteFilePath() except:
def _find_md_package(system, protocol, gpu_support=False): """Find a molecular dynamics package on the system and return a handle to it as a MDPackage object. Parameters ---------- system : :class:`System <BioSimSpace._SireWrappers.System>` The molecular system. protocol : :class:`Protocol <BioSimSpace.Protocol>` The simulation protocol. gpu_support : bool Whether to use package must have GPU support. Returns ------- (package, exe) : (str, str) The name of the MD package and a path to its executable. """ # The input has already been validated in the run method, so no need # to re-validate here. # Get the file format of the molecular system. fileformat = system.fileFormat() # Make sure that this format is supported. if not fileformat in _file_extensions: raise ValueError("Cannot find an MD package that supports format: %s" % fileformat) else: packages = _file_extensions[fileformat] # Is this a free energy protocol. if type(protocol) is _Protocol.FreeEnergy: is_free_energy = True else: is_free_energy = False # Loop over each package that supports the file format. for package in packages: # If this is free energy protocol, then check that the package has support. if not is_free_energy or _free_energy[package]: # Check whether this package exists on the system and has the desired # GPU support. for exe, gpu in _md_packages[package].items(): # If the user has requested GPU support make sure the package # supports it. if not gpu_support or gpu: # AMBER if package == "AMBER": # Search AMBERHOME, if set. if _amber_home is not None: _exe = "%s/bin/%s" % (_amber_home, exe) if _os.path.isfile(_exe): return (package, _exe) # Search system PATH. else: try: exe = _SireBase.findExe(exe).absoluteFilePath() return (package, exe) except: pass # GROMACS elif package == "GROMACS": if _gmx_exe is not None: return (package, _gmx_exe) # SOMD elif package == "SOMD": return (package, _SireBase.getBinDir() + "/somd") # Search system PATH. else: try: exe = _SireBase.findExe(exe).absoluteFilePath() return (package, exe) except: pass # If we get this far, then no package was found. raise _MissingSoftwareError("Couldn't find package to support format: %s" % fileformat)