Exemple #1
0
def read_model(filename):
    """
    Read a ModelConfig model.

    :param filename: name of file to be saved to
    :type filename: str

    :returns: a ModelConfig model
    """

    model = ModelConfig()
    model.read(filename)

    try:
        model.check_data(model['data.hash'], model['data.hash_type'])

        if not model['is.valid']:
            # FIXME: change exception type
            raise errors.SetupError('invalid model %s' % model['name'])
    except KeyError:
        raise errors.SetupError('invalid model %s' % model['name'])

    model.check_keys()

    return model
Exemple #2
0
    def get_charge(self):
        """
        Get the protein charge via leap.

        :raises: SetupError
        """

        mol_file = self.mol_file

        if not os.access(mol_file, os.R_OK):
            raise errors.SetupError(
                'the protein start file %s does not exist ' % mol_file)

        out = utils.run_leap(
            '', '', 'tleap',
            '%s\np = loadpdb %s\ncharge p\n' % (self.ff_cmd, mol_file))

        charge = None

        for line in out.split('\n'):
            if 'Total unperturbed charge:' in line[0:]:
                charge = line.split(':')[1]
                break

        if charge:
            try:
                self.charge = float(charge)
            except ValueError:
                raise errors.SetupError(
                    'Cannot convert charge from string: %s' % charge)
        else:
            raise errors.SetupError('leap cannot compute charge')

        logger.write('Protein charge: %.3f' % self.charge)
Exemple #3
0
    def _run_mdprog(self, suffix, config, mask, restr_force):
        """
        Run DL_POLY executable.
        """

        if mask:
            self._make_restraints(mask, restr_force)

        self.dlpoly.writeField(FIELD_FILENAME)

        with open(CONTROL_FILENAME, 'w') as mdin:
            mdin.writelines(config)

        retc, out, err = utils.run_exe(' '.join(
            (self.mdpref, self.mdprog, self.mdpost)))

        if retc:
            logger.write(err)
            raise errors.SetupError('%s has failed (see logfile)' %
                                    self.mdprog)

        for mfile in MOVE_LIST:
            try:
                shutil.move(mfile, mfile + os.extsep + suffix)
            except IOError:  # some files may not be created
                continue

        try:
            shutil.copy2(REVCON_FILENAME, REVCON_FILENAME + os.extsep + suffix)
            shutil.move(REVCON_FILENAME, CONFIG_FILENAME)
        except IOError as why:
            raise errors.SetupError(why)

        self.run_no += 1
Exemple #4
0
    def minimize(self,
                 namelist='%ALL',
                 nsteps=100,
                 ncyc=10,
                 restraint='',
                 restr_force=10.0):
        """
        Use the AMBER/sander module to minimize a system.

        :param config: MD paramers, if start with '%' respective default is
           chosen, otherwise a full sander namelist as string
        :type config: string
        :param nsteps: maximum number of conjugated gradient steps
        :type nsteps: integer
        :param ncyc: number of initial steepest decent steps
        :type ncyc: float
        :param restraint: pre-defined restraints
        :type restraint: string
        :param restr_force: force constant for the restraints in kcal/mol/AA
        :type restr_force: float
        :raises: SetupError
        """

        if not self.amber_top or not self.amber_crd:
            raise errors.SetupError('Topology and/or coordinates missing.  '
                                    'Please run amber_create_top first.')

        self.mdengine.minimize(namelist, nsteps, ncyc, restraint, restr_force)
        self.amber_crd = self.mdengine.sander_crd  # FIXME: only for AMBER
Exemple #5
0
    def _self_check(self, mdprog):
        """
        Check DL_POLY installation.

        :param mdprog: the dl_poly executable provided to the class
        :type mdprog: str
        """

        if not 'DLPOLYHOME' in os.environ:
            raise errors.SetupError('DLPOLYHOME not set')

        self.mdprog = os.path.join(os.environ['DLPOLYHOME'], 'execute', mdprog)

        if not os.access(self.mdprog, os.X_OK):
            raise errors.SetupError('DLPOLYHOME does not have a %s binary' %
                                    mdprog)
Exemple #6
0
    def _self_check(self, mdprog):
        """
        Check NAMD installation.

        :param mdprog: the namd executable provided to the class
        :type mdprog: str
        """

        if not 'AMBERHOME' in os.environ:
            raise errors.SetupError('AMBERHOME not set')

        self.mdprog = os.path.join(os.environ['AMBERHOME'], 'bin', mdprog)

        if not os.access(self.mdprog, os.X_OK):
            raise errors.SetupError('AMBERHOME does not have a %s binary' %
                                    mdprog)
Exemple #7
0
    def copy_files(srcs, filenames=None, overwrite=False):
        """
        Copy files from basedir to the current directory.

        :param src: the source directories to copy from
        :type src: list of str
        :param filenames: the filenames to be copied
        :type filenames: list of str
        :param overwrite: overwrite the files in the current dir?
        :type overwrite: bool
        """

        try:
            for src in srcs:
                logger.write('%sopying directory contents of %s to %s' %
                             ('Overwrite mode: c' if overwrite else 'C', src,
                              os.getcwd()))

                if not filenames:
                    filenames = os.listdir(src)

                for filename in filenames:
                    if overwrite or not os.access(filename, os.F_OK):
                        src_file = os.path.join(src, filename)

                        # FIXME: only here to accommodate Complex
                        if os.access(src_file, os.F_OK):
                            shutil.copy(src_file, '.')

        except OSError as why:
            raise errors.SetupError(why)
Exemple #8
0
    def _run_mdprog(self, prefix, config, mask, restr_force):
        """
        Run mdrun from the Gromacs package.
        """

        filename = prefix + os.extsep
        config_filename = '_' + filename + 'mdp'

        with open(config_filename, 'w') as mdin:
            mdin.writelines(config)

        if self.run_no == 1:
            params = ('-f %s -c %s -r %s -p %s -o %s -po %s' %
                      (config_filename, self.gro, self.gro, self.top,
                       filename + 'tpr', filename + 'mdp') )
        else:
            edr = self.prev + os.extsep + 'edr'
            trr = self.prev + os.extsep + 'trr'

            params = ('-f %s -c %s -r %s -e %s -t %s -p %s -o %s -po %s' %
                      (config_filename, self.prev + os.extsep + 'gro',
                       self.gro, edr, trr, self.top, filename + 'tpr',
                       filename + 'mdp'))

        if mask:
             self._make_restraints(mask, restr_force)

        retc, out, err = utils.run_exe(' '.join((self.grompp, params)))

        if retc:
            logger.write(err)
            raise errors.SetupError('%s has failed (see logfile)' %
                                    self.grompp)

        params = '-deffnm %s' % prefix

        retc, out, err = utils.run_exe(' '.join((self.mdpref, self.mdprog,
                                                 self.mdpost, params)))

        if retc:
            logger.write(err)
            raise errors.SetupError('%s has failed (see logfile) has failed' %
                                    self.mdprog)

        self.run_no += 1
        self.prev = prefix
