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
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)
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
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
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)
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)
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)
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
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
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
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)
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
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)
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
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
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
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
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
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
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
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
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
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)
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
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)
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
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))
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')
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
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