Beispiel #1
0
    def getFrames(self, indices=None):
        """Get trajectory frames as a list of System objects.

           Parameters
           ----------

           indices : [int], [:class:`Time <BioSimSpace.Types.Time>`]
               A list of trajectory frame indices, or time stamps (in ns).

           Returns
           -------

           frames : [:class:`System <BioSimSpace._SireWrappers.System>`]
               The list of System objects.
        """

        # The process is running. Grab the latest trajectory.
        if self._process is not None and self._process.isRunning():
            self._trajectory = self.getTrajectory()

            # There is no trajectory.
            if self._trajectory is None:
                return None

        # Store the number of frames.
        n_frames = self._trajectory.n_frames

        # Work out the frame spacing in nanoseconds.
        # TODO:
        # How can we do this in a robust way if the trajectory is loaded from file?
        # Some formats do not store time information as part of the trajectory.
        if n_frames > 1:
            if self._process is not None:
                time_interval = self._process._protocol.getRunTime(
                ) / self._process._protocol.getFrames()
            else:
                time_interval = self._trajectory.timestep / 1000

        # Create the indices array.

        # Default to all frames.
        if indices is None:
            indices = [x for x in range(0, self._trajectory.n_frames)]

        # A single frame index.
        elif type(indices) is int:
            indices = [indices]

        # A single time stamp.
        elif type(indices) is _Time:
            if n_frames > 1:
                # Round time stamp to nearest frame index.
                indices = [
                    round(indices.nanoseconds().magnitude() / time_interval) -
                    1
                ]
            else:
                raise _IncompatibleError(
                    "Cannot determine time stamps for a trajectory "
                    "with only one frame!")

        # A list of frame indices.
        elif all(isinstance(x, int) for x in indices):
            pass

        # A list of time stamps.
        elif all(isinstance(x, _Time) for x in indices):
            if n_frames <= 1:
                raise _IncompatibleError(
                    "Cannot determine time stamps for a trajectory "
                    "with only one frame!")

            # Round time stamps to nearest frame indices.
            indices = [
                round(x.nanoseconds().magnitude() / time_interval) - 1
                for x in indices
            ]

        # Unsupported argument.
        else:
            raise ValueError(
                "Unsupported argument. Indices or time stamps "
                "must be an 'int' or 'BioSimSpace.Types.Time', or list of 'int' or "
                "'BioSimSpace.Types.Time' types.")

        # Intialise the list of frames.
        frames = []

        # Loop over all indices.
        for x in indices:

            # Make sure the frame index is within range.
            if x > 0 and x >= n_frames:
                raise ValueError("Frame index (%d) of of range (0 to %d)." %
                                 (x, n_frames - 1))
            elif x < -n_frames:
                raise ValueError("Frame index (%d) of of range (-1 to -%d)." %
                                 (x, n_frames))

            # The name of the frame coordinate file.
            frame_file = ".frame.nc"

            # Write the current frame as a NetCDF file.
            self._trajectory[x].save(frame_file)

            # Load the frame and create a System object.
            try:
                system = _System(
                    _SireIO.MoleculeParser.read([self._top_file, frame_file]))
            except Exception as e:
                _os.remove(frame_file)
                msg = "Failed to read trajectory frame: '%s'" % frame_file
                if _isVerbose():
                    raise IOError(msg) from e
                else:
                    raise IOError(msg) from None

            # Append the system to the list of frames.
            frames.append(system)

        # Remove the temporary frame coordinate file.
        _os.remove(frame_file)

        # Return the frames.
        return frames
Beispiel #2
0
    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!")
