Ejemplo n.º 1
0
def build(mol,
          ff=None,
          topo=None,
          param=None,
          prefix='structure',
          outdir='./build',
          caps=None,
          ionize=True,
          saltconc=0,
          saltanion=None,
          saltcation=None,
          disulfide=None,
          teleap=None,
          teleapimports=None,
          execute=True,
          atomtypes=None,
          offlibraries=None,
          gbsa=False,
          igb=2):
    """ Builds a system for AMBER

    Uses tleap to build a system for AMBER. Additionally it allows the user to ionize and add disulfide bridges.

    Parameters
    ----------
    mol : :class:`Molecule <moleculekit.molecule.Molecule>` object
        The Molecule object containing the system
    ff : list of str
        A list of leaprc forcefield files.
        Use :func:`amber.listFiles <htmd.builder.amber.listFiles>` to get a list of available forcefield files.
        Default: :func:`amber.defaultFf <htmd.builder.amber.defaultFf>`
    topo : list of str
        A list of topology `prepi/prep/in` files.
        Use :func:`amber.listFiles <htmd.builder.amber.listFiles>` to get a list of available topology files.
        Default: :func:`amber.defaultTopo <htmd.builder.amber.defaultTopo>`
    param : list of str
        A list of parameter `frcmod` files.
        Use :func:`amber.listFiles <htmd.builder.amber.listFiles>` to get a list of available parameter files.
        Default: :func:`amber.defaultParam <htmd.builder.amber.defaultParam>`
    prefix : str
        The prefix for the generated pdb and psf files
    outdir : str
        The path to the output directory
        Default: './build'
    caps : dict
        A dictionary with keys segids and values lists of strings describing the caps for a particular protein segment.
        e.g. caps['P'] = ['ACE', 'NME'] or caps['P'] = ['none', 'none']. Default: will apply ACE and NME caps to every
        protein segment.
    ionize : bool
        Enable or disable ionization
    saltconc : float
        Salt concentration to add to the system after neutralization.
    saltanion : {'Cl-'}
        The anion type. Please use only AMBER ion atom names.
    saltcation : {'Na+', 'K+', 'Cs+'}
        The cation type. Please use only AMBER ion atom names.
    disulfide : list of pairs of atomselection strings
        If None it will guess disulfide bonds. Otherwise provide a list pairs of atomselection strings for each pair of
        residues forming the disulfide bridge.
    teleap : str
        Path to teLeap executable used to build the system for AMBER
    teleapimports : list
        A list of paths to pass to teLeap '-I' flag, i.e. directories to be searched
        Default: determined from :func:`amber.defaultAmberHome <htmd.builder.amber.defaultAmberHome>` and
        :func:`amber.htmdAmberHome <htmd.builder.amber.htmdAmberHome>`
    execute : bool
        Disable building. Will only write out the input script needed by tleap. Does not include ionization.
    atomtypes : list of triplets
        Custom atom types defined by the user as ('type', 'element', 'hybrid') triplets
        e.g. (('C1', 'C', 'sp2'), ('CI', 'C', 'sp3')). Check `addAtomTypes` in AmberTools docs.
    offlibraries : str or list
        A path or a list of paths to OFF library files. Check `loadOFF` in AmberTools docs.
    gbsa : bool
        Modify radii for GBSA implicit water model
    igb : int
        GB model. Select: 1 for mbondi, 2 and 5 for mbondi2, 7 for bondi and 8 for mbondi3.
        Check section 4. The Generalized Born/Surface Area Model of the AMBER manual.

    Returns
    -------
    molbuilt : :class:`Molecule <moleculekit.molecule.Molecule>` object
        The built system in a Molecule object

    Example
    -------
    >>> from htmd.ui import *  # doctest: +SKIP
    >>> mol = Molecule("3PTB")
    >>> molbuilt = amber.build(mol, outdir='/tmp/build')  # doctest: +SKIP
    >>> # More complex example
    >>> disu = [['segid P and resid 157', 'segid P and resid 13'], ['segid K and resid 1', 'segid K and resid 25']]
    >>> molbuilt = amber.build(mol, outdir='/tmp/build', saltconc=0.15, disulfide=disu)  # doctest: +SKIP
    """
    # Remove pdb protein bonds as they can be regenerated by tleap. Keep non-protein bonds i.e. for ligands
    mol = mol.copy()
    _removeProteinBonds(mol)

    if teleap is None:
        teleap = _findTeLeap()
    else:
        if shutil.which(teleap) is None:
            raise NameError(
                'Could not find executable: `{}` in the PATH. Cannot build for AMBER.'
                .format(teleap))

    if not os.path.isdir(outdir):
        os.makedirs(outdir)
    _cleanOutDir(outdir)
    if ff is None:
        ff = defaultFf()
    if topo is None:
        topo = defaultTopo()
    if param is None:
        param = defaultParam()
    if caps is None:
        caps = _defaultProteinCaps(mol)

    _missingSegID(mol)
    _checkMixedSegment(mol)

    mol = _charmmLipid2Amber(mol)

    _applyProteinCaps(mol, caps)

    f = open(os.path.join(outdir, 'tleap.in'), 'w')
    f.write('# tleap file generated by amber.build\n')

    # Printing out the forcefields
    for i, force in enumerate(ensurelist(ff)):
        if not os.path.isfile(force):
            force = _locateFile(force, 'ff', teleap)
            if force is None:
                continue
        newname = 'ff{}_{}'.format(i, os.path.basename(force))
        shutil.copy(force, os.path.join(outdir, newname))
        f.write('source {}\n'.format(newname))
    f.write('\n')

    if gbsa:
        gbmodels = {
            1: 'mbondi',
            2: 'mbondi2',
            5: 'mbondi2',
            7: 'bondi',
            8: 'mbondi3'
        }
        f.write('set default PBradii {}\n\n'.format(gbmodels[igb]))

    # Adding custom atom types
    if atomtypes is not None:
        atomtypes = ensurelist(tocheck=atomtypes[0], tomod=atomtypes)
        f.write('addAtomTypes {\n')
        for at in atomtypes:
            if len(at) != 3:
                raise RuntimeError(
                    'Atom type definitions have to be triplets. Check the AMBER documentation.'
                )
            f.write('    {{ "{}" "{}" "{}" }}\n'.format(at[0], at[1], at[2]))
        f.write('}\n\n')

    # Loading OFF libraries
    if offlibraries is not None:
        offlibraries = ensurelist(offlibraries)
        for i, off in enumerate(offlibraries):
            if not os.path.isfile(off):
                raise RuntimeError(
                    'Could not find off-library in location {}'.format(off))
            newname = 'offlib{}_{}'.format(i, os.path.basename(off))
            shutil.copy(off, os.path.join(outdir, newname))
            f.write('loadoff {}\n'.format(newname))

    # Loading frcmod parameters
    f.write('# Loading parameter files\n')
    for i, p in enumerate(param):
        if not os.path.isfile(p):
            p = _locateFile(p, 'param', teleap)
            if p is None:
                continue
        newname = 'param{}_{}'.format(i, os.path.basename(p))
        shutil.copy(p, os.path.join(outdir, newname))
        f.write('loadamberparams {}\n'.format(newname))
    f.write('\n')

    # Loading prepi topologies
    f.write('# Loading prepi topologies\n')
    for i, t in enumerate(topo):
        if not os.path.isfile(t):
            t = _locateFile(t, 'topo', teleap)
            if t is None:
                continue
        newname = 'topo{}_{}'.format(i, os.path.basename(t))
        shutil.copy(t, os.path.join(outdir, newname))
        f.write('loadamberprep {}\n'.format(newname))
    f.write('\n')

    f.write('# Loading the system\n')
    f.write('mol = loadpdb input.pdb\n\n')

    if np.sum(mol.atomtype != '') != 0:
        logger.debug('Writing mol2 files for input to tleap.')
        segs = np.unique(mol.segid[mol.atomtype != ''])
        combstr = 'mol = combine {mol'
        for s in segs:
            name = 'segment{}'.format(s)
            mol2name = os.path.join(outdir, '{}.mol2'.format(name))
            mol.write(mol2name, (mol.atomtype != '') & (mol.segid == s))
            if not os.path.isfile(mol2name):
                raise NameError(
                    'Could not write a mol2 file out of the given Molecule.')
            f.write('# Loading the rest of the system\n')
            f.write('{} = loadmol2 {}.mol2\n\n'.format(name, name))
            combstr += ' {}'.format(name)
        combstr += '}\n\n'
        f.write(combstr)

    # Write patches for disulfide bonds (only after ionizing)
    if not ionize:
        # TODO: Remove this once we deprecate the class
        from htmd.builder.builder import DisulfideBridge
        from moleculekit.molecule import UniqueResidueID
        if disulfide is not None and len(disulfide) != 0 and isinstance(
                disulfide[0], DisulfideBridge):
            newdisu = []
            for d in disulfide:
                r1 = UniqueResidueID.fromMolecule(
                    mol, 'resid {} and segname {}'.format(d.resid1, d.segid1))
                r2 = UniqueResidueID.fromMolecule(
                    mol, 'resid {} and segname {}'.format(d.resid2, d.segid2))
                newdisu.append([r1, r2])
            disulfide = newdisu
        # TODO: Remove up to here ----------------------

        if disulfide is not None and len(disulfide) != 0 and isinstance(
                disulfide[0][0], str):
            disulfide = convertDisulfide(mol, disulfide)

        if disulfide is None:
            logger.info('Detecting disulfide bonds.')
            disulfide = detectDisulfideBonds(mol)

        # Fix structure to match the disulfide patching
        if len(disulfide) != 0:
            torem = np.zeros(mol.numAtoms, dtype=bool)
            f.write('# Adding disulfide bonds\n')
            for d in disulfide:
                # Rename the residues to CYX if there is a disulfide bond
                atoms1 = d[0].selectAtoms(mol, indexes=False)
                atoms2 = d[1].selectAtoms(mol, indexes=False)
                mol.resname[atoms1] = 'CYX'
                mol.resname[atoms2] = 'CYX'
                # Remove (eventual) HG hydrogens on these CYS (from proteinPrepare)
                torem |= (atoms1 & (mol.name == 'HG')) | (atoms2 &
                                                          (mol.name == 'HG'))
                # Convert to stupid amber residue numbering
                uqseqid = sequenceID(
                    (mol.resid, mol.insertion, mol.segid)) + mol.resid[0]
                uqres1 = int(np.unique(uqseqid[atoms1]))
                uqres2 = int(np.unique(uqseqid[atoms2]))
                f.write('bond mol.{}.SG mol.{}.SG\n'.format(uqres1, uqres2))
            f.write('\n')
            mol.remove(torem, _logger=False)

    # Calculate the bounding box and store it in the CRD file
    f.write('setBox mol "vdw"\n\n')

    f.write('# Writing out the results\n')
    f.write('saveamberparm mol ' + prefix + '.prmtop ' + prefix + '.crd\n')
    f.write('quit')
    f.close()

    # Printing and loading the PDB file. AMBER can work with a single PDB file if the segments are separate by TER
    logger.debug('Writing PDB file for input to tleap.')
    pdbname = os.path.join(outdir, 'input.pdb')

    # mol2 files have atomtype, here we only write parts not coming from mol2
    # We need to write the input.pdb at the end since we modify the resname for disulfide bridges in mol
    mol.write(pdbname, mol.atomtype == '')
    if not os.path.isfile(pdbname):
        raise NameError(
            'Could not write a PDB file out of the given Molecule.')

    molbuilt = None
    if execute:
        if not teleapimports:
            teleapimports = []
            # Source default Amber (i.e. the same paths tleap imports)
            amberhome = defaultAmberHome(teleap=teleap)
            teleapimports += [
                os.path.join(amberhome, s)
                for s in _defaultAmberSearchPaths.values()
            ]
            if len(teleapimports) == 0:
                raise RuntimeWarning(
                    'No default Amber force-field found. Check teLeap location: {}'
                    .format(teleap))
            # Source HTMD Amber paths that contain ffs
            htmdamberdir = htmdAmberHome()
            teleapimports += [
                os.path.join(htmdamberdir, os.path.dirname(f)) for f in ff
                if os.path.isfile(os.path.join(htmdamberdir, f))
            ]
            if len(teleapimports) == 0:
                raise RuntimeError(
                    'No default Amber force-field imports found. Check '
                    '`htmd.builder.amber.defaultAmberHome()` and `htmd.builder.amber.htmdAmberHome()`'
                )
        # Set import flags for teLeap
        teleapimportflags = []
        for p in teleapimports:
            teleapimportflags.append('-I')
            teleapimportflags.append(str(p))
        logpath = os.path.abspath(os.path.join(outdir, 'log.txt'))
        logger.info('Starting the build.')
        currdir = os.getcwd()
        os.chdir(outdir)
        f = open(logpath, 'w')
        try:
            cmd = [teleap, '-f', './tleap.in']
            cmd[1:1] = teleapimportflags
            logger.debug(cmd)
            call(cmd, stdout=f)
        except:
            raise NameError('teLeap failed at execution')
        f.close()
        errors = _logParser(logpath)
        os.chdir(currdir)
        if errors:
            raise BuildError(errors + [
                'Check {} for further information on errors in building.'.
                format(logpath)
            ])
        logger.info('Finished building.')

        if os.path.exists(os.path.join(outdir, 'structure.crd')) and \
                        os.path.getsize(os.path.join(outdir, 'structure.crd')) != 0 and \
                        os.path.getsize(os.path.join(outdir, 'structure.prmtop')) != 0:
            try:
                molbuilt = Molecule(os.path.join(outdir, 'structure.prmtop'))
                molbuilt.read(os.path.join(outdir, 'structure.crd'))
            except Exception as e:
                raise RuntimeError(
                    'Failed at reading structure.prmtop/structure.crd due to error: {}'
                    .format(e))
        else:
            raise BuildError(
                'No structure pdb/prmtop file was generated. Check {} for errors in building.'
                .format(logpath))

        if ionize:
            shutil.move(os.path.join(outdir, 'structure.crd'),
                        os.path.join(outdir, 'structure.noions.crd'))
            shutil.move(os.path.join(outdir, 'structure.prmtop'),
                        os.path.join(outdir, 'structure.noions.prmtop'))
            totalcharge = np.sum(molbuilt.charge)
            nwater = np.sum(molbuilt.atomselect('water and noh'))
            anion, cation, anionatom, cationatom, nanion, ncation = ionizef(
                totalcharge,
                nwater,
                saltconc=saltconc,
                anion=saltanion,
                cation=saltcation)
            newmol = ionizePlace(mol, anion, cation, anionatom, cationatom,
                                 nanion, ncation)
            # Redo the whole build but now with ions included
            return build(newmol,
                         ff=ff,
                         topo=topo,
                         param=param,
                         prefix=prefix,
                         outdir=outdir,
                         caps={},
                         ionize=False,
                         execute=execute,
                         saltconc=saltconc,
                         disulfide=disulfide,
                         teleap=teleap,
                         atomtypes=atomtypes,
                         offlibraries=offlibraries)

    tmpbonds = molbuilt.bonds
    molbuilt.bonds = []  # Removing the bonds to speed up writing
    molbuilt.write(os.path.join(outdir, 'structure.pdb'))
    molbuilt.bonds = tmpbonds  # Restoring the bonds
    detectCisPeptideBonds(molbuilt)  # Warn in case of cis bonds
    return molbuilt