Exemple #9
0
def read_model(filename):

    model = ModelConfig()
    model.read(filename)

    try:
        model.check_data(model['data.hash'], model['data.hash_type'])

        if not model['is.valid']:
            # FIXME: change exception type
            raise errors.SetupError('invalid model %s' % model['name'])
    except KeyError:
        raise errors.SetupError('invalid model %s' % model['name'])

    model.check_keys()

    return model
Exemple #10
0
    def md(self, config='', nsteps=1000, T=300.0, p=1.0, mask='',
           restr_force=5.0, nrel=1, wrap=True, dt=0.002):
        """
        Run molecular dynamics on a system.

        :param config: MD paramers, if start with '%' respective default is
           chosen, otherwise a full mdrun input as string
        :type config: string
        :param nsteps: maximum number of MD steps
        :type nsteps: integer
        :param T: temperature
        :type T: float
        :param p: pressure
        :type p: float
        :param mask: pre-defined restraints or AMBER mask
        :type mask: string
        :param restr_force: force constant for the restraints in kcal/mol/AA
        :type restr_force: float
        :param nrel: number of restraint relaxation steps
        :type nrel: integer
        :param wrap: wrap coordinates in a periodic system
        :type wrap: bool
        :param dt: timestep in ps
        :type dt: float
        :raises: SetupError
        """

        prefix = mdebase.MD_PREFIX + '%05i' % self.run_no

        if self.run_no == 1:
            gen_vel = 'yes'
        else:
            gen_vel = 'no'

        if config[0] == '%':
            pname = config[1:]
            logger.write('Running MD with protocol %s' % pname)

            try:
                config = PROTOCOLS['MD_%s' % pname]
            except KeyError:
                raise errors.SetupError('no such MD protocol predefined: '
                                        '%s' % config)

            if mask:
                cons = 'define = -DPOSRES'
            else:
                cons = ''

            # read coordinates from self.amber_pdb because origin is at center
            # of cell, it is on one edge in the parm file!
            #
            # FIXME: water model, box shape
            config = config.format(cons, nsteps, nsteps / 10, nsteps / 5,
                                   dt, nsteps * dt, gen_vel, T, p)

        self._run_mdprog(prefix, config, mask, restr_force)
        self.prefix = prefix
Exemple #11
0
    def md(self, namelist='', nsteps=1000, T=300.0, p=1.0,
           restr_str='', restr_force=5.0, nrel=1, wrap=True, dt=0.002):
        """
        Use the sander module from AMBER to run molecular dynamics on a system.

        :param namelist: MD paramers, if start with '%' respective default is
           chosen, otherwise a full sander namelist as string
        :type namelist: string
        :param nsteps: maximum number of MD steps
        :type nsteps: integer
        :param T: temperature
        :type T: float
        :param p: pressure
        :type p: float
        :param restr_str: pre-defined restraints or AMBER mask
        :type restr_str: string
        :param restr_force: force constant for the restraints in kcal/mol/AA
        :type restr_force: float
        :param nrel: number of restraint relaxation steps
        :type nrel: integer
        :param wrap: wrap coordinates in a periodic system
        :type wrap: bool
        :param dt: timestep in ps
        :type dt: float
        :raises: SetupError
        """

        constp = False

        if namelist[0] == '%':
            pname = namelist[1:]
            logger.write('Running MD with protocol %s' % pname)

            if pname == 'PRESS' or pname == 'SHRINK':
                constp = True

            try:
                namelist = PROTOCOLS['MD_%s' % pname]
            except KeyError:
                raise errors.SetupError('no such MD protocol predefined: '
                                        '%s' % namelist)

            restraint = ''

            if restr_str:
                try:
                    mask = mdebase._restraint_table[restr_str]
                except KeyError:
                    mask = restr_str

                restraint = (mdebase._rs % (restr_force, mask) )

            namelist = namelist.format(nsteps, nsteps / 5, nsteps / 10,
                                       self.md_periodic + restraint,
                                       '{:d}'.format(wrap), T, p, dt)


        self._run_mdprog(mdebase.MD_PREFIX, namelist, mask, constp)
Exemple #12
0
    def get_box_dims(self):
        """
        Extract box information from rst7 file.

        :returns: box dimensions
        """

        config_file = CONFIG_FILENAME
        line_no = 0
        box_dims = []

        with open(config_file, 'r') as cfg:
            for line in cfg:
                line_no += 1

                if line_no == 1:
                    continue

                if line_no == 2:
                    try:
                        imcon = int(line.split()[1])
                    except ValueError:
                        raise errors.SetupError('invalid line %i in file %s' %
                                                (line_no, config_file))
                    continue

                if line_no < 5 and imcon > 0:
                    # FIXME: rectangular box only
                    try:
                        x, y, z = line.split()
                        box_dims.append(float(x))

                        x, y, z = cfg.next().split()
                        box_dims.append(float(y))

                        x, y, z = cfg.next().split()
                        box_dims.append(float(z))
                    except ValueError:
                        raise errors.SetupError('invalid cell in file %s' %
                                                config_file)
                    break

        box_dims.extend((90.0, 90.0, 90.0))
        return box_dims
Exemple #13
0
    def mk_esp(self,
               program='gauss',
               gkeys='',
               gmem='',
               gnproc='',
               gus_header=GUS_HEADER):
        """
        Create an input file for ESP calculation using the MK scheme.
        The input is written for either Gaussian or Gamess-US.

        :param program: ab initio QM program, either gauss or gus
        :type program: string
        :param gkeys: addition keys for Gaussian (antechamber)
        :type gkeys: string
        :param gmem: memory information for Gaussian (antechamber)
        :type gmem: string
        :param gnproc: number of processors for Gaussian (antechamber)
        :type gnproc: string
        :param gus_header: Gamess-US ESP control parameters
        :type gus_header: string
        """

        if program == 'gauss':
            antechamber = utils.check_amber('antechamber')

            ac_cmd = [
                '-i %s' % self.mol_file,
                '-fi %s' % self.mol_fmt,
                '-o %s' % GAUSS_INP, '-fo gcrt -pf y'
            ]

            if gkeys:
                ac_cmd.append('-gk "%s"' % gkeys)
            if gmem:
                ac_cmd.append('-gm "%s"' % gmem)
            if gnproc:
                ac_cmd.append('-gn "%s"' % gnproc)

            utils.run_amber(antechamber, ' '.join(ac_cmd))
        elif program == 'gus':
            conv = ob.OBConversion()
            conv.SetInAndOutFormats(self.mol_fmt, 'gamin')

            obm = ob.OBMol()
            conv.ReadFile(obm, self.mol_file)

            inp = conv.WriteString(obm)
            nl = inp.find('\n') + 1  # skip first line

            with open(GUS_INP, 'w') as gus:
                gus.writelines(gus_header + inp[nl:])

        else:
            raise errors.SetupError('Unknow QM program %s' % program)