Beispiel #3
0
    def _generate_config(self):
        """Generate SOMD configuration file strings."""

        # Clear the existing configuration list.
        self._config = []

        # Check whether the system contains periodic box information.
        # For now, well not attempt to generate a box if the system property
        # is missing. If no box is present, we'll assume a non-periodic simulation.
        if "space" in self._system._sire_object.propertyKeys():
            has_box = True
        else:
            _warnings.warn(
                "No simulation box found. Assuming gas phase simulation.")
            has_box = False

        # Work out the GPU device ID.
        if self._platform == "CUDA":
            if "CUDA_VISIBLE_DEVICES" in _os.environ:
                try:
                    # Get the ID of the first available device.
                    #gpu_id = int(_os.environ.get("CUDA_VISIBLE_DEVICES").split(",")[0])
                    gpu_id = 0
                except:
                    raise EnvironmentError(
                        "'CUDA' platform is selected but cannot parse "
                        "'CUDA_VISIBLE_DEVICES' environment variable!")
            else:
                raise EnvironmentError(
                    "'CUDA' platform selected but 'CUDA_VISIBLE_DEVICES' "
                    "environment variable is unset.")

        # While the configuration parameters below share a lot of overlap,
        # we choose the keep them separate so that the user can modify options
        # for a given protocol in a single place.

        # Add configuration variables for a minimisation simulation.
        if type(self._protocol) is _Protocol.Minimisation:
            if self._platform == "CUDA":
                self.addToConfig("gpu = %d" % gpu_id)  # GPU device ID.
            self.addToConfig("minimise = True")  # Minimisation simulation.
            self.addToConfig(
                "minimise maximum iterations = %d"  # Maximum number of steps.
                % self._protocol.getSteps())
            self.addToConfig(
                "minimise tolerance = 1")  # Convergence tolerance.
            self.addToConfig("ncycles = 1")  # Perform a single SOMD cycle.
            self.addToConfig("nmoves = 1")  # Perform a single MD move.
            self.addToConfig(
                "save coordinates = True")  # Save molecular coordinates.
            if not has_box or not self._has_water:
                self.addToConfig(
                    "cutoff type = cutoffnonperiodic")  # No periodic box.
            else:
                self.addToConfig(
                    "cutoff type = cutoffperiodic")  # Periodic box.
            self.addToConfig(
                "cutoff distance = 10 angstrom")  # Non-bonded cut-off.

        # In the following protocols we save coordinates every cycle, which is
        # 10000 MD steps (moves) in length (this is for consistency with other
        # MD drivers).

        # Add configuration variables for an equilibration simulation.
        elif type(self._protocol) is _Protocol.Equilibration:
            # Only constant temperature equilibration simulations are supported.
            if not self._protocol.isConstantTemp():
                raise _IncompatibleError(
                    "SOMD only supports constant temperature equilibration.")

            # Backbone restraints aren't supported.
            if self._protocol.isRestrained():
                raise _IncompatibleError(
                    "SOMD doesn't support backbone atom restraints.")

            # Work out the number of cycles.
            ncycles = (self._protocol.getRunTime() /
                       self._protocol.getTimeStep()) / self._num_moves

            # If there is less than a single cycle, then reduce the number of moves.
            if ncycles < 1:
                self._num_moves = _math.ceil(ncycles * self._num_moves)
                ncycles = 1

            # Work out the number of cycles per frame.
            cycles_per_frame = ncycles / self._protocol.getFrames()

            # Work out whether we need to adjust the buffer frequency.
            buffer_freq = 0
            if cycles_per_frame < 1:
                buffer_freq = cycles_per_frame * self._num_moves
                cycles_per_frame = 1
                self._buffer_freq = buffer_freq
            else:
                cycles_per_frame = _math.floor(cycles_per_frame)

            # Convert the timestep to femtoseconds.
            timestep = self._protocol.getTimeStep().femtoseconds().magnitude()

            # Convert the temperature to Kelvin.
            temperature = self._protocol.getStartTemperature().kelvin(
            ).magnitude()

            if self._platform == "CUDA":
                self.addToConfig("gpu = %d" % gpu_id)  # GPU device ID.
            self.addToConfig("ncycles = %d" %
                             ncycles)  # The number of SOMD cycles.
            self.addToConfig("nmoves = %d" %
                             self._num_moves)  # The number of moves per cycle.
            self.addToConfig(
                "save coordinates = True")  # Save molecular coordinates.
            self.addToConfig("ncycles_per_snap = %d" %
                             cycles_per_frame)  # Cycles per trajectory write.
            self.addToConfig("buffered coordinates frequency = %d" %
                             buffer_freq)  # Buffering frequency.
            self.addToConfig("timestep = %.2f femtosecond" %
                             timestep)  # Integration time step.
            self.addToConfig("thermostat = True")  # Turn on the thermostat.
            self.addToConfig("temperature = %.2f kelvin" %
                             temperature)  # System temperature.
            if self._protocol.getPressure() is None:
                self.addToConfig(
                    "barostat = False")  # Disable barostat (constant volume).
            else:
                if self._has_water and has_box:
                    self.addToConfig("barostat = True")  # Enable barostat.
                    self.addToConfig(
                        "pressure = %.5f atm"  # Pressure in atmosphere.
                        % self._protocol.getPressure().atm().magnitude())
                else:
                    self.addToConfig("barostat = False"
                                     )  # Disable barostat (constant volume).
            if self._has_water:
                self.addToConfig(
                    "reaction field dielectric = 78.3")  # Solvated box.
            else:
                self.addToConfig("reaction field dielectric = 82.0")  # Vacuum.
            if not has_box or not self._has_water:
                self.addToConfig(
                    "cutoff type = cutoffnonperiodic")  # No periodic box.
            else:
                self.addToConfig(
                    "cutoff type = cutoffperiodic")  # Periodic box.
            self.addToConfig(
                "cutoff distance = 10 angstrom")  # Non-bonded cut-off.
            if self._is_seeded:
                self.addToConfig("random seed = %d" %
                                 self._seed)  # Random number seed.

        # Add configuration variables for a production simulation.
        elif type(self._protocol) is _Protocol.Production:

            # Work out the number of cycles.
            ncycles = (self._protocol.getRunTime() /
                       self._protocol.getTimeStep()) / self._num_moves

            # If there is less than a single cycle, then reduce the number of moves.
            if ncycles < 1:
                self._num_moves = _math.ceil(ncycles * self._num_moves)
                ncycles = 1

            # Work out the number of cycles per frame.
            cycles_per_frame = ncycles / self._protocol.getFrames()

            # Work out whether we need to adjust the buffer frequency.
            buffer_freq = 0
            if cycles_per_frame < 1:
                buffer_freq = cycles_per_frame * self._num_moves
                cycles_per_frame = 1
            else:
                cycles_per_frame = _math.floor(cycles_per_frame)

            # Convert the timestep to femtoseconds.
            timestep = self._protocol.getTimeStep().femtoseconds().magnitude()

            # Convert the temperature to Kelvin.
            temperature = self._protocol.getTemperature().kelvin().magnitude()

            if self._platform == "CUDA":
                self.addToConfig("gpu = %d" % gpu_id)  # GPU device ID.
            self.addToConfig("ncycles = %d" %
                             ncycles)  # The number of SOMD cycles.
            self.addToConfig("nmoves = %d" %
                             self._num_moves)  # The number of moves per cycle.
            self.addToConfig(
                "save coordinates = True")  # Save molecular coordinates.
            self.addToConfig("ncycles_per_snap = %d" %
                             cycles_per_frame)  # Cycles per trajectory write.
            self.addToConfig("buffered coordinates frequency = %d" %
                             buffer_freq)  # Buffering frequency.
            self.addToConfig("timestep = %.2f femtosecond" %
                             timestep)  # Integration time step.
            self.addToConfig("thermostat = True")  # Turn on the thermostat.
            self.addToConfig("temperature = %.2f kelvin" %
                             temperature)  # System temperature.
            if self._protocol.getPressure() is None:
                self.addToConfig(
                    "barostat = False")  # Disable barostat (constant volume).
            else:
                if self._has_water and has_box:
                    self.addToConfig("barostat = True")  # Enable barostat.
                    self.addToConfig(
                        "pressure = %.5f atm"  # Presure in atmosphere.
                        % self._protocol.getPressure().atm().magnitude())
                else:
                    self.addToConfig("barostat = False"
                                     )  # Disable barostat (constant volume).
            if self._has_water:
                self.addToConfig(
                    "reaction field dielectric = 78.3")  # Solvated box.
            else:
                self.addToConfig("reaction field dielectric = 82.0")  # Vacuum.
            if not has_box or not self._has_water:
                self.addToConfig(
                    "cutoff type = cutoffnonperiodic")  # No periodic box.
            else:
                self.addToConfig(
                    "cutoff type = cutoffperiodic")  # Periodic box.
            self.addToConfig(
                "cutoff distance = 10 angstrom")  # Non-bonded cut-off.
            if self._is_seeded:
                self.addToConfig("random seed = %d" %
                                 self._seed)  # Random number seed.

        # Add configuration variables for a free energy simulation.
        elif type(self._protocol) is _Protocol.FreeEnergy:

            # Work out the number of cycles.
            ncycles = (self._protocol.getRunTime() /
                       self._protocol.getTimeStep()) / self._num_moves

            # If there is less than a single cycle, then reduce the number of moves.
            if ncycles < 1:
                self._num_moves = _math.ceil(ncycles * self._num_moves)
                ncycles = 1

            # Work out the number of cycles per frame.
            cycles_per_frame = ncycles / self._protocol.getFrames()

            # Work out whether we need to adjust the buffer frequency.
            buffer_freq = 0
            if cycles_per_frame < 1:
                buffer_freq = cycles_per_frame * self._num_moves
                cycles_per_frame = 1
            else:
                cycles_per_frame = _math.floor(cycles_per_frame)

            # Convert the timestep to femtoseconds.
            timestep = self._protocol.getTimeStep().femtoseconds().magnitude()

            # Convert the temperature to Kelvin.
            temperature = self._protocol.getTemperature().kelvin().magnitude()

            if self._platform == "CUDA":
                self.addToConfig("gpu = %d" % gpu_id)  # GPU device ID.
            self.addToConfig("ncycles = %d" %
                             ncycles)  # The number of SOMD cycles.
            self.addToConfig("nmoves = %d" %
                             self._num_moves)  # The number of moves per cycle.
            self.addToConfig("energy frequency = 100"
                             )  # Frequency of free energy gradient evaluation.
            self.addToConfig(
                "save coordinates = True")  # Save molecular coordinates.
            self.addToConfig("ncycles_per_snap = %d" %
                             cycles_per_frame)  # Cycles per trajectory write.
            self.addToConfig("buffered coordinates frequency = %d" %
                             buffer_freq)  # Buffering frequency.
            self.addToConfig("timestep = %.2f femtosecond" %
                             timestep)  # Integration time step.
            self.addToConfig("thermostat = True")  # Turn on the thermostat.
            self.addToConfig("temperature = %.2f kelvin" %
                             temperature)  # System temperature.
            if self._protocol.getPressure() is None:
                self.addToConfig(
                    "barostat = False")  # Disable barostat (constant volume).
            else:
                if self._has_water and has_box:
                    self.addToConfig("barostat = True")  # Enable barostat.
                    self.addToConfig(
                        "pressure = %.5f atm"  # Presure in atmosphere.
                        % self._protocol.getPressure().atm().magnitude())
                else:
                    self.addToConfig("barostat = False"
                                     )  # Disable barostat (constant volume).
            if self._has_water:
                self.addToConfig(
                    "reaction field dielectric = 78.3")  # Solvated box.
            else:
                self.addToConfig("reaction field dielectric = 82.0")  # Vacuum.
            if not has_box or not self._has_water:
                self.addToConfig(
                    "cutoff type = cutoffnonperiodic")  # No periodic box.
            else:
                self.addToConfig(
                    "cutoff type = cutoffperiodic")  # Periodic box.
            self.addToConfig(
                "cutoff distance = 10 angstrom")  # Non-bonded cut-off.
            if self._is_seeded:
                self.addToConfig("random seed = %d" %
                                 self._seed)  # Random number seed.
            self.addToConfig("constraint = hbonds-notperturbed"
                             )  # Handle hydrogen perturbations.
            self.addToConfig("minimise = True")  # Perform a minimisation.
            self.addToConfig("equilibrate = False")  # Don't equilibrate.
            # The lambda value array.
            self.addToConfig("lambda array = %s" \
                % ", ".join([str(x) for x in self._protocol.getLambdaValues()]))
            self.addToConfig("lambda_val = %s" \
                % self._protocol.getLambda())                               # The value of lambda.

        else:
            raise _IncompatibleError("Unsupported protocol: '%s'" %
                                     self._protocol.__class__.__name__)

        # Flag that this isn't a custom protocol.
        self._protocol._setCustomised(False)