Ejemplo n.º 2
0
    def write(self, inputdir, outputdir):
        """Write the equilibration protocol

        Writes the equilibration protocol and files into a folder for execution
        using files inside the inputdir directory

        Parameters
        ----------
        inputdir : str
            Path to a directory containing the files produced by a build process.
        outputdir : str
            Directory where to write the equilibration setup files.

        Examples
        --------
        >>> md = Equilibration()
        >>> md.write('./build','./equil')
        """

        from moleculekit.molecule import Molecule

        self._findFiles(inputdir)
        self._amberFixes()

        from htmd.units import convert

        numsteps = convert(self.timeunits,
                           "timesteps",
                           self.runtime,
                           timestep=self.acemd.timestep)

        self.acemd.temperature = self.temperature
        self.acemd.thermostattemperature = self.temperature
        self.acemd.run = str(numsteps)

        pdbfile = os.path.join(inputdir, self.acemd.coordinates)
        inmol = Molecule(pdbfile)

        from htmd.builder.builder import detectCisPeptideBonds

        detectCisPeptideBonds(inmol)

        if np.any(inmol.atomselect("lipids")) and not self.useconstantratio:
            logger.warning(
                "Lipids detected in input structure. We highly recommend setting useconstantratio=True "
                "for membrane simulations.")

        if self.restraintsteps is None:
            constrsteps = int(numsteps / 2)
        else:
            constrsteps = int(self.restraintsteps)

        if self.restraints is not None:
            self.acemd.restraints = self.restraints
        else:
            self.acemd.restraints = self.defaultEquilRestraints(constrsteps,
                                                                mol=inmol)

        if self.acemd.boxsize is None and self.acemd.extendedsystem is None:
            coords = inmol.get("coords", sel="water")
            if coords.size == 0:  # It's a vacuum simulation
                coords = inmol.get("coords", sel="all")
                dim = np.max(coords, axis=0) - np.min(coords, axis=0)
                dim += 12.0
            else:
                dim = np.max(coords, axis=0) - np.min(coords, axis=0)
            self.acemd.boxsize = "{} {} {}".format(dim[0], dim[1], dim[2])

        if self.useconstantratio:
            self.acemd.useconstantratio = "on"

        self.acemd.setup(inputdir, outputdir, overwrite=True)