Exemple #14
0
    def set_atomtype(self, atomtype):
        """
        :param atomtype: set the current force field atom type to either 'gaff'
           or 'sybyl'
        :type atomtype: string
        """

        if atomtype not in const.KNOWN_MOL2_ATOMTYPES:
            raise errors.SetupError('Only %s atom types are supported' %
                                    str(const.KNOWN_MOL2_ATOMTYPES))

        self.mol_atomtype = atomtype
Exemple #15
0
def preminimize(self, add_hyd = False, ffield = 'mmff94', nsteps = 10):
    """
    Preminimize mol2 file using the small molecule force fields of OpenBabel.
    Typically usage is to avoid convergence problems with sqm due to a 'bad'
    starting structure.

    :param add_hyd: add hydrogens
    :type add_hyd: bool
    :param ffield: small molecule force field supported by OpenBabel
    :type ffield: string
    :param nsteps: number of minimisation steps
    :type nsteps: integer
    :raises: SetupError
    """

    logger.write('Minimizing %s (%s format)' %
                 (self.mol_file, self.mol_fmt) )

    # NOTE: Openbabel may miscalculate charges from mol2 files
    try:
        mol = pybel.readfile(self.mol_fmt, self.mol_file).next()
    except IOError as why:
        raise errors.SetupError(why)

    if add_hyd:
        logger.write('Adding hydrogens')
        mol.addh()

    mol.localopt(forcefield = ffield, steps = nsteps)

    logger.write('Writing structure to %s (%s format)' %
                 (self.mol_file, self.mol_fmt) )

    try:
        mol.write(self.mol_fmt, self.mol_file, overwrite = True)
    except IOError as why:
        raise errors.SetupError(why)

    self.ref_file = self.mol_file
    self.ref_fmt = self.mol_fmt
Exemple #16
0
def ob_read_one(conv, filen, mol, ifmt, ofmt):
    """
    Use Openbabel to read one structure from a molecular structure file.

    :param conv: Openbanel conversion object
    :type conv: OBConversion
    :param filen: molecular structure file name
    :type filen: string
    :param mol: Openbabel molecule
    :type mol: OBMol
    :param fmt: molecular structure file format known to Openbabel
    :type fmt: string
    :raises: SetupError
    """

    if not conv.SetInAndOutFormats(ifmt, ofmt):
        raise errors.SetupError('conversion from %s to %s not supported '
                                'by Openbabel' % (ifmt, ofmt) )

    # set error level to avoid warnings about non-standard input
    errlev = ob.obErrorLog.GetOutputLevel()
    ob.obErrorLog.SetOutputLevel(0)

    try:
        success = conv.ReadFile(mol, filen)
    except IOError as why:
        raise errors.SetupError(why)

    ob.obErrorLog.SetOutputLevel(errlev)

    if not success:
        txt = 'cannot read %s (%s format), ' % (filen, ifmt)

        if not os.path.isfile(filen):
            raise errors.SetupError(txt + 'file does not exist')
        else:
            raise errors.SetupError(txt + 'check if file/format is valid')

    return mol, conv
Exemple #17
0
def ssbonds(ss_file, offset=0):
    """
    Read file with SS-bond information.

    :param ss_file: filename with disulfide bond information
    :type ss_file: str
    :param offset: offset to work around leap indexing
    :type offset: int

    :returns: pair of SS-bond indices
    """

    lcnt = 0
    pairs = []

    with open(ss_file, 'r') as ssb:
        for line in ssb:
            lcnt += 1
            l = line.lstrip()

            if l.startswith('#') or l == '':
                continue

            try:
                a, b = line.split()
                a, b = int(a) + offset, int(b) + offset

                if a == b:
                    raise errors.SetupError(
                        'malformed file %s in '
                        'line %i: residue numbers cannot be the '
                        'same' % (lcnt, ss_file))

                pairs.append((a, b))
            except (ValueError, TypeError) as why:
                raise errors.SetupError('malformed file %s in '
                                        'line %i: %s' % (ss_file, lcnt, why))

    return pairs
Exemple #18
0
def run_leap(top, crd, program='tleap', script=''):
    """
    Simple wrapper to execute the AMBER leap program.

    :param top: topology file name, used to check if created
    :type top: string
    :param crd: coordinate file name, used to check if created
    :type crd: string
    :param program: program file name, either tleap or sleap (not recommended)
    :type program: string
    :param script: leap script as string, if 'leap.in' read from respective file
      name
    :type script: string
    :returns: output from leap
    :raises: SetupError
    """

    leap = check_amber(program)
    cmd = [leap, '-f']

    env = _setenv()

    if script == 'leap.in':
        cmd.append(script)
        logger.write('Executing command:\n%s' % ' '.join(cmd))

        proc = subp.Popen(cmd,
                          stdin=None,
                          stdout=subp.PIPE,
                          stderr=subp.PIPE,
                          env=env)
        out = proc.communicate()[0]
    else:
        cmd.append('-')
        logger.write('Executing command:\n%s -f - <<_EOF \n%s\n_EOF\n' %
                     (leap, script))

        proc = subp.Popen(cmd,
                          stdin=subp.PIPE,
                          stdout=subp.PIPE,
                          stderr=subp.PIPE,
                          env=env)
        out = proc.communicate(script)[0]

    if top and crd:
        if os.path.getsize(top) == 0 or os.path.getsize(crd) == 0:
            raise errors.SetupError(
                'Leap did not create the topology and/or coordinate '
                'file(s): %s, %s' % (top, crd))

    return out