Beispiel #4
0
    def __init__(self, system, protocol, name=None, 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 process.

           name : str
               The name of the process.

           work_dir : str
               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" }
        """

	# Don't allow user to create an instance of this base class.
        if type(self) is Process:
            raise Exception("<Process> must be subclassed.")

        # Check that the system is valid.
        if type(system) is not _System:
            raise TypeError("'system' must be of type 'BioSimSpace._SireWrappers.System'")

        # Check that the protocol is valid.
        if not isinstance(protocol, _Protocol):
            raise TypeError("'protocol' must be of type 'BioSimSpace.Protocol'")

        # Check that the working directory is valid.
        if work_dir is not None and type(work_dir) is not str:
            raise TypeError("'work_dir' must be of type 'str'")

        # Check that the seed is valid.
        if seed is not None and type(seed) is not int:
            raise TypeError("'seed' must be of type 'int'")

        # Check that the map is valid.
        if type(property_map) is not dict:
            raise TypeError("'property_map' must be of type 'dict'")

        # Make sure that molecules in the system have coordinates.
        prop = property_map.get("coordinates", "coordinates")
        for mol in system.getMolecules():
            if not mol.isMerged():
                if not mol._sire_object.hasProperty(prop):
                    raise _IncompatibleError("System object contains molecules without coordinates!")

        # Set the process to None.
        self._process = None

        # Set the script to None (used on Windows as it does not support symlinks).
        self._script = None

        # Is the process running interactively? If so, don't block
        # when a get method is called.
        self._is_blocked = not _is_interactive

        # Whether this process can generate trajectory data.
        # Even if a process can generate a trajectory, whether it does
        # will depend on the chosen protocol.
        self._has_trajectory = False

	# Copy the passed system, protocol, and process name.
        self._system = system.copy()
        self._protocol = protocol

        # Flag whether the system contains water molecules.
        self._has_water = system.nWaterMolecules() > 0

        # Flag whether the system contains perturbable molecules.
        self._has_perturbable = system.nPerturbableMolecules() > 0

        # Flag that the process isn't queued.
        self._is_queued = False

        # Set the name
        if name is None:
            self._name = None
        else:
            self.setName(name)

        # Set the random number seed.
        if seed is None:
            self._is_seeded = False
            self._seed = None
        else:
            self._is_seeded = True
            self.setSeed(seed)

        # Set the map.
        self._property_map = property_map.copy()

        # Set the timer and running time None.
        self._timer = None
        self._runtime = None

        # Set the command-line string to None
        self._command = None

        # Set the list of input files to None.
        self._input_files = None

        # Create a temporary working directory and store the directory name.
        if work_dir is None:
            self._tmp_dir = _tempfile.TemporaryDirectory()
            self._work_dir = self._tmp_dir.name

        # User specified working directory.
        else:
            # Use full path.
            if work_dir[0] != "/":
                work_dir = _os.getcwd() + "/" + work_dir
            self._work_dir = work_dir

            # Create the directory if it doesn't already exist.
            if not _os.path.isdir(work_dir):
                _os.makedirs(work_dir, exist_ok=True)

        # Files for redirection of stdout and stderr.
        self._stdout_file = "%s/%s.out" % (self._work_dir, name)
        self._stderr_file = "%s/%s.err" % (self._work_dir, name)

        # Files for metadynamics simulation with PLUMED.
        self._plumed_config_file = "%s/plumed.dat" % self._work_dir
        self._plumed_config = None

        # Initialise the configuration file string list.
        self._config = []

        # Initalise the command-line argument dictionary.
        self._args = _collections.OrderedDict()

        # Clear any existing output in the current working directory
        # and set out stdout/stderr files.
        self._clear_output()