Ejemplo n.º 3
0
    def write(self, inputdir, outputdir, cisbondcheck=True):
        """Writes the production protocol and files into a folder.

        Parameters
        ----------
        inputdir : str
            Path to a directory containing the files produced by a equilibration process.
        outputdir : str
            Directory where to write the production setup files.
        cisbondcheck : bool
            Set to False to disable the cis peptidic bond checks
        """
        from moleculekit.molecule import Molecule
        from htmd.units import convert
        from htmd.builder.builder import detectCisPeptideBonds

        self._findFiles(inputdir)
        self._amberFixes()

        self.acemd.temperature = self.temperature
        self.acemd.thermostattemperature = self.temperature

        numsteps = convert(self.timeunits,
                           "timesteps",
                           self.runtime,
                           timestep=self.acemd.timestep)
        self.acemd.run = str(numsteps)

        structfile = os.path.join(inputdir, self.acemd.structure)
        pdbfile = os.path.join(inputdir, self.acemd.coordinates)
        inmol = Molecule([structfile, pdbfile])

        if cisbondcheck:
            detectCisPeptideBonds(inmol, respect_bonds=True)

        if np.any(inmol.atomselect("lipids")) and not self.useconstantratio:
            logger.warning(
                "Lipids detected in input structure. We highly recommend setting useconstantratio=True "
                "for membrane simulations.")

        if self.restraints is not None:
            logger.info(
                "Using user-provided restraints and ignoring constraints and fb_potential"
            )
            self.acemd.restraints = self.restraints
        else:
            restraints = list()
            if self.fb_k > 0:
                logger.warning(
                    "Converting fb_potential to restraints. This is a convenience "
                    "functional conversion. We recommend to start using restraints."
                )
                restraints += self._fb_potential2restraints(inputdir)
            if self.useconstraints:
                logger.warning(
                    "Converting constraints to restraints. This is a convenience "
                    "functional conversion. We recommend to start using restraints"
                )
                restraints += self._constraints2restraints()
            else:
                if len(self.constraints) != 0:
                    logger.warning(
                        "You have setup constraints to {} but constraints are turned off. "
                        "If you want to use constraints, define "
                        "useconstraints=True".format(self.constraints))
            if len(restraints) != 0:
                self.acemd.restraints = restraints

        if self.useconstantratio:
            self.acemd.barostatconstratio = "on"

        if self.adaptive:
            self.acemd.binvelocities = None

        self.acemd.setup(inputdir, outputdir, overwrite=True)