Exemple #19
0
    def md(self,
           namelist='',
           nsteps=1000,
           T=300.0,
           p=1.0,
           restraint='',
           restr_force=10.0,
           nrestr=1,
           wrap=True):
        """
        Use the sander module from AMBER to run molecular dynamics on a system.

        :param namelist: MD paramers, if start with '%' respective default is
           chosen, otherwise a full sander namelist as string
        :type namelist: string
        :param nsteps: maximum number of MD steps
        :type nsteps: integer
        :param T: temperature
        :type T: float
        :param p: pressure
        :type p: float
        :param restraint: pre-defined restraints
            backbone, heavy, protein, notligand, notsolvent or empty string
            for no restraints.  Any other string is passed on as a mask.  Must
            be set to any true value if namelist is not a preset template and
            restraints are to be used in the custom namelist.
        :type restraint: string
        :param restr_force: force constant for the restraints in kcal/mol/AA
        :type restr_force: float
        :param nrel: number of restraint relaxation steps
        :type nrel: integer
        :param wrap: wrap coordinates in a periodic system
        :type wrap: bool
        :raises: SetupError
        """

        if not namelist:
            raise errors.SetupError('No namelist supplied.')

        # FIXME: clean up nrestr to be consistent between MD programs
        self.mdengine.md(namelist, nsteps, T, p, restraint, restr_force,
                         nrestr, wrap)

        # FIXME: do we also want to density?
        self.box_dims = self.mdengine.get_box_dims()
        self.amber_crd = self.mdengine.sander_crd  # FIXME: only for AMBER
Exemple #20
0
def _lambda_paths(dummies0, dummies1, separate=True):
    '''
    Compute lambda paths depending on what state the dummy atoms are in.
    Works for appearing or disappearing atoms only.

    :param initial: the initial state of the morph pair
    :type initial: either Ligand or Complex
    :param final: the final state of the morph pair
    :type final: either Ligand or Complex
    :param separate: separate vdW from elecstrostatic lambda
    :type separate: bool
    '''

    fepl = '0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0'
    vdwl = '0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0'
    masl = '0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0'

    if not dummies0 and not dummies1:
        seps = 'linear transformation only'
        sc_coul = 'no'
    elif not separate:
        seps = 'one-step protocol'
        sc_coul = 'yes'
    elif dummies0:
        fepl = ('0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.2 0.4 '
                '0.6 0.8 1.0')
        vdwl = ('0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.0 1.0 '
                '1.0 1.0 1.0')
        masl = ('0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 '
                '0.0 0.0 0.0')
        seps = 'appearing atoms: vdW before q_on'
        sc_coul = 'no'
    elif dummies1:
        fepl = ('0.0 0.2 0.4 0.6 0.8 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 '
                '1.0 1.0 1.0')
        vdwl = ('0.0 0.0 0.0 0.0 0.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 '
                '0.8 0.9 1.0')
        masl = ('0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 '
                '0.0 0.0 0.0')
        seps = 'disappearing atoms: q_off before vdW'
        sc_coul = 'no'
    else:
        raise errors.SetupError('BUG: gromacs morph tolopgy - unexpected '
                                'dummies in "dummy" protocol')

    return fepl, vdwl, masl, seps, sc_coul
Exemple #21
0
    def __init__(self, FE_sub_type, separate, ff, con_morph, atoms_initial,
                 atoms_final, lig_initial, lig_final, atom_map,
                 reverse_atom_map, zz_atoms, gaff):

        if FE_sub_type[:4] != 'pert':
            raise errors.SetupError('Morph code only supports the '
                                    'CHARMM/PERT module')
        self.separate = separate
        self.ff = ff
        self.gaff = gaff
        self.con_morph = con_morph
        self.atoms_initial = atoms_initial
        self.atoms_final = atoms_final
        self.lig_initial = lig_initial
        self.lig_final = lig_final
        self.atom_map = atom_map
        self.reverse_atom_map = reverse_atom_map
        self.zz_atoms = zz_atoms

        self.topol = None
        self.itypes = []
        self.ftypes = []

        self.files_created = []

        self.dummies0 = not all([a.atom for a in self.atom_map.keys()])
        self.dummies1 = not all([a.atom for a in self.atom_map.values()])

        if not self.dummies0 and not self.dummies1:
            self.softcore = 'nopssp'
        else:
            self.softcore = 'pssp'

        if self.separate and self.dummies0 and self.dummies1:
            self.FE_sub_type = 'pert3'
        elif self.separate and (self.dummies0 or self.dummies1):
            self.FE_sub_type = 'pert2'
        else:
            self.FE_sub_type = 'pert'

            if self.separate:
                logger.write('Warning: linear transformation, not separated '
                             'into vdw and electrostatic step\n')

        self.stype = self.FE_sub_type
Exemple #22
0
    def minimize(self, config='%STD', nsteps=100, ncyc=100, mask='',
                 restr_force=5.0):
        """
        Minimize a system.

        :param config: MD paramers, if start with '%' respective default is
           chosen, otherwise a full mdrun input as string
        :type config: string
        :param nsteps: maximum number of conjugated gradient steps
        :type nsteps: integer
        :param ncyc: number of initial steepest decent steps
        :type ncyc: float
        :param mask: pre-defined restraints or AMBER mask
        :type mask: string
        :param restr_force: force constant for the restraints in kcal/mol/AA
        :type restr_force: float
        :raises: SetupError
        """

        prefix = mdebase.MIN_PREFIX + '%05i' % self.run_no

        if config[0] == '%':
            pname = config[1:]
            logger.write('Running minimisation with protocol %s' % pname)

            try:
                config = PROTOCOLS['MIN_%s' % pname]
            except KeyError:
                raise errors.SetupError('no such min protocol predefined: '
                                        '%s' % config)

            if mask:
                cons = 'define = -DPOSRES'
            else:
                cons = ''

            # read coordinates from self.amber_pdb because origin is at center
            # of cell, it is on one edge in the parm file!
            #
            # FIXME: water model, box shape
            config = config.format(cons, nsteps, ncyc, nsteps / 10, nsteps / 5)

        self._run_mdprog(prefix, config, mask, restr_force)
        self.prefix = prefix
Exemple #23
0
    def minimize(self, namelist='%ALL', nsteps=100, ncyc=10, restr_str='',
                 restr_force=5.0):
        """
        Use the AMBER/sander module to minimize a system.

        :param config: MD paramers, if start with '%' respective default is
           chosen, otherwise a full sander namelist as string
        :type config: string
        :param nsteps: maximum number of conjugated gradient steps
        :type nsteps: integer
        :param ncyc: number of initial steepest decent steps
        :type ncyc: float
        :param restr_str: pre-defined restraints or AMBER mask
        :type restr_str: string
        :param restr_force: force constant for the restraints in kcal/mol/AA
        :type restr_force: float
        :raises: SetupError
        """

        if namelist[0] == '%':
            pname = namelist[1:]
            logger.write('Running minimisation with protocol %s' % pname)

            try:
                namelist = PROTOCOLS['MIN_%s' % pname]
            except KeyError:
                raise errors.SetupError('no such min protocol predefined: '
                                        '%s' % namelist)

            restraint = ''

            if restr_str:
                try:
                    mask = mdebase._restraint_table[restr_str]
                except KeyError:
                    mask = restr_str

                restraint = (mdebase._rs % (restr_force, mask) )

            namelist = namelist.format(nsteps, ncyc, nsteps / 5,
                                       self.min_periodic + restraint)

        self._run_mdprog(mdebase.MIN_PREFIX, namelist, mask, False)