Ejemplo n.º 4
0
    def write(self, inputdir, outputdir):
        """ Writes the production protocol and files into a folder.

        Parameters
        ----------
        inputdir : str
            Path to a directory containing the files produced by a equilibration process.
        outputdir : str
            Directory where to write the production setup files.
        """
        from moleculekit.molecule import Molecule

        # Do version consistency check
        if (self._version == 2 and not isinstance(self.acemd, Acemd2)) and \
                (self._version == 3 and not isinstance(self.acemd, Acemd)):
            raise RuntimeError('Acemd object version ({}) inconsistent with protocol version at instantiation '
                               '({})'.format(type(self.acemd), self._version))

        self._findFiles(inputdir)
        self._amberFixes()

        if self._version == 2:
            self.acemd.temperature = str(self.temperature)
            self.acemd.langevintemp = str(self.temperature)
        elif self._version == 3:
            self.acemd.temperature = self.temperature
            self.acemd.thermostattemperature = self.temperature

        from htmd.units import convert
        numsteps = convert(self.timeunits, 'timesteps', self.runtime, timestep=self.acemd.timestep)
        if self._version == 3:
            self.acemd.run = str(numsteps)

        pdbfile = os.path.join(inputdir, self.acemd.coordinates)
        inmol = Molecule(pdbfile)

        from htmd.builder.builder import detectCisPeptideBonds
        detectCisPeptideBonds(inmol)

        if np.any(inmol.atomselect('lipids')) and not self.useconstantratio:
            logger.warning('Lipids detected in input structure. We highly recommend setting useconstantratio=True '
                           'for membrane simulations.')

        if self._version == 2:
            if self.restraints:
                raise RuntimeWarning('restraints are only available on {}(_version=3)'.format(self.__class__.__name__))
            if self.fb_k > 0:  # use TCL only for flatbottom
                self.acemd.tclforces = 'on'
                if isinstance(self.acemd.TCL, tuple):
                    tcl = list(self.acemd.TCL)
                    tcl[0] = tcl[0].format(NUMSTEPS=numsteps, TEMPERATURE=self.temperature, KCONST=self.fb_k,
                                           REFINDEX=' '.join(map(str, inmol.get('index', self.fb_reference))),
                                           SELINDEX=' '.join(map(str, inmol.get('index', self.fb_selection))),
                                           BOX=' '.join(map(str, self.fb_box)))
                    self.acemd.TCL = tcl[0] + tcl[1]
                else:
                    logger.warning('{} default TCL was already formatted.'.format(self.__class__.__name__))
            else:
                self.acemd.TCL = 'set numsteps {NUMSTEPS}\n'.format(NUMSTEPS=numsteps)
            if self.useconstraints:
                # Turn on constraints
                self.acemd.constraints = 'on'
                self.acemd.constraintscaling = '1.0'
            else:
                if len(self.constraints) != 0:
                    logger.warning('You have setup constraints to {} but constraints are turned off. '
                                   'If you want to use constraints, define '
                                   'useconstraints=True'.format(self.constraints))
        elif self._version == 3:
            if self.restraints is not None:
                logger.info('Using user-provided restraints and ignoring constraints and fb_potential')
                self.acemd.restraints = self.restraints
            else:
                restraints = list()
                if self.fb_k > 0:
                    logger.warning('Converting fb_potential to restraints. This is a convenience '
                                   'functional conversion. We recommend start using restraints with '
                                   '{}(_version=3)'.format(self.__class__.__name__))
                    restraints += self._fb_potential2restraints(inputdir)
                if self.useconstraints:
                    logger.warning('Converting constraints to restraints. This is a convenience '
                                   'functional conversion. We recommend start using restraints with '
                                   '{}(_version=3)'.format(self.__class__.__name__))
                    restraints += self._constraints2restraints()
                else:
                    if len(self.constraints) != 0:
                        logger.warning('You have setup constraints to {} but constraints are turned off. '
                                       'If you want to use constraints, define '
                                       'useconstraints=True'.format(self.constraints))
                if len(restraints) != 0:
                    self.acemd.restraints = restraints

        if self.useconstantratio:
            self.acemd.useconstantratio = 'on'

        if self.adaptive:
            self.acemd.binvelocities = None

        self.acemd.setup(inputdir, outputdir, overwrite=True)

        if self._version == 2:
            # Adding constraints by writing them to the consref file
            if self.useconstraints:
                inconsreffile = os.path.join(inputdir, self.acemd.consref)
                consrefmol = Molecule(inconsreffile)
                consrefmol.set('occupancy', 0)
                consrefmol.set('beta', 0)
                if len(self.constraints) == 0:
                    raise RuntimeError('You have set the production to use constraints (useconstraints=True), but have '
                                       'not defined any constraints (constraints={}).')
                else:
                    for sel in self.constraints:
                        consrefmol.set('beta', self.constraints[sel], sel)
                outconsreffile = os.path.join(outputdir, self.acemd.consref)
                consrefmol.write(outconsreffile)