Exemple #24
0
    def prepare_top(self, gaff='gaff', pert=None, add_frcmods=[]):
        """
        Prepare for parmtop creation i.e. add molecules to Leap structure.
        This needs to be run before create_top() to ensure that the molecule
        has been added but also to not trigger parmtop generation to early.
        Pmemd needs to have a second molecule added in alchemical free
        energy setups.
        """

        if self.mol_fmt == 'mol2':
            if self.mol_atomtype != self.gaff:
                mol_file = const.GAFF_MOL2_FILE
                antechamber = utils.check_amber('antechamber')

                utils.run_amber(
                    antechamber, '-i %s -fi ac '
                    '-o %s -fo mol2 '
                    '-at %s -s 2 -pf y' %
                    (const.LIGAND_AC_FILE, mol_file, self.gaff))
                self.mol_file = mol_file
        elif self.mol_fmt == 'pdb':
            pass
        else:
            raise errors.SetupError('unsupported leap input format: %s (only '
                                    'mol2 and pdb)' % self.mol_fmt)

        if os.path.isfile(self.frcmod):
            frcmods = [self.frcmod]
        else:
            frcmods = []

        if add_frcmods:
            frcmods.extend(add_frcmods)

        if not self.leap_added:
            self.leap.add_force_field(self.gaff)
            self.leap.add_mol(self.mol_file, self.mol_fmt, frcmods, pert=pert)

            self.leap_added = True
Exemple #25
0
    def minimize(self,
                 config='%STD',
                 nsteps=100,
                 ncyc=100,
                 mask='',
                 restr_force=5.0):
        """
        Minimize a system.

        :param config: MD paramers, if start with '%' respective default is
           chosen, otherwise a full mdrun input as string
        :type config: string
        :param nsteps: maximum number of conjugated gradient steps
        :type nsteps: integer
        :param ncyc: number of initial steepest decent steps
        :type ncyc: float
        :param mask: pre-defined restraints or AMBER mask
        :type mask: string
        :param restr_force: force constant for the restraints in kcal/mol/AA
        :type restr_force: float
        :raises: SetupError
        """

        suffix = '%05i' % self.run_no

        if config[0] == '%':
            pname = config[1:]
            logger.write('Running minimisation with protocol %s' % pname)

            try:
                config = PROTOCOLS['MIN_%s' % pname]
            except KeyError:
                raise errors.SetupError('no such min protocol predefined: '
                                        '%s' % config)

        self._run_mdprog(suffix, config, mask, restr_force)
Exemple #26
0
    def _run_mdprog(self, prefix, namelist, mask, constp):
        """
        Run sander/pmemd from the Amber package.
        """

        prefix += '%05i'

        prefix = prefix % self.run_no
        self.sander_rst = prefix + mdebase.RST_EXT

        with open(prefix + os.extsep + 'in', 'w') as mdin:
            mdin.writelines(namelist)

        # NOTE: we assume trajectory will be written in NetCDF
        #       format (ioutfm = 1)
        flags = ('-i {0}.in -p {1} -c {2} '
                 '-O -o {0}.out -e {0}.en -x {0}.nc -inf {0}.info -r {3}')

        if mask:
            # Constant pressure with positional restraints shifts coordinates.
            if constp:
                flags += ' -ref %s' % self.sander_crd
            else:
                flags += ' -ref %s' % self.amber_crd

        err = utils.run_amber(self.mdpref + ' ' + self.mdprog,
                              flags.format(prefix, self.amber_top,
                                           self.sander_crd, self.sander_rst) )

        if err:
            logger.write('sander/pmemd failed with message %s' % err[1])
            raise errors.SetupError('error in sander run %s: %s' %
                                    (prefix, err[1]) )

        self.sander_crd = self.sander_rst
        self.run_no += 1
Exemple #27
0
    def create_absolute_Sire(self, prog='Sire'):
        """
        Create Sire input file for absolute transformation.
        """

        amber = Sire.IO.Amber()

        # FIXME: we only need vacuum.parm7/rst7
        try:
            molecules = amber.readCrdTop(self.amber_crd, self.amber_top)[0]
        except UserWarning as error:
            raise errors.SetupError('error opening %s/%s: %s' %
                                    (self.amber_crd, self.amber_top, error))

        nmol = molecules.molNums()
        nmol.sort()

        ligand = molecules.at(nmol[0]).molecule()

        # 1-step
        outstr = ['version 1', 'molecule %s' % (const.LIGAND_NAME)]

        for atom in ligand.atoms():
            # ambertype, connectivity, charge, bond, mass, element,
            # amberparameters, intrascale, LJ, angle, dihedral, improper,
            # coordinates
            outstr.extend(
                ('\tatom', '\t\tname           %s' % atom.name().value(),
                 '\t\tinitial_type   %s' % atom.property('ambertype'),
                 '\t\tfinal_type     du', '\t\tinitial_charge %-8.5f' %
                 atom.property('charge').value(), '\t\tfinal_charge   0.0',
                 ('\t\tinitial_LJ    %8.5f %8.5f' %
                  (atom.property('LJ').sigma().value(),
                   atom.property('LJ').epsilon().value())),
                 '\t\tfinal_LJ       0.0 0.0', '\tendatom'))

        outstr.append('endmolecule\n')

        with open(const.SIRE_ABS_PERT_FILE, 'w') as pfile:
            pfile.write('\n'.join(outstr))

        # 2-step
        outstr = ['version 1', 'molecule %s' % (const.LIGAND_NAME)]

        for atom in ligand.atoms():
            # ambertype, connectivity, charge, bond, mass, element,
            # amberparameters, intrascale, LJ, angle, dihedral, improper,
            # coordinates
            outstr.extend(
                ('\tatom', '\t\tname           %s' % atom.name().value(),
                 '\t\tinitial_charge %-8.5f' % atom.property('charge').value(),
                 '\t\tfinal_charge   0.0', '\tendatom'))

        outstr.append('endmolecule\n')

        with open(const.SIRE_ABS_PERT_EL_FILE, 'w') as pfile:
            pfile.write('\n'.join(outstr))

        outstr = ['version 1', 'molecule %s' % (const.LIGAND_NAME)]

        for atom in ligand.atoms():
            # ambertype, connectivity, charge, bond, mass, element,
            # amberparameters, intrascale, LJ, angle, dihedral, improper,
            # coordinates
            outstr.extend(
                ('\tatom', '\t\tname           %s' % atom.name().value(),
                 '\t\tinitial_type   %s' % atom.property('ambertype'),
                 '\t\tfinal_type     du', '\t\tinitial_charge 0.0',
                 '\t\tfinal_charge   0.0',
                 ('\t\tinitial_LJ    %8.5f %8.5f' %
                  (atom.property('LJ').sigma().value(),
                   atom.property('LJ').epsilon().value())),
                 '\t\tfinal_LJ       0.0 0.0', '\tendatom'))

        outstr.append('endmolecule\n')

        with open(const.SIRE_ABS_PERT_VDW_FILE, 'w') as pfile:
            pfile.write('\n'.join(outstr))
Exemple #28
0
    def create_top(self,
                   boxtype='',
                   boxlength='10.0',
                   align=False,
                   neutralize=0,
                   addcmd='',
                   addcmd2='',
                   remove_first=False,
                   conc=0.0,
                   dens=1.0,
                   write_dlf=False):
        """
        Generate an AMBER topology file via leap. Leap requires atom names in
        GAFF format to match against GAFF force field database.  Finally
        generated MOL2 file is in GAFF format.

        :param boxtype: rectangular, octahedron or set (set dimensions explicitly)
        :type boxtype: string
        :param boxlength: side length of the box
        :type boxlength: float
        :param align: align solute along the principal axes
        :type align: bool
        :param neutralize: neutralise the system
        :type neutralize: int
        :param addcmd: inject additional leap commands
        :type addcmd: string
        :param remove_first: remove first unit/residue
        :type remove_first: bool
        :param conc: ion concentration
        :type conc: float
        :param dens: expected target density
        :type dens: float
        :param write_dlf: write udff and pdb files for DL_FIELD?
        :type write_dlf: bool
        """

        # we allow the user to have their own leap input file which is used
        # instead of the autogenerated one
        if os.access(const.LEAP_IN, os.F_OK):
            self.amber_top = const.LEAP_IN + self.TOP_EXT
            self.amber_crd = const.LEAP_IN + self.RST_EXT
            self.amber_pdb = const.LEAP_IN + const.PDB_EXT

            utils.run_leap(self.amber_top,
                           self.amber_crd,
                           program='tleap',
                           script=const.LEAP_IN)

            return

        leapin = self._amber_top_common(boxtype,
                                        boxlength,
                                        neutralize,
                                        align=align,
                                        remove_first=remove_first,
                                        conc=conc,
                                        dens=dens)

        # Strangely, sleap does not create sander compatible top files with
        # TIP4P but tleap does.  Sleap also crashes when @<TRIPOS>SUBSTRUCTURE
        # is missing.  Sleap has apparently been abandonded.
        utils.run_leap(self.amber_top, self.amber_crd, 'tleap', leapin)

        # create DL_FIELD UDFF/PDB for vacuum case
        if not boxtype:
            amber = Sire.IO.Amber()

            try:
                mols = amber.readCrdTop(self.amber_crd, self.amber_top)[0]
            except UserWarning as error:
                raise errors.SetupError(
                    'error opening %s/%s: %s' %
                    (self.amber_crd, self.amber_top, error))

            # there should be only one molecule
            nmols = mols.nMolecules()
            if nmols > 1:
                return  # FIXME: don't write this when pert top
                raise errors.SetupError('BUG: only one molecule expected, '
                                        'found %i' % nmols)

            lig = mols.molNums()[0]

            if write_dlf:
                dlfield.dlf_write(mols.at(lig).molecule(), '_AG')