Ejemplo n.º 5
0
    def write(self, inputdir, outputdir):
        """ Write the equilibration protocol

        Writes the equilibration protocol and files into a folder for execution
        using files inside the inputdir directory

        Parameters
        ----------
        inputdir : str
            Path to a directory containing the files produced by a build process.
        outputdir : str
            Directory where to write the equilibration setup files.

        Examples
        --------
        >>> md = Equilibration()
        >>> md.write('./build','./equil')
        """

        from moleculekit.molecule import Molecule

        # Do version consistency check
        if (self._version == 2 and not isinstance(self.acemd, Acemd2)) and \
                (self._version == 3 and not isinstance(self.acemd, Acemd)):
            raise RuntimeError(
                'Acemd object version ({}) inconsistent with protocol version at instantiation '
                '({})'.format(type(self.acemd), self._version))

        self._findFiles(inputdir)
        self._amberFixes()

        from htmd.units import convert
        numsteps = convert(self.timeunits,
                           'timesteps',
                           self.runtime,
                           timestep=self.acemd.timestep)
        if self._version == 3:
            self.acemd.temperature = self.temperature
            self.acemd.thermostattemperature = self.temperature
            self.acemd.run = str(numsteps)

        pdbfile = os.path.join(inputdir, self.acemd.coordinates)
        inmol = Molecule(pdbfile)

        from htmd.builder.builder import detectCisPeptideBonds
        detectCisPeptideBonds(inmol)

        if np.any(inmol.atomselect('lipids')) and not self.useconstantratio:
            logger.warning(
                'Lipids detected in input structure. We highly recommend setting useconstantratio=True '
                'for membrane simulations.')

        if self.constraintsteps is None:
            constrsteps = int(numsteps / 2)
        else:
            constrsteps = int(self.constraintsteps)

        if self._version == 2:
            if self.restraints:
                raise RuntimeWarning(
                    'restraints are only available on {}(_version=3)'.format(
                        self.__class__.__name__))
            if isinstance(self.acemd.TCL, tuple):
                tcl = list(self.acemd.TCL)
                tcl[0] = tcl[0].format(
                    NUMSTEPS=numsteps,
                    KCONST=self.fb_k,
                    REFINDEX=' '.join(
                        map(str, inmol.get('index', self.fb_reference))),
                    SELINDEX=' '.join(
                        map(str, inmol.get('index', self.fb_selection))),
                    BOX=' '.join(map(str, self.fb_box)),
                    NVTSTEPS=self.nvtsteps,
                    CONSTRAINTSTEPS=constrsteps,
                    TEMPERATURE=self.temperature)
                self.acemd.TCL = tcl[0] + tcl[1]
            else:
                logger.warning('{} default TCL was already formatted.'.format(
                    self.__class__.__name__))
        elif self._version == 3:
            if self.restraints is not None:
                logger.info(
                    'Using user-provided restraints and ignoring constraints and fb_potential'
                )
                self.acemd.restraints = self.restraints
            else:
                logger.warning(
                    'Converting constraints and fb_potential to restraints. This is a convenience '
                    'functional conversion. We recommend start using restraints with '
                    '{}(_version=3)'.format(self.__class__.__name__))
                restraints = list()
                # convert constraints to restraints and add them
                if self.constraints is not None:
                    restraints += self._constraints2restraints(constrsteps)
                # convert fb_potential to restraints and add them
                if self.fb_k > 0:
                    restraints += self._fb_potential2restraints(inputdir)
                self.acemd.restraints = restraints

        if ((self._version == 2) and self.acemd.celldimension is None and self.acemd.extendedsystem is None) or \
            ((self._version == 3) and self.acemd.boxsize is None and self.acemd.extendedsystem is None):
            coords = inmol.get('coords', sel='water')
            if coords.size == 0:  # It's a vacuum simulation
                coords = inmol.get('coords', sel='all')
                dim = np.max(coords, axis=0) - np.min(coords, axis=0)
                dim += 12.
            else:
                dim = np.max(coords, axis=0) - np.min(coords, axis=0)
            if self._version == 2:
                self.acemd.celldimension = '{} {} {}'.format(
                    dim[0], dim[1], dim[2])
            else:
                self.acemd.boxsize = '{} {} {}'.format(dim[0], dim[1], dim[2])

        if self.useconstantratio:
            self.acemd.useconstantratio = 'on'

        self.acemd.setup(inputdir, outputdir, overwrite=True)

        if self._version == 2:
            # Adding constraints by writing them to the consref file
            inconsreffile = os.path.join(inputdir, self.acemd.consref)
            consrefmol = Molecule(inconsreffile)
            consrefmol.set('occupancy', 0)
            consrefmol.set('beta', 0)
            if len(self.constraints) == 0:
                raise RuntimeError(
                    'You have not defined any constraints for the {} ('
                    'constraints={{}}).'.format(self.__class__.__name__))
            else:
                for sel in self.constraints:
                    consrefmol.set('beta', self.constraints[sel], sel)
            outconsreffile = os.path.join(outputdir, self.acemd.consref)
            consrefmol.write(outconsreffile)
Ejemplo n.º 6
0
def build(
    mol,
    topo=None,
    param=None,
    stream=None,
    prefix="structure",
    outdir="./build",
    caps=None,
    ionize=True,
    saltconc=0,
    saltanion=None,
    saltcation=None,
    disulfide=None,
    regenerate=["angles", "dihedrals"],
    patches=None,
    noregen=None,
    aliasresidues=None,
    psfgen=None,
    execute=True,
    _clean=True,
):
    """Builds a system for CHARMM

    Uses VMD and psfgen to build a system for CHARMM. Additionally it allows for ionization and adding of disulfide bridges.

    Parameters
    ----------
    mol : :class:`Molecule <moleculekit.molecule.Molecule>` object
        The Molecule object containing the system
    topo : list of str
        A list of topology `rtf` files.
        Use :func:`charmm.listFiles <htmd.builder.charmm.listFiles>` to get a list of available topology files.
        Default: ['top/top_all36_prot.rtf', 'top/top_all36_lipid.rtf', 'top/top_water_ions.rtf']
    param : list of str
        A list of parameter `prm` files.
        Use :func:`charmm.listFiles <htmd.builder.charmm.listFiles>` to get a list of available parameter files.
        Default: ['par/par_all36_prot.prm', 'par/par_all36_lipid.prm', 'par/par_water_ions.prm']
    stream : list of str
        A list of stream `str` files containing topologies and parameters.
        Use :func:`charmm.listFiles <htmd.builder.charmm.listFiles>` to get a list of available stream files.
        Default: ['str/prot/toppar_all36_prot_arg0.str']
    prefix : str
        The prefix for the generated pdb and psf files
    outdir : str
        The path to the output directory
        Default: './build'
    caps : dict
        A dictionary with keys segids and values lists of strings describing the caps of that segment.
        e.g. caps['P'] = ['first ACE', 'last CT3'] or caps['P'] = ['first none', 'last none'].
        Default: will apply ACE and CT3 caps to proteins and none caps to the rest.
    ionize : bool
        Enable or disable ionization
    saltconc : float
        Salt concentration (in Molar) to add to the system after neutralization.
    saltanion : {'CLA'}
        The anion type. Please use only CHARMM ion atom names.
    saltcation : {'SOD', 'MG', 'POT', 'CES', 'CAL', 'ZN2'}
        The cation type. Please use only CHARMM ion atom names.
    disulfide : list of pairs of atomselection strings
        If None it will guess disulfide bonds. Otherwise provide a list pairs of atomselection strings for each pair of
        residues forming the disulfide bridge.
    regenerate : None or list of strings of: ['angles', 'dihedrals']
        Disable angle/dihedral regeneration with `regenerate=None`, or enable it with `regenerate=['angles', 'diheldrals']`
        or just one of the two options with `regenerate=['angles']` or `regenerate=['diheldrals']`.
    patches : list of str
        Any further patches the user wants to apply
    noregen : list of str
        A list of patches that must not be regenerated (angles and dihedrals)
        Default: ['FHEM', 'PHEM', 'PLOH', 'PLO2', 'PLIG', 'PSUL']
    aliasresidues : dict of aliases
        A dictionary of key: value pairs of residue names we want to alias
    psfgen : str
        Path to psfgen executable used to build for CHARMM
    execute : bool
        Disable building. Will only write out the input script needed by psfgen. Does not include ionization.

    Returns
    -------
    molbuilt : :class:`Molecule <moleculekit.molecule.Molecule>` object
        The built system in a Molecule object

    Example
    -------
    >>> from htmd.ui import *
    >>> mol = Molecule("3PTB")
    >>> mol.filter("not resname BEN")
    >>> molbuilt = charmm.build(mol, outdir='/tmp/build', ionize=False)  # doctest: +ELLIPSIS
    Bond between A: [serial 185 resid 42 resname CYS chain A segid 0]
                 B: [serial 298 resid 58 resname CYS chain A segid 0]...
    >>> # More complex example
    >>> topos  = ['top/top_all36_prot.rtf', './BEN.rtf', 'top/top_water_ions.rtf']
    >>> params = ['par/par_all36_prot.prm', './BEN.prm', 'par/par_water_ions.prm']
    >>> disu = [['segid P and resid 157', 'segid P and resid 13'], ['segid K and resid 1', 'segid K and resid 25']]
    >>> ar = {'SAPI24': 'SP24'}  # Alias large resnames to a short-hand version
    >>> molbuilt = charmm.build(mol, topo=topos, param=params, outdir='/tmp/build', saltconc=0.15, disulfide=disu, aliasresidues=ar)  # doctest: +SKIP
    """

    mol = mol.copy()
    _missingSegID(mol)
    _checkMixedSegment(mol)
    _checkLongResnames(mol, aliasresidues)
    if psfgen is None:
        psfgen = shutil.which("psfgen", mode=os.X_OK)
        if not psfgen:
            raise FileNotFoundError(
                "Could not find psfgen executable, or no execute permissions are given. "
                "Run `conda install psfgen -c acellera`.")
    if not os.path.isdir(outdir):
        os.makedirs(outdir)
    if _clean:
        _cleanOutDir(outdir)
    if topo is None:
        topo = defaultTopo()
    if param is None:
        param = defaultParam()
    if stream is None:
        stream = defaultStream()
    if caps is None:
        caps = _defaultCaps(mol)
    # patches that must _not_ be regenerated
    if noregen is None:
        noregen = ["FHEM", "PHEM", "PLOH", "PLO2", "PLIG", "PSUL"]

    alltopo = topo.copy()
    allparam = param.copy()

    # Splitting the stream files and adding them to the list of parameter and topology files
    charmmdir = htmdCharmmHome()
    for s in stream:
        if s[0] != "." and path.isfile(path.join(charmmdir, s)):
            s = path.join(charmmdir, s)
        outrtf, outprm = _prepareStream(s)
        alltopo.append(outrtf)
        allparam.append(outprm)

    # _missingChain(mol)
    # _checkProteinGaps(mol)
    if patches is None:
        patches = []
    if isinstance(patches, str):
        patches = [patches]
    allpatches = []
    allpatches += patches
    # Find protonated residues and add patches for them
    allpatches += _protonationPatches(mol)

    f = open(path.join(outdir, "build.vmd"), "w")
    f.write("# psfgen file generated by charmm.build\n")
    f.write("package require psfgen;\n")
    f.write("psfcontext reset;\n\n")

    # Copying and printing out the topologies
    if not path.exists(path.join(outdir, "topologies")):
        os.makedirs(path.join(outdir, "topologies"))
    for i in range(len(alltopo)):
        if alltopo[i][0] != "." and path.isfile(
                path.join(charmmdir, alltopo[i])):
            alltopo[i] = path.join(charmmdir, alltopo[i])
        localname = "{}.".format(i) + path.basename(alltopo[i])
        shutil.copy(alltopo[i], path.join(outdir, "topologies", localname))
        f.write("topology " + path.join("topologies", localname) + "\n")
    f.write("\n")

    _printAliases(f)
    if aliasresidues is not None:  # User defined aliases
        for key, val in aliasresidues.items():
            mol.resname[mol.resname == key] = val
            f.write("        pdbalias residue {} {}\n".format(val, key))

    # Printing out segments
    if not path.exists(path.join(outdir, "segments")):
        os.makedirs(path.join(outdir, "segments"))
    logger.info("Writing out segments.")
    segments = _getSegments(mol)
    wateratoms = mol.atomselect("water")
    for seg in segments:
        pdbname = "segment" + seg + ".pdb"
        segatoms = mol.segid == seg
        mol.write(path.join(outdir, "segments", pdbname), sel=segatoms)

        segwater = wateratoms & segatoms
        f.write("segment " + seg + " {\n")
        if np.all(segatoms ==
                  segwater):  # If segment only contains waters, set: auto none
            f.write("\tauto none\n")
        f.write("\tpdb " + path.join("segments", pdbname) + "\n")
        if caps is not None and seg in caps:
            for c in caps[seg]:
                f.write("\t" + c + "\n")
        f.write("}\n")
        f.write("coordpdb " + path.join("segments", pdbname) + " " + seg +
                "\n\n")

    if (disulfide is not None and len(disulfide) != 0
            and isinstance(disulfide[0][0], str)):
        disulfide = convertDisulfide(mol, disulfide)

    if disulfide is None:
        disulfide = detectDisulfideBonds(mol)

    if len(disulfide) != 0:
        for d in sorted(disulfide, key=lambda x: x[0].segid):
            str0 = f"{d[0].segid}:{d[0].resid}{d[0].insertion}"
            str1 = f"{d[1].segid}:{d[1].resid}{d[1].insertion}"
            f.write(f"patch DISU {str0} {str1}\n")
        f.write("\n")

    noregenpatches = [p for p in allpatches if p.split()[1] in noregen]
    regenpatches = [p for p in allpatches if p.split()[1] not in noregen]

    # Printing regenerable patches
    if len(regenpatches) != 0:
        for p in regenpatches:
            f.write(p + "\n")
        f.write("\n")

    # Regenerate angles and dihedrals
    if regenerate is not None:
        f.write("regenerate {}\n".format(" ".join(regenerate)))
        f.write("\n")

    # Printing non-regenerable patches
    if len(noregenpatches) != 0:
        for p in noregenpatches:
            f.write(p + "\n")
        f.write("\n")

    f.write("guesscoord\n")
    f.write("writepsf " + prefix + ".psf\n")
    f.write("writepdb " + prefix + ".pdb\n")
    # f.write('quit\n')
    f.close()

    if allparam is not None:
        combine(allparam, path.join(outdir, "parameters"))

    molbuilt = None
    if execute:
        logpath = os.path.abspath("{}/log.txt".format(outdir))
        logger.info("Starting the build.")
        currdir = os.getcwd()
        os.chdir(outdir)
        f = open(logpath, "w")
        # call([vmd, '-dispdev', 'text', '-e', './build.vmd'], stdout=f)
        my_env = os.environ.copy()
        my_env["LC_ALL"] = "C"
        call([psfgen, "./build.vmd"], stdout=f, stderr=f, env=my_env)
        f.close()
        errors = _logParser(logpath)
        os.chdir(currdir)
        if errors:
            raise BuildError(errors + [
                "Check {} for further information on errors in building.".
                format(logpath)
            ])
        logger.info("Finished building.")

        if path.isfile(path.join(outdir, "structure.pdb")) and path.isfile(
                path.join(outdir, "structure.psf")):
            molbuilt = Molecule(path.join(outdir, "structure.pdb"))
            molbuilt.read(path.join(outdir, "structure.psf"))
        else:
            raise BuildError(
                "No structure pdb/psf file was generated. Check {} for errors in building."
                .format(logpath))

        if ionize:
            os.makedirs(path.join(outdir, "pre-ionize"))
            data = glob(path.join(outdir, "*"))
            for f in data:
                shutil.move(f, path.join(outdir, "pre-ionize"))
            totalcharge = np.sum(molbuilt.charge)
            nwater = np.sum(molbuilt.atomselect("water and noh"))
            anion, cation, anionatom, cationatom, nanion, ncation = ionizef(
                molbuilt,
                totalcharge,
                nwater,
                saltconc=saltconc,
                anion=saltanion,
                cation=saltcation,
            )
            newmol = ionizePlace(mol, anion, cation, anionatom, cationatom,
                                 nanion, ncation)
            # Redo the whole build but now with ions included
            return build(
                newmol,
                topo=alltopo,
                param=allparam,
                stream=[],
                prefix=prefix,
                outdir=outdir,
                ionize=False,
                caps=caps,
                execute=execute,
                saltconc=saltconc,
                disulfide=disulfide,
                regenerate=regenerate,
                patches=patches,
                noregen=noregen,
                aliasresidues=aliasresidues,
                psfgen=psfgen,
                _clean=False,
            )
    _checkFailedAtoms(molbuilt)
    _recoverProtonations(molbuilt)
    detectCisPeptideBonds(molbuilt,
                          respect_bonds=True)  # Warn in case of cis bonds
    return molbuilt