Exemple #29
0
    def param(self, gb_charges=False, sqm_strategy=None):
        """
        Compute symmetrized AM1/BCC charges and generate missing forcefield
        parameters. Runs antechamber, parmchk. Finally generated MOL2 file
        is in Sybyl format.  GAFF atom names are needed internally by AMBER.

        :param gb_charges: use a GB model for parameterisation
        :type gb_charges: bool
        :param sqm_strategy: a strategy pattern using preminimize() and setting
           the SCF convergence criterion for sqm
        :type sqm_strategy: list of 2-tuples
        :raises: SetupError
        """

        logger.write('Deriving AMBER/GAFF force field parameters')

        antechamber = utils.check_amber('antechamber')

        ac_cmd = [
            '-i %s' % self.mol_file,  # input file
            '-fi %s' % self.mol_fmt,  # input file format
            '-o %s' %
            const.LIGAND_AC_FILE,  # output file, crds from input file
            '-fo ac',  # output file format
            '-c bcc',  # charge method
            '-nc %s' % str(self.charge),  # net molecular charge
            '-m 1',  # FIXME: spin multiplicity (sqm only 1)
            '-df 2',  # 0 = mopac, 2 = sqm, (1 was divcon)
            '-at %s' % self.gaff,  # write GAFF types
            '-du y',  # fix duplicate atom names
            '-an y',  # adjust atom names
            '-j 4',  # atom/bond type prediction = full
            '-s 2',  # status information = verbose
            '-eq 2',  # equalise atom charges (path+geometry)
            '-pf y',  # clean up temporary files
            '-rn %s' % const.LIGAND_NAME  # overwrite ligand name
        ]

        tmp_file = const.LIGAND_TMP + os.extsep + self.mol_fmt
        shutil.copyfile(self.mol_file, tmp_file)

        # NOTE: The main problem is SCF convergence. If this happens MM
        #       minimisation is used to hope to obtain a better structure with a
        #       better wavefunction.  This obviously depends on a sensible
        #       assignment of force field parameters which may fail if the
        #       structure is "too" distorted and no bonding information, etc. are
        #       available a priori.
        #       A test on a few thousand ZINC structures showed that this feature
        #       is rarly useful.  It also complicates the code because the
        #       coordinates are changed and this mus be guarded against.
        if not sqm_strategy:
            if not gb_charges:
                sqm_strategy = ((0, '1.0d-10', 1, 500, 1000,
                                 ''), (50, '1.0d-10', 1, 500, 1000,
                                       ''), (0, '1.0d-9', 1, 500, 1000, ''),
                                (50, '1.0d-9', 1, 500, 1000,
                                 ''), (50, '1.0d-9', 0, 500, 1000, ''))
            else:
                # harder cases like ZINC03814826/28/31/32/38 may be parameterised
                # with a GB model and a more elaborate name list, vshift=0.1
                # may later be of use for some cases too
                sqm_strategy = (
                    #(0, '1.0d-10', 1, 1000, 0, ''),
                    #(50, '1.0d-10', 1, 1000, 0, ''),
                    (0, '1.0d-9', 1, 1000, 0, 'ndiis_attempts=100'),
                    (50, '1.0d-9', 1, 1000, 0,
                     'ndiis_attempts=200,ndiis_matrices=10'),
                    (50, '1.0d-9', 0, 1000, 0,
                     'ndiis_attempts=200,ndiis_matrices=20'))

        logger.write('Optimizing structure and creating AM1/BCC charges')
        premin_done = False

        for premin, scfconv, tight, itrmax, maxcyc, sqm_extra in sqm_strategy:
            converged = False

            if premin:
                self.preminimize(nsteps=premin)
                premin_done = True

            sqm_nlv = ("qm_theory='AM1',grms_tol=0.0002,tight_p_conv=%i,\n  "
                       "scfconv=%s,itrmax=%i,pseudo_diag=1,\n  "
                       "maxcyc=%i,\n%s" %
                       (tight, scfconv, itrmax, maxcyc, sqm_extra))
            ek = ['-ek "%s"' % sqm_nlv]  # sqm namelist variables

            # FIXME: Buffering messes with the stdout output order of
            #        antechamber (last line comes first).  Use stdbuf, pexpect
            #        or pty (probably Linux only)?
            err = utils.run_amber(antechamber, ' '.join(ac_cmd + ek))

            if err:
                if 'the assigned bond types may be wrong' in err[0]:
                    logger.write('Error: antechamber failed to assign '
                                 'atom/bond types properly\n')
                    raise errors.SetupError('antechamber cannot assign atom '
                                            'and/or bond types, check input '
                                            'structure, e.g. with acdoctor')

                sce = False

                with open(SQM_OUT, 'r') as sqm:
                    for line in sqm:
                        if 'Unable to achieve self consistency' in line:
                            logger.write('Warning: SCF has not converged '
                                         'with %i %s\n' % (premin, scfconv))
                            sce = True
                            break

                        if 'odd number of electrons' in line:
                            logger.write('Error: odd electron number\n')
                            raise errors.SetupError('wrong ligand charge, or '
                                                    'radical')

                if not sce:
                    raise errors.SetupError('unknown error see log file '
                                            'and %s file' %
                                            os.path.join(self.dst, SQM_OUT))
            else:
                converged = True
                break

        if not converged:
            if sce:
                logger.write('Error: SCF has not converged\n')
                raise errors.SetupError('SCF has not converged')
            else:
                logger.write('Error: failed to produce atom charges\n')
                raise errors.SetupError('failed to produce atom charges')

        # make sure we do not carry over the coordinates from a possible
        # preminimisation step above
        if premin_done:
            utils.run_amber(antechamber, '-i %s -fi ac '
                            '-a %s -fa %s -ao crd '
                            '-o %s -fo ac' %
                            (const.LIGAND_AC_FILE, tmp_file, self.mol_fmt,
                             const.LIGAND_AC_FILE))  # FIXME: dangerous?

        if not gb_charges:
            logger.write('SCF has converged with %i preminimisation steps and '
                         'scfconv = %s kcal/mol\n' % (premin, scfconv))

            ngconv = 0
            H_form = 'unknown'
            grad = 'unknown'

            with open(SQM_OUT, 'r') as sqm:
                for line in sqm:
                    if line.startswith('xmin'):
                        ngconv = int(line[4:10].strip())
                        H_form = line[10:33].strip()
                        grad = line[33:].strip()

            if ngconv >= maxcyc:
                logger.write(
                    'Warning: maximum number of geometry optimisation '
                    'steps reached (%i), gradient = %s '
                    '(grms_tol=0.0002), check %s file\n' %
                    (maxcyc, grad, SQM_OUT))
            else:
                logger.write('Geometry has converged after %i steps, heat of '
                             'formation: %s and gradient = %s\n' %
                             (ngconv, H_form, grad))
        else:
            if self.parmchk_version > 1:
                parmchk = utils.check_amber('parmchk%s' %
                                            str(self.parmchk_version))

                if self.gaff == 'gaff2':
                    params += '-s gaff2 '
            else:
                parmchk = utils.check_amber('parmchk')

            utils.run_amber(
                parmchk, '-i %s -f ac -o %s' %
                (const.LIGAND_AC_FILE, const.GB_FRCMOD_FILE))

            converged = _calc_gb_charge(const.LIGAND_AC_FILE,
                                        const.GB_FRCMOD_FILE, self.charge,
                                        scfconv, tight, sqm_extra, antechamber,
                                        self.gaff)

            if not converged:
                logger.write('Error: GB parameterisation failed\n')
                raise errors.SetupError('failed to produce atom charges')

        self._parmchk(const.LIGAND_AC_FILE, 'ac', self.frcmod)

        charges = []

        with open(const.LIGAND_AC_FILE, 'r') as acfile:
            for line in acfile:
                if line[:4] == 'ATOM':
                    charges.append(float(line[54:64]))

        if filter(lambda ch: math.fabs(ch) > const.MAX_CHARGE, charges):
            logger.write('Warning: some atom charges > %.2f' %
                         const.MAX_CHARGE)

        total_charge = sum(charges)
        dec_frac = total_charge - round(total_charge)

        if abs(dec_frac) > const.MAX_CHARGE_DIFF:
            logger.write('Warning: total molecule charge (%f) is far from '
                         'being an integer' % total_charge)

        corr = dec_frac / len(charges)

        for idx, charge in enumerate(charges):
            charges[idx] = charge - corr

        with open(const.CORR_CH_FILE, 'w') as chfile:
            for charge in charges:
                chfile.write('%.9f\n' % charge)

        utils.run_amber(
            antechamber, '-i %s -fi ac '
            '-o %s -fo ac '
            '-cf %s -c rc '
            '-s 2 -pf y -at %s' % (const.LIGAND_AC_FILE, const.CORR_AC_FILE,
                                   const.CORR_CH_FILE, self.gaff))

        # FIXME: Do we really need this? It only documents the charge orginally
        #        derived via antechamber.
        shutil.copyfile(const.LIGAND_AC_FILE,
                        const.LIGAND_AC_FILE + os.extsep + '0')

        shutil.move(const.CORR_AC_FILE, const.LIGAND_AC_FILE)

        self.charge = float('%.12f' % sum(charges))
        logger.write('Total molecule charge is %.2f\n' % self.charge)

        self.ref_file = self.mol_file
        self.ref_fmt = self.mol_fmt
Exemple #30
0
    def readParm(self, parmtop, inpcrd):
        """
        Extract topology and coordinate information from AMBER parmtop and
        inpcrd.  Store data internally.

        :param parmtop: parmtop file name
        :type parmtop: string
        :param inpcrd: inpcrd file name
        :type inprcd: string
        :raises: SetupError
        """

        amber = Sire.IO.Amber()

        try:
            # (Sire.Mol.Molecules,  Sire.Vol.PeriodicBox or Sire.Vol.Cartesian)
            mols, perbox = amber.readCrdTop(inpcrd, parmtop)
        except UserWarning as error:
            raise errors.SetupError('error opening %s/%s' % (parmtop, inpcrd))

        try:
            dims = perbox.dimensions()  # Sire.Maths.Vector

            x = dims.x()
            y = dims.y()
            z = dims.z()
        except:  # FIXME: which exception?
            x, y, z = 0.0, 0.0, 0.0

        self.box_dims = x, y, z

        mol_numbers = mols.molNums()
        mol_numbers.sort()

        self.tot_natoms = sum(
            mols.at(num).molecule().nAtoms() for num in mol_numbers)

        segcnt = -1
        atomno = 0
        offset = 0

        for num in mol_numbers:
            mol = mols.at(num).molecule()
            natoms = mol.nAtoms()
            segcnt += 1
            is_atom = False

            try:
                params = mol.property('amberparameters')
            except UserWarning:
                # FIXME: adjust segcnt?
                is_atom = True

            for atom in mol.atoms():
                atomno += 1
                residue = atom.residue()
                resno = residue.number().value()
                res = str(residue.name().value())
                atom_type = str(atom.name().value())
                amber_type = str(atom.property('ambertype'))
                resid = str(resno)  # FIXME
                charge = atom.property('charge').value()
                mass = atom.property('mass').value()
                lj = atom.property('LJ')
                coords = atom.property('coordinates')

                # FIXME: water name, large segments, segid overflow
                if residue.name().value() == 'WAT':
                    segid = 'WATER'
                    res = 'TIP3'
                else:
                    if not is_atom:
                        # FIXME: max is 475253
                        segid = '{:A>4s}'.format(_makeseg(segcnt))
                    else:
                        if charge != 0.0:
                            segid = 'ION'
                        else:
                            segid = 'ATOM'

                amber_type = _check_type(amber_type, self.atomtypes,
                                         atomno - 1)

                self.atoms.append((atomno, resno, segid, resid, res, atom_type,
                                   amber_type, charge, mass, coords))
                self.atom_params[amber_type] = (mass, lj)

            # IMPORTANT: do not forget to increase offset because all
            #            indices below are relative within each molecule!
            if is_atom:
                offset += 1
                continue

            for bond in params.getAllBonds():  # Sire.Mol.BondID
                at0 = bond.atom0()  # Sire.Mol.AtomIdx!
                at1 = bond.atom1()
                k, r = params.getParams(bond)

                idx0 = at0.value()
                idx1 = at1.value()

                t0 = str(mol.select(at0).property('ambertype'))
                t1 = str(mol.select(at1).property('ambertype'))

                name0 = _check_type(t0, self.atomtypes, idx0)
                name1 = _check_type(t1, self.atomtypes, idx1)

                self.bonds.append(
                    (at0.value() + 1 + offset, at1.value() + 1 + offset))
                self.bond_params[name0, name1] = (k, r)

            self.bonds.sort()

            for angle in params.getAllAngles():  # Sire.Mol.AngleID
                at0 = angle.atom0()  # Sire.Mol.AtomIdx!
                at1 = angle.atom1()
                at2 = angle.atom2()
                k, theta = params.getParams(angle)

                idx0 = at0.value()
                idx1 = at1.value()
                idx2 = at2.value()

                t0 = str(mol.select(at0).property('ambertype'))
                t1 = str(mol.select(at1).property('ambertype'))
                t2 = str(mol.select(at2).property('ambertype'))

                name0 = _check_type(t0, self.atomtypes, idx0)
                name1 = _check_type(t1, self.atomtypes, idx1)
                name2 = _check_type(t2, self.atomtypes, idx2)

                self.angles.append(
                    (at0.value() + 1 + offset, at1.value() + 1 + offset,
                     at2.value() + 1 + offset))
                self.angle_params[name0, name1,
                                  name2] = (k, theta * const.RAD2DEG)

            self.angles.sort()

            for dihedral in params.getAllDihedrals():  # Sire.Mol.DihedralID
                at0 = dihedral.atom0()  # Sire.Mol.AtomIdx!
                at1 = dihedral.atom1()
                at2 = dihedral.atom2()
                at3 = dihedral.atom3()

                idx0 = at0.value()
                idx1 = at1.value()
                idx2 = at2.value()
                idx3 = at3.value()

                t0 = str(mol.select(at0).property('ambertype'))
                t1 = str(mol.select(at1).property('ambertype'))
                t2 = str(mol.select(at2).property('ambertype'))
                t3 = str(mol.select(at3).property('ambertype'))

                name0 = _check_type(t0, self.atomtypes, idx0)
                name1 = _check_type(t1, self.atomtypes, idx1)
                name2 = _check_type(t2, self.atomtypes, idx2)
                name3 = _check_type(t3, self.atomtypes, idx3)

                p = params.getParams(dihedral)
                terms = []

                n = 3
                for i in range(0, len(p), n):  # k, np, phase
                    terms.append(p[i:i + n])

                self.dihedrals.append(
                    (at0.value() + 1 + offset, at1.value() + 1 + offset,
                     at2.value() + 1 + offset, at3.value() + 1 + offset))

                self.dihedral_params[name0, name1, name2, name3] = terms

            self.dihedrals.sort()

            for improper in params.getAllImpropers():
                at0 = improper.atom0()
                at1 = improper.atom1()
                at2 = improper.atom2()
                at3 = improper.atom3()

                idx0 = at0.value()
                idx1 = at1.value()
                idx2 = at2.value()
                idx3 = at3.value()

                t0 = str(mol.select(at0).property('ambertype'))
                t1 = str(mol.select(at1).property('ambertype'))
                t2 = str(mol.select(at2).property('ambertype'))
                t3 = str(mol.select(at3).property('ambertype'))

                name0 = _check_type(t0, self.atomtypes, idx0)
                name1 = _check_type(t1, self.atomtypes, idx1)
                name2 = _check_type(t2, self.atomtypes, idx2)
                name3 = _check_type(t3, self.atomtypes, idx3)

                term = params.getParams(improper)

                self.impropers.append(
                    (at0.value() + 1 + offset, at1.value() + 1 + offset,
                     at2.value() + 1 + offset, at3.value() + 1 + offset))

                self.improper_params[name0, name1, name2, name3] = term

            self.impropers.sort()

            # groups: base pointer charge type (1=neutral,2=charged),
            #         entire group fixed?
            for residue in mol.residues():
                charge = 0.0
                first = True

                for atom in residue.atoms():
                    if first:
                        gp_base = atom.index().value() + offset
                        first = False

                    charge += atom.property('charge').value()

                if charge > 0.01:  # FIXME
                    gp_type = 2
                else:
                    gp_type = 1

                self.groups.append((gp_base, gp_type, 0))

            offset += natoms