def run_amber(program, params): """ Simple wrapper to execute external AMBER programs through subprocess. :param program: AMBER program file name :type program: string :param params: paramters to the AMBER program :type params: string :raises: SetupError :returns: True on failure """ cmd = shlex.split(program) cmd.extend(shlex.split(params)) logger.write('Executing command:\n%s %s\n' % (program, params)) env = _setenv() proc = subp.Popen(cmd, stdout=subp.PIPE, stderr=subp.PIPE, env=env) out, err = proc.communicate() for stream in out, err: text = _cleanup_string(stream) if text: logger.write(' %s {\n %s\n }\n' % ('stdout' if stream is out else 'stderr', text)) if proc.returncode: return out, err return False
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 __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): self.separate = separate self.ff = ff self.gaff = gaff 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.files_created = [] self.frcmod = None self.dummies0 = not all([a.atom for a in atom_map.keys()]) self.dummies1 = not all([a.atom for a in atom_map.values()]) if self.separate and self.dummies0 and self.dummies1: self.FE_sub_type = 'dummy3' elif self.separate and (self.dummies0 or self.dummies1): self.FE_sub_type = 'dummy2' else: self.FE_sub_type = 'dummy' if self.separate: logger.write('Warning: linear transformation, not separating ' 'into vdw and electrostatic step\n')
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 prelude(opts): lff = len(opts[SECT_DEF]['forcefield']) deff = defaults[SECT_DEF]['forcefield'][0] if lff < 1 or lff > 5: raise dGprepError('malformed "forcefield" key, must have 1-5 strings ' 'required') if len(opts[SECT_DEF]['mdengine']) != 2: raise dGprepError('malformed "mdengine" key, 2 strings required') create_logger(opts[SECT_DEF]['logfile']) logger.write('\n%s\n\n%s\n' % (vstring, istring)) atexit.register(lambda : logger.finalize() ) ff_opts = list(opts[SECT_DEF]['forcefield']) lu = len(ff_opts) ff_opts[lu:] = deff[lu:] ff_opts.append(opts[SECT_DEF]['ff_addons']) ff_opts.append(opts[SECT_DEF]['mdengine'][0]) ff_opts.append(opts[SECT_DEF]['parmchk_version']) ff_opts.append(opts[SECT_DEF]['gaff']) return prep.ForceField(*ff_opts)
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 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 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 __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): 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.files_created = [] self.frcmod0 = None self.frcmod1 = None self.dummies0 = not all([a.atom for a in atom_map.keys()]) self.dummies1 = not all([a.atom for a in atom_map.values()]) want_softcore = FE_sub_type[:8] == 'softcore' self.FE_sub_type = '' # overwrite user choice, also for backward compatibility if self.separate and self.dummies0 and self.dummies1: if want_softcore: self.FE_sub_type = 'softcore3' else: self.FE_sub_type = 'dummy3' elif self.separate and (self.dummies0 or self.dummies1): if want_softcore: self.FE_sub_type = 'softcore2' else: self.FE_sub_type = 'dummy2' else: if want_softcore: self.FE_sub_type = 'softcore' else: self.FE_sub_type = 'dummy' if self.separate: logger.write('Warning: linear transformation, not separating ' 'into vdw and electrostatic step\n')
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 __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 _parmchk(self, infile, informat, outfile): """ Run parmcheck to generate missing parameters. :param infile: input file name :type infile: string :param informat: input file format :type informat: string :param outfile: output frcmod file :type outifle: string :param gaff: GAFF version (needs parmchk2) :type gaff: string """ params = '' 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') logger.write('Creating frcmod file') params += '-i %s -f %s -o %s -a N ' % (infile, informat, outfile) # FIXME: parmchk only reads one parmfile since AmberTools 16, # and ignores -p, not sure what the thinking her is... # possible solution: write temporary frcmod files and paste # together? # if self.ff_addons: # addon = self.ff_addons[0] # # FIXME: Can it get any uglier? Consistent file naming, ey... # if addon.startswith('GLYCAM_06'): # addon = addon[:10] # params += ' -p %s' % (os.path.join(os.environ['AMBERHOME'], 'dat', # 'leap', 'parm', addon) + # os.extsep + 'dat') utils.run_amber(parmchk, params)
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 run_exe(cmdline): """ Simple wrapper to execute the external programs through subprocess. :param cmdline: complete command line as given on a shell prompt :type cmdline: str """ logger.write('Executing command:\n%s\n' % cmdline) env = os.environ.copy() if 'COPY_LD_LIBRARY_PATH' in os.environ: env['LD_LIBRARY_PATH'] = os.environ['COPY_LD_LIBRARY_PATH'] else: env['LD_LIBRARY_PATH'] = '' proc = subp.Popen(shlex.split(cmdline), stdout=subp.PIPE, stderr=subp.PIPE, env=env) out, err = proc.communicate() return proc.returncode, out, err
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 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 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 prepare(self, to_format = 'mol2', addH = False, calc_charge = False, correct_for_pH = False, pH = 7.4): """ Read a molecule structure with Openbabel and get basic information from it. If requested modifiy the data and convert the file. :param to_format: format to convert to if not empty :type to_format: string :param addH: add hydrogens? (experimental) :type addH: bool :param calc_charge: force total formal charge calculation if True, leave it to Openbabel if False :type calc_charge: bool :param correct_for_pH: correct for pH, i.e. determine protonation state? (very experimental!) :type correct_for_pH: bool :param pH: pH to be considered when correct_for_pH is True (very experimental!) :type pH: float :raises: SetupError """ conv = ob.OBConversion() mol = ob.OBMol() ob_read_one(conv, self.mol_file, mol, self.mol_fmt, to_format) # FIXME: does this test for the right thing? if mol.GetDimension() != 3: raise errors.SetupError('input cooridnates (%s) must have 3 dimensions' % self.mol_file) orig_atoms = [] etab = ob.OBElementTable() acnt = 0 for atom in ob.OBMolAtomIter(mol): acnt += 1 res = atom.GetResidue() # when is this NULL? element = etab.GetSymbol(atom.GetAtomicNum() ) orig_atoms.append( (res.GetAtomID(atom).strip(), element) ) if to_format: res.SetAtomID(atom, '%s%d' % (element, acnt) ) if addH: logger.write('Adding hydrogens') mol.DeleteHydrogens() if correct_for_pH: # reimplementation from phmodel.cpp mol.SetAutomaticFormalCharge(correct_for_pH) transform = ob.OBChemTsfm() logger.write('Applying transforms') for reactant, product, pKa in const.TRANSFORM_SMARTS: success = transform.Init(reactant, product) if not success: raise ValueError('BUG in SMARTS transform Init %s >> %s', (reactant, product) ) if (transform.IsAcid() and pH > pKa) or \ (transform.IsBase() and pH < pKa): success = transform.Apply(mol) mol.AddHydrogens(False, False, 0.0) # valence filling if mol.GetTotalSpinMultiplicity() != 1: raise errors.SetupError('only multiplicity=1 supported (%s)' % self.mol_file) if to_format: logger.write('Converting/Writing %s (%s format) to %s format' % (self.mol_file, self.mol_fmt, to_format) ) # NOTE: this relies on a modified Openbabel MOL2 writer conv.AddOption('r', ob.OBConversion.OUTOPTIONS) # do not append resnum self.mol_file = const.CONV_MOL2_FILE % to_format self.mol_fmt = to_format try: conv.WriteFile(mol, self.mol_file) except IOError as why: raise errors.SetupError(why) else: logger.write('Leaving %s unmodified.' % self.mol_file) # some formats allow formal charge definition: # PDB (col 79-80), SDF(M CHG, atom block), MOL2 (UNITY_ATOM_ATTR?) if calc_charge: logger.write('Computing total formal charge\n') # set formal charge for some functional groups by hand because # OpenBabel doesn't support it in C++ for formats like mol2 formal_charge = 0 sm = ob.OBSmartsPattern() for smarts, fchg in const.CHARGE_SMARTS: sm.Init(smarts) if sm.Match(mol): m = list(sm.GetUMapList() ) formal_charge += fchg * len(m) mol.SetTotalCharge(formal_charge) self.charge = formal_charge else: logger.write('Total formal charge taken from coordinate file\n') self.charge = mol.GetTotalCharge() # trust Openbabel... self.atomtype = 'sybyl' # Openbabel will try to convert to Sybyl format conv.SetOutFormat('smi') conv.AddOption('n') conv.AddOption('c') smiles = conv.WriteString(mol).rstrip() conv.SetOutFormat('inchikey') errlev = ob.obErrorLog.GetOutputLevel() ob.obErrorLog.SetOutputLevel(0) inchi_key = conv.WriteString(mol).rstrip() ob.obErrorLog.SetOutputLevel(errlev) logger.write('''Ligand key data: Formula: %s SMILES: %s InChIKey: %s Net charge: %i Molecular weight: %f\n''' % (mol.GetFormula(), smiles, inchi_key, self.charge, mol.GetMolWt() ) )
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 mixer(top0, top1, filename = const.GROMACS_PERT_ITP, typename = const.GROMACS_PERT_ATP): """ Creates a perturbed Gromacs topology file from two top file mix-ins. :param parmtop: Gromacs topology object for state0 :type parmtop: GromacsTop :param inpcrd: Gromacs topology object for state1 :type inprcd: GromacsTop :param filename: itp file name for writing :type filename: string :raises: SetupError """ import copy # FIXME: is this check sufficient? if len(top0) != len(top1): raise errors.SetupError('topologies of different lengths: %s %s' % (top0.parmtop, top1.parmtop) ) with open(typename, 'w') as atype: #atype.write('\n[ atomtypes ]\n;name btype mass charge ' # 'ptype sigma epsilon\n') combined = copy.copy(top0.top.atomtypes) combined.update(top1.top.atomtypes) for typ in sorted(combined): atype.write('%-4s %-4s %8.3f 0.0000 A %12.6e %12.6e\n' % ( (typ, typ) + combined[typ][:]) ) with open(filename, 'w') as itp: for mt0, mt1 in zip(top0.top.moleculetype, top1.top.moleculetype): # FIXME: sensible molecule name itp.write('\n[ moleculetype ]\n%s 3\n' % mt0.molname) natom = 0 cgnr = 0 itp.write('\n[ atoms ]\n; nr type resno resnm ' 'atom cgnr charge mass' ' typeB chargeB massB\n') for atom0, atom1 in zip(mt0.atoms, mt1.atoms): natom +=1 cgnr += 1 itp.write('%7i %3s %6i %5s %4s %7i %11.6f %11.4f %3s ' '%11.6f %11.4f\n' % ( (natom, atom0[0], 1) + atom0[1:3] + (cgnr,) + atom0[3:] + (atom1[0],) + atom1[3:]) ) if mt0.bonds: itp.write('\n[ bonds ]\n; ai aj f r0' ' k r0B kB\n') for bond0, bond1 in zip(mt0.bonds, mt1.bonds): if bond0[2] == 0.0 or bond1[2] == 0.0: logger.write('Warning: zero bonds in %s, %s' % (' '.join(str(i) for i in bond0), ' '.join(str(i) for i in bond1))) itp.write('%7i %7i 1 %12.6e %12.6e %12.6e %12.6e\n' % (bond0 + bond1[2:]) ) if mt0.angles: itp.write('\n[ angles ]\n; ai aj ak f' ' a0 k' ' a0B kB\n') for angle0, angle1 in zip(mt0.angles, mt1.angles): if angle0[3] == 0.0 or angle1[3] == 0.0: logger.write('Warning: zero angles in %s, %s' % (' '.join(str(i) for i in angle0), ' '.join(str(i) for i in angle1))) itp.write('%7i %7i %7i 1 %12.6e %12.6e %12.6e %12.6e\n' % (angle0 + angle1[3:]) ) if mt0.pairs: itp.write('\n[ pairs ]\n; ai aj f\n') if len(mt0.pairs) != len(mt1.pairs): raise errors.SetupError('pairs of different size') for pair0, pair1 in zip(sorted(mt0.pairs), sorted(mt1.pairs) ): if pair0 != pair1: raise errors.SetupError('different pairs') itp.write('%7i %7i 1\n' % pair0) if mt0.propers: itp.write('\n[ dihedrals ] ;propers\n; i j ' 'k l f phase pk pn' ' phase pk pn\n') for proper0, proper1 in zip(mt0.propers, mt1.propers): for dih0, dih1 in zip(proper0[4], proper1[4]): # for some reason, leap creates null entries if dih0[0] == 0.0 and dih1[0] == 0.0: continue itp.write('%7i %7i %7i %7i 9 %11.2f %11.5f %i ' '%11.2f %11.5f %i\n' % (proper0[:4] + (dih0[2] * const.RAD2DEG, dih0[0] * const.CAL2J) + (dih0[1],) + (dih1[2] * const.RAD2DEG, dih1[0] * const.CAL2J) + (dih1[1],)) ) if mt0.impropers: itp.write('\n[ dihedrals ] ;impropers\n; i j ' ' k l f phase pk pn' ' phase pk pn\n') for improper0, improper1 in zip(mt0.impropers, mt1.impropers): itp.write('%7i %7i %7i %7i 4 %11.2f %11.5f %i ' '%11.2f %11.5f %i\n' % (improper0 + improper1[4:]) ) itp.write('\n\n')
def protonate_propka(self, pH = 7.0): """ Protonate a protein with PROPKA 3.1. :param pH: desired pH for protein protonation :type pH: float """ import os import sys import StringIO import propka.lib as plib import FESetup.propka.newmc as pmc PROT_RES = ('HIS', 'ASP', 'GLU') DEPROT_RES = ('LYS', 'CYS', 'ARG', 'TYR') # add PDB file name to options to avoid warning of missing file, also # set config file path to module path to find propka.cfg options, dummy = plib.loadOptions( ['--pH', pH, '-q', self.mol_file] ) options.parameters = os.path.join(os.path.dirname(pmc.__file__), options.parameters) with CaptureOutput() as output: mol = pmc.Molecular_container_new(self.mol_file, options) pKas = mol.calculate_pka() logger.write('%s%s' % (output[0], output[1]) ) protres = [] for resName, resSeq, chainID, pKa in pKas: # NOTE: currently we ignore termini 'N+' and 'C-' if pH < pKa: if resName in PROT_RES: protres.append( (resName, resSeq, chainID) ) else: if resName in DEPROT_RES: protres.append( (resName, resSeq, chainID) ) logger.write('pH = %.2f' % pH) msg_res = set() with open(const.PROTONATED_PDB_FILE, 'w') as newfile: with open(self.mol_file, 'r') as pdbfile: for line in pdbfile: if line[:6] in ('ATOM ', 'HETATM'): resName = line[17:21].strip() resSeq = int(line[22:26]) chainID = line[20:22].strip() if (resName, resSeq, chainID) in protres: msg_res.add( (resName, resSeq, chainID) ) newfile.write(line[:17] + '{:3s} '.format(self.PROT_MAP[resName]) + line[21:]) else: newfile.write(line) for resName, resSeq, chainID in msg_res: logger.write('Changing %s %i %s to %s' % (resName, resSeq, chainID, self.PROT_MAP[resName]) ) self.mol_file = const.PROTONATED_PDB_FILE
def make_ligand(name, ff, opts): """ Prepare ligands for simulation: charge parameters, vacuum top/crd, confomer search + alignment (both optional), optionally hydrated top/crd and minimisation/MD simulation. :param name: the name of the ligandx :type name: str :param ff: the ForceField class holding all relevant data for setup and also simulation :type ff: ForceField :param opts: the name of the ligandx :type opts: IniParser """ logger.write('*** Working on %s ***\n' % name) lig = opts[SECT_LIG] load_cmds = '' if opts[SECT_DEF]['user_params']: load_cmds = _param_glob(PARAM_CMDS) vac_model_filename = name + const.MODEL_EXT sol_model_filename = 'solv_' + name + const.MODEL_EXT from_scratch = True workdir = os.path.join(os.getcwd(), const.LIGAND_WORKDIR, name) if not opts[SECT_DEF]['remake']: model_path = \ _search_for_model([sol_model_filename, vac_model_filename], const.LIGAND_WORKDIR) # FIXME: check for KeyError if model_path: model = read_model(model_path) name = model['name'] # FIXME: only extract when const.LIGAND_WORKDIR not present? model.extract(direc = workdir) ligand = ff.Ligand(name) # invoke context manager to preset directory for absolute file # creation below with DirManager(workdir): logger.write('Found model %s, extracting data' % name) ligand.charge = float(model['charge.total']) ligand.gaff = model['forcefield'] ligand.amber_top = model['top.filename'] ligand.amber_crd = model['crd.filename'] ligand.orig_file = model['crd.original'] ligand.mol_file = ligand.orig_file ligand.mol_fmt ='mol2' # this file will not be created when skip_param = True try: ligand.frcmod = model['frcmod'] except KeyError: pass if 'box.dimensions' in model: ligand.box_dims = [float(b) for b in model['box.dimensions'].strip('[]')\ .split(',')] if lig['morph.absolute'] and \ opts[SECT_DEF]['AFE.type'] == 'Sire': logger.write('Creating input files for absolute ' 'transformations with Sire') ligand.create_absolute_Sire() if os.path.basename(model_path) == vac_model_filename: from_scratch = False else: return ligand, load_cmds print('Making ligand %s...' % name) if not lig['basedir']: raise dGprepError('[%s] "basedir" must be set' % SECT_LIG) if not lig['file.format']: fmt = os.path.splitext(lig['file.name'])[1][1:] else: fmt = lig['file.format'] if from_scratch: model = ModelConfig(name) ligand = ff.Ligand(name, lig['file.name'], fmt) # this file will not be created when skip_param = True if os.path.isfile(ligand.frcmod): model['frcmod'] = ligand.frcmod model.add_file(ligand.frcmod) if os.path.isabs(lig['basedir']): src = os.path.join(lig['basedir'], name) else: src = os.path.join(os.getcwd(), lig['basedir'], name) with DirManager(workdir): if from_scratch: ligand.copy_files((src,), None, opts[SECT_DEF]['overwrite']) if not os.access(lig['file.name'], os.F_OK): raise errors.SetupError('start file %s does not exist in %s' % (lig['file.name'], os.getcwd() ) ) if lig['skip_param']: if fmt != 'pdb' and fmt != 'mol2': raise dGprepError('When parameterisation is skipped, the input ' 'format must be PDB or MOL2') ligand.prepare('', lig['add_hydrogens'], lig['calc_charge'], lig['correct_for_pH'], lig['pH']) elif not os.access(os.path.join(workdir, const.GAFF_MOL2_FILE), os.F_OK): # IMPORTANT: do not allow OpenBabel to add Hs, it may mess up # everything ligand.prepare('mol2', lig['add_hydrogens'], lig['calc_charge'], lig['correct_for_pH'], lig['pH']) ligand.param(lig['gb_charges']) else: # FIXME: ugly ligand.prepare('', lig['add_hydrogens'], lig['calc_charge'], lig['correct_for_pH'], lig['pH']) ligand.mol_file = const.GAFF_MOL2_FILE ligand.mol_fmt = 'mol2' ligand.prepare_top() ligand.create_top(boxtype='', addcmd=load_cmds, write_dlf=lig['write_dlf']) # this file will not be created when skip_param = True if os.path.isfile(const.LIGAND_AC_FILE): model['charge.filename'] = const.LIGAND_AC_FILE model.add_file(const.LIGAND_AC_FILE) model['charge.total'] = ligand.charge model['charge.filetype'] = 'ac' model['charge.method'] = 'AM1-BCC' model['forcefield'] = ligand.gaff model['molecule.type'] = 'ligand' model.add_file(ligand.mol_file) model['crd.original'] = ligand.mol_file save_model(model, ligand, vac_model_filename, '..') if opts[SECT_DEF]['MC_prep']: ligand.flex() nconf = lig['conf_search.numconf'] if lig['morph.absolute'] and opts[SECT_DEF]['AFE.type'] == 'Sire': ligand.create_absolute_Sire() if nconf > 0: ligand.conf_search(numconf = nconf, geomsteps = lig['conf_search.geomsteps'], steep_steps = lig['conf_search.steep_steps'], steep_econv = lig['conf_search.steep_econv'], conj_steps = lig['conf_search.conj_steps'], conj_econv = lig['conf_search.conj_econv'], ffield = lig['conf_search.ffield']) ligand.align() # FIXME: also check for boxlength and neutralize if lig['box.type']: ligand.prepare_top() ligand.create_top(boxtype = lig['box.type'], boxlength = lig['box.length'], neutralize = lig['neutralize'], addcmd = load_cmds, remove_first = False) if lig['ions.conc'] > 0.0: ligand.create_top(boxtype = lig['box.type'], boxlength = lig['box.length'], neutralize = 2, addcmd = load_cmds, remove_first = False, conc = lig['ions.conc'], dens = lig['ions.dens']) restr_force = lig['min.restr_force'] nsteps = lig['min.nsteps'] ligand.setup_MDEngine(opts[SECT_DEF]['mdengine'][1], opts[SECT_DEF]['mdengine.prefix'], opts[SECT_DEF]['mdengine.postfix']) if nsteps > 0: do_min(ligand, lig) press_done = False nsteps = lig['md.heat.nsteps'] if nsteps > 0: restr_force = lig['md.heat.restr_force'] do_md(ligand, lig, 'heat') nsteps = lig['md.constT.nsteps'] if nsteps > 0: restr_force = lig['md.constT.restr_force'] do_md(ligand, lig, 'constT') nsteps = lig['md.press.nsteps'] if nsteps > 0: restr_force = lig['md.press.restr_force'] do_md(ligand, lig, 'press') press_done = True nrestr = lig['md.relax.nrestr'] if nrestr > 0: # FIXME: unify with AMBER mdengine if opts[SECT_DEF]['mdengine'][0] == 'namd': ligand.md('%RELRES', lig['md.relax.nsteps'], lig['md.relax.T'], lig['md.relax.p'], lig['md.relax.restraint'], restr_force, nrestr, wrap = True) else: sp = restr_force / (nrestr - 1) for k in range(nrestr - 2, -1, -1): if press_done: nmlist = '%PRESS' else: nmlist = '%CONSTT' ligand.md(nmlist, lig['md.relax.nsteps'], lig['md.relax.T'], lig['md.relax.p'], lig['md.relax.restraint'], sp * k, wrap = True) if opts[SECT_DEF]['mdengine'][0] != 'amber': if _minmd_done(lig): ligand.to_rst7() save_model(model, ligand, sol_model_filename, '..') return ligand, load_cmds
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 """ suffix = '%05i' % self.run_no cfg = config if config[0] == '%': pname = config[1:] logger.write('Running MD (%s) with protocol %s' % (suffix, pname)) try: config = PROTOCOLS['MD_%s' % pname] except KeyError: raise errors.SetupError('no such MD protocol predefined: ' '%s' % config) p /= const.ATM2BAR * 1000 # convert to kbar # DL_POLY does not have a built-in heating protocol if cfg == '%HEAT': ninc = int(NO_STEPS_PER_100DEG * T / 100.0) tinc = T / (ninc - 1) nst = nsteps / ninc for i in range(0, ninc): if i == 0: T_curr = START_T else: T_curr = i * tinc ctrl = config.format(T_curr, nst, nst / 5, nst / 10, nst / 5, p, dt) self._run_mdprog(suffix, ctrl, mask, restr_force) suffix = '%05i' % self.run_no else: ctrl = config.format(T, nsteps, nsteps / 5, nsteps / 10, nsteps / 5, p, dt) self._run_mdprog(suffix, ctrl, mask, restr_force)
def align(self, inc_hyd = False, symmetry = False, filt = False): """ Align two structures via OpenBabel. :param inc_hyd: include hydrogens :type inc_hyd: bool :param symmetry: consider symmetry of the molecule :type symmetry: bool :param filt: invoke primitive filter :type filt: bool :raises: SetupError """ if not self.ref_file: raise errors.SetupError('reference file not set yet') if self.ref_file == self.mol_file: logger.write('Identical files: will not perform alignment') return conv = ob.OBConversion() conv.SetInAndOutFormats(self.ref_fmt, self.mol_fmt) ref = ob.OBMol() tgt = ob.OBMol() # ignore warning messages about non-standard PDB errlev = ob.obErrorLog.GetOutputLevel() ob.obErrorLog.SetOutputLevel(0) conv.ReadFile(ref, self.ref_file) conv.ReadFile(tgt, self.mol_file) ob.obErrorLog.SetOutputLevel(errlev) if filt: # delete unwanted atoms in reference delat = [] for atom in ob.OBMolAtomIter(ref): resname = atom.GetResidue().GetName() # FIXME: replace with proper function if resname in const.IGNORE_RESIDUES: delat.append(atom) ref.BeginModify() for atom in delat: ref.DeleteAtom(atom, True) ref.EndModify() # copy wanted atoms from target to new OBMol cpy = ob.OBMol() for atom in ob.OBMolAtomIter(tgt): resname = atom.GetResidue().GetName() # FIXME: replace with proper function if resname not in const.IGNORE_RESIDUES: # NOTE: this copies only some info but incl. coordinates cpy.AddAtom(atom) molecs = ob.OBAlign(ref, cpy, inc_hyd, symmetry) else: molecs = ob.OBAlign(ref, tgt, inc_hyd, symmetry) logger.write('Aligning %s with %s as reference' % (self.mol_file, self.ref_file) ) if not molecs.Align(): logger.write('Alignment failed') return logger.write('RMSD is %.2f' % molecs.GetRMSD() ) if filt: rotate = molecs.GetRotMatrix() molecs.UpdateCoords(ref) first = True for atom in ob.OBMolAtomIter(tgt): tmpvec = ob.vector3(atom.GetVector()) tmpvec *= rotate if first: # we obviously can't do this directly: OB's vector3 does not # support the '-' but only the '-=' operator. But the # latter leads to a memory corruption bug... # shift = ref.GetAtom(1).GetVector() # shift -= cpy.GetAtom(1).GetVector() # we assume atoms 1 are equivalent in both molecules... v1 = ref.GetAtom(1).GetVector() v2 = cpy.GetAtom(1).GetVector() x = v1.GetX() - v2.GetX() y = v1.GetY() - v2.GetY() z = v1.GetZ() - v2.GetZ() shift = ob.vector3(x, y, z) first = False tmpvec += shift atom.SetVector(tmpvec) else: if not molecs.UpdateCoords(tgt): logger.write('Coordinate update failed') return try: conv.WriteFile(tgt, self.mol_file) except IOError as why: raise errors.SetupError(why) self.mol_atomtype = 'sybyl'
options.parse(args.infile, 'globals') # backward compatibility if options[SECT_DEF]['FE_type']: options[SECT_DEF]['AFE.type'] = options[SECT_DEF]['FE_type'] if options[SECT_DEF]['gaff'] == 'gaff1': options[SECT_DEF]['gaff'] = 'gaff' for section in ALL_SECTIONS: check_dict(section, options) ff = prelude(options) logger.write('Command line: %s\n\nOptions:\n--------' % ' '.join(sys.argv)) logger.write('\n'.join(options.format())) logger.write('--------\n\nForce field and MD engine:\n%s\n' % ff) # FIXME: We keep all molecule objects in memory. For 2000 morph pairs # this may mean more than 1 GB on a 64 bit machine. ### proteins proteins = {} prot_failed = [] for prot_name in options[SECT_PROT]['molecules']: try: protein, cmds = make_protein(prot_name, ff, options)
def make_pert_file(old_morph, new_morph, stepname, qprop0, qprop1, LJprop0, LJprop1, atprop0, atprop1, lig_initial, lig_final, atoms_final, atom_map, reverse_atom_map, zz_atoms, qonly, turnoffdummyangles=False, shrinkdummybonds=False, zero_dih_dummies=False): """ Create a perturbation file for Sire. :param old_morph: the original morph molecule :type old_morph: Sire.Mol.Molecule :param new_morph: a new morph molecule for manipulations :type new_morph: Sire.Mol.Molecule :param stepname: name of the current morph step :type stepname: str :param qprop0: name of inital charge property :type qprop0: str :param qprop1: name of final charge property :type qprop1: str :param LJprop0: name of inital LJ property :type LJprop0: str :param LJprop1: name of final LJ property :type LJprop1: str :param atprop0: name of inital atom type property :type atprop0: str :param atprop1: name of final atom type property :type atprop1: str :param lig_initial: the initial state molecule :type lig_initial: Sire.Mol.Molecule :param lig_final: the final state molecule :type lig_final: Sire.Mol.Molecule :param atoms_final: set of final atoms :type atoms_final: Sire.Mol.Selector_Atom :param atom_map: the forward atom map :type atom_map: dict of _AtomInfo to _AtomInfo :param reverse_atom_map: the reverse atom map :type reverse_atom_map: dict of _AtomInfo to _AtomInfo :param zz_atoms: rename atoms in list to 'zz' to circumvent leap valency check :type zz_atoms: list of Sire.Mol.AtomName :param charge_only: write only charges or also vdW+bonded terms :type charge_only: bool :param turnoffdummyangles: turn off dummy angles :type turnoffdummyangles: bool :param shrinkdummybonds: shrink dummy bonds :type shrinkdummybonds: bool :param zero_dih_dummies: use zero dihedrals and impropers when all atoms are dummies :type zero_dih_dummies: bool :raises: SetupError """ # FIXME: change name according to step protocol pert_fname = const.MORPH_NAME + os.extsep + stepname + os.extsep + 'pert' logger.write('Writing perturbation file %s...\n' % pert_fname) pertfile = open(pert_fname, 'w') outstr = 'version 1\n' outstr += 'molecule %s\n' % (const.LIGAND_NAME) pertfile.write(outstr) # Write atom perts morph_natoms = old_morph.nAtoms() for atom in old_morph.atoms(): outstr = '' #if ((atom.property(atprop0) != # atom.property(atprop1)) # or (atom.property(LJprop0) != # atom.property(LJprop1))): outstr += '\t\tinitial_type %s\n' % atom.property(atprop0) outstr += '\t\tfinal_type %s\n' % atom.property(atprop1) outstr += '\t\tinitial_LJ %8.5f %8.5f\n' % (atom.property( LJprop0).sigma().value(), atom.property(LJprop0).epsilon().value()) outstr += '\t\tfinal_LJ %8.5f %8.5f\n' % (atom.property( LJprop1).sigma().value(), atom.property(LJprop1).epsilon().value()) #if (atom.property(qprop0) != atom.property(qprop1)): if qprop0 == 'zero_all' and qprop1 == 'zero_all': outstr += '\t\tinitial_charge 0.0\n' outstr += '\t\tfinal_charge 0.0\n' elif qprop1 == 'zero_all': outstr += '\t\tinitial_charge %8.5f\n' % \ atom.property(qprop0).value() outstr += '\t\tfinal_charge 0.0\n' elif qprop0 == 'zero_all': outstr += '\t\tinitial_charge 0.0\n' outstr += '\t\tfinal_charge %8.5f\n' % \ atom.property(qprop1).value() elif qprop1 == 'zero_dummy': pass elif qprop0 == 'zero_dummy': pass else: outstr += '\t\tinitial_charge %8.5f\n' % \ atom.property(qprop0).value() outstr += '\t\tfinal_charge %8.5f\n' % \ atom.property(qprop1).value() if outstr: atom_name = '\t\tname %s\n' % atom.name().value() pertfile.write('\tatom\n' + atom_name + outstr + '\tendatom\n') if qonly: pertfile.write('endmolecule\n') pertfile.close() return # Figure out which bond, angles, dihedrals have their potential variable params_initial = lig_initial.property('amberparameters') bonds_initial = params_initial.getAllBonds() angles_initial = params_initial.getAllAngles() dihedrals_initial = params_initial.getAllDihedrals() impropers_initial = params_initial.getAllImpropers() params_final = lig_final.property('amberparameters') bonds_final = params_final.getAllBonds() angles_final = params_final.getAllAngles() dihedrals_final = params_final.getAllDihedrals() impropers_final = params_final.getAllImpropers() params_morph = new_morph.property('amberparameters') bonds_morph = params_morph.getAllBonds() angles_morph = params_morph.getAllAngles() dihedrals_morph = params_morph.getAllDihedrals() impropers_morph = params_morph.getAllImpropers() # For each pair of atoms making a bond in the morph we find # the equivalent pair in the initial topology. If there # are no matches this should be because one of the two atoms is a dummy # atom. If not, this sounds like a bug and the code aborts. for bond in bonds_morph: mpot = params_morph.getParams(bond) idummy = False fdummy = False ipot = None at0 = new_morph.select(bond.atom0()) at1 = new_morph.select(bond.atom1()) at0i = at0.index() at1i = at1.index() for ibond in bonds_initial: iat0 = lig_initial.select(ibond.atom0()).index() iat1 = lig_initial.select(ibond.atom1()).index() if ((at0i == iat0 and at1i == iat1) or (at0i == iat1 and at1i == iat0)): ipot = params_initial.getParams(ibond) break if not ipot: if (at0.name().value().startsWith('DU') or at1.name().value().startsWith('DU')): ipot = 'todefine' idummy = True else: raise errors.SetupError('Could not locate bond parameters for ' 'the final state. This is most likely ' 'because the atom mapping would open ' 'up a ring in the intial state. ') fpot = None map_at0 = util.search_atom(at0i, atom_map) map_at1 = util.search_atom(at1i, atom_map) for fbond in bonds_final: fat0 = lig_final.select(fbond.atom0()) fat1 = lig_final.select(fbond.atom1()) if ((map_at0 == fat0 and map_at1 == fat1) or (map_at0 == fat1 and map_at1 == fat0)): fpot = params_final.getParams(fbond) break if fpot is None: if (not map_at0 or not map_at1): fpot = 'todefine' fdummy = True else: raise errors.SetupError('Could not locate bond parameters for ' 'the final state. This is most likely ' 'because the atom mapping would open ' 'up a ring in the intial state.') samepotential = _isSameBondAnglePotential(ipot, fpot) if (not samepotential and ipot != 'todefine' and not _isSameBondAnglePotential(ipot, mpot)): if (at0.name().value() in zz_atoms or at1.name().value() in zz_atoms): samepotential = False else: raise errors.SetupError('The initial and morph potentials ' 'are different, but the potential ' 'does not involve a dummy atom. ' 'This case is not handled by the ' 'code.') # # If either atom in the initial/final states is a dummy, then the # parameters are kept constant throughout the perturbation # if idummy and fdummy: raise errors.SetupError('BUG: Both the initial and final states ' 'involve dummy atoms. (bond)') elif idummy: ipot = fpot elif fdummy: fpot = ipot if (idummy or fdummy) or (not samepotential): i_force = ipot[0] i_eq = ipot[1] f_force = fpot[0] f_eq = fpot[1] if shrinkdummybonds and idummy: i_eq = 0.2 # Angstrom if shrinkdummybonds and fdummy: f_eq = 0.2 # Angstrom outstr = '\tbond\n' outstr += '\t\tatom0 %s\n' % at0.name().value() outstr += '\t\tatom1 %s\n' % at1.name().value() outstr += '\t\tinitial_force %s\n' % i_force outstr += '\t\tinitial_equil %s\n' % i_eq outstr += '\t\tfinal_force %s\n' % f_force outstr += '\t\tfinal_equil %s\n' % f_eq outstr += '\tendbond\n' pertfile.write(outstr) # Now angles... for angle in angles_morph: mpot = params_morph.getParams(angle) idummy = False fdummy = False ipot = None at0 = new_morph.select(angle.atom0()) at1 = new_morph.select(angle.atom1()) at2 = new_morph.select(angle.atom2()) at0i = at0.index() at1i = at1.index() at2i = at2.index() for iangle in angles_initial: iat0 = lig_initial.select(iangle.atom0()).index() iat1 = lig_initial.select(iangle.atom1()).index() iat2 = lig_initial.select(iangle.atom2()).index() if ((at0i == iat0 and at1i == iat1 and at2i == iat2) or (at0i == iat2 and at1i == iat1 and at2i == iat0)): ipot = params_initial.getParams(iangle) break if ipot is None: if (at0.name().value().startsWith('DU') or at1.name().value().startsWith('DU') or at2.name().value().startsWith('DU')): ipot = 'todefine' idummy = True else: raise errors.SetupError( 'can not determine ipot for angle %s ' % angle) fpot = None map_at0 = util.search_atom(at0i, atom_map) map_at1 = util.search_atom(at1i, atom_map) map_at2 = util.search_atom(at2i, atom_map) for fangle in angles_final: fat0 = lig_final.select(fangle.atom0()) fat1 = lig_final.select(fangle.atom1()) fat2 = lig_final.select(fangle.atom2()) if ((map_at0 == fat0 and map_at1 == fat1 and map_at2 == fat2) or (map_at0 == fat2 and map_at1 == fat1 and map_at2 == fat0)): fpot = params_final.getParams(fangle) break if fpot is None: if (not map_at0 or not map_at1 or not map_at2): fpot = 'todefine' fdummy = True else: raise errors.SetupError( 'can not determine fpot for angle %s ' % angle) samepotential = _isSameBondAnglePotential(ipot, fpot) if (not samepotential and ipot != 'todefine' and (not _isSameBondAnglePotential(ipot, mpot))): if (at0.name().value() not in zz_atoms or at1.name().value() not in zz_atoms or at2.name().value() not in zz_atoms): samepotential = False else: raise errors.SetupError('BUG: The initial and morph angles ' 'are different, but the potential does' 'involve a dummy atom.') if idummy and fdummy: # This happens when we create 1-3 interactions involving two groups # of dummy atoms with some dummy in the initial state and some dummy # in the final state. The best option is to use null parameters so # as not to artificially restraint the morph ipot = (0, 0) fpot = ipot elif idummy: ipot = fpot elif fdummy: fpot = ipot if (idummy or fdummy) or (not samepotential): i_force = ipot[0] i_eq = ipot[1] f_force = fpot[0] f_eq = fpot[1] if turnoffdummyangles and idummy: if (not at0.name().value().startsWith('DU') or not at2.name().value().startsWith('DU')): i_force = 0.0 if turnoffdummyangles and fdummy: if (not at0.name().value().startsWith('DU') or not at2.name().value().startsWith('DU')): i_force = 0.0 outstr = '\tangle\n' outstr += '\t\tatom0 %s\n' % at0.name().value() outstr += '\t\tatom1 %s\n' % at1.name().value() outstr += '\t\tatom2 %s\n' % at2.name().value() outstr += '\t\tinitial_force %s\n' % i_force outstr += '\t\tinitial_equil %s\n' % i_eq outstr += '\t\tfinal_force %s\n' % f_force outstr += '\t\tfinal_equil %s\n' % f_eq outstr += '\tendangle\n' pertfile.write(outstr) # Now dihedrals... # # JM 03/13 # Problem: there could be some dihedrals/impropers that are not contained # in dihedrals_morph or dihedrals_initial # BUT do exist in dihedrals_final. These cases can be detected by looking # for dihedrals in the final topology that haven't been matched to # dihedrals in the morph # unmapped_fdihedrals = dihedrals_final for dihedral in dihedrals_morph: mpot = params_morph.getParams(dihedral) idummy = False fdummy = False allidummy = False allfdummy = False ipot = None at0 = new_morph.select(dihedral.atom0()) at1 = new_morph.select(dihedral.atom1()) at2 = new_morph.select(dihedral.atom2()) at3 = new_morph.select(dihedral.atom3()) at0i = at0.index() at1i = at1.index() at2i = at2.index() at3i = at3.index() for idihedral in dihedrals_initial: iat0 = lig_initial.select(idihedral.atom0()).index() iat1 = lig_initial.select(idihedral.atom1()).index() iat2 = lig_initial.select(idihedral.atom2()).index() iat3 = lig_initial.select(idihedral.atom3()).index() if ((at0i == iat0 and at1i == iat1 and at2i == iat2 and at3i == iat3) or (at0i == iat3 and at1i == iat2 and at2i == iat1 and at3i == iat0)): ipot = params_initial.getParams(idihedral) break if not ipot: is_dummy = [ at.name().value().startsWith('DU') for at in (at0, at1, at2, at3) ] if any(is_dummy): ipot = 'todefine' idummy = True else: raise errors.SetupError( 'Could not locate torsion parameters for ' 'the final state. This is most likely ' 'because the atom mapping would open ' 'up a ring in the intiial state.') allidummy = all(is_dummy) fpot = None map_at0 = util.search_atom(at0i, atom_map) map_at1 = util.search_atom(at1i, atom_map) map_at2 = util.search_atom(at2i, atom_map) map_at3 = util.search_atom(at3i, atom_map) for fdihedral in dihedrals_final: fat0 = lig_final.select(fdihedral.atom0()) fat1 = lig_final.select(fdihedral.atom1()) fat2 = lig_final.select(fdihedral.atom2()) fat3 = lig_final.select(fdihedral.atom3()) if ((map_at0 == fat0 and map_at1 == fat1 and map_at2 == fat2 and map_at3 == fat3) or (map_at0 == fat3 and map_at1 == fat2 and map_at2 == fat1 and map_at3 == fat0)): fpot = params_final.getParams(fdihedral) unmapped_fdihedrals.remove(fdihedral) break if not fpot: if not map_at0 or not map_at1 or not map_at2 or not map_at3: fpot = 'todefine' fdummy = True else: # This could be because the dihedral has a 0 force constant and # was not loaded from the top file. Flaw in the amber top parser? raise errors.SetupError( 'Cannot determine fpot for dihedral %s' % dihedral) # Check if all atoms are dummies in final torsion allfdummy = (not map_at0 and not map_at1 and not map_at2 and not map_at3) samepotential = _isSameDihedralPotential(ipot, fpot) if (not samepotential and ipot != 'todefine' and (not _isSameDihedralPotential(ipot, mpot))): if (at0.name().value() not in zz_atoms or at1.name().value() not in zz_atoms or at2.name().value() not in zz_atoms or at3.name().value() not in zz_atoms): samepotential = False else: raise errors.SetupError( 'BUG: The initial and morph dihedrals ' 'are different, but the potential does ' 'not involve a dummy atom.') # # Unlike bonds/angles, dihedrals with dummies have no energy # if idummy and fdummy: # JM 03/13 This can happen in some morphs. Then use a null potential # throughout. ipot = [0.0, 0.0, 0.0] fpot = [0.0, 0.0, 0.0] elif idummy: if allidummy and not zero_dih_dummies: ipot = fpot else: ipot = [0.0, 0.0, 0.0] elif fdummy: if allfdummy and not zero_dih_dummies: fpot = ipot else: fpot = [0.0, 0.0, 0.0] # leap creates for some unkown reason zero torsions if len(ipot) == 3 and len(fpot) == 3 and \ ipot[0] == 0.0 and fpot[0] == 0.0: continue if (idummy or fdummy) or (not samepotential): outstr = '\tdihedral\n' outstr += '\t\tatom0 %s\n' % at0.name().value() outstr += '\t\tatom1 %s\n' % at1.name().value() outstr += '\t\tatom2 %s\n' % at2.name().value() outstr += '\t\tatom3 %s\n' % at3.name().value() outstr += '\t\tinitial_form %s\n' % ' '.join( [str(i) for i in ipot]) outstr += '\t\tfinal_form %s\n' % ' '.join([str(f) for f in fpot]) outstr += '\tenddihedral\n' pertfile.write(outstr) if unmapped_fdihedrals: logger.write("\ndihedrals in the final topology that haven't been " "mapped to the initial topology:") logger.write(unmapped_fdihedrals) for fdihedral in unmapped_fdihedrals: fat0 = lig_final.select(fdihedral.atom0()).index() fat1 = lig_final.select(fdihedral.atom1()).index() fat2 = lig_final.select(fdihedral.atom2()).index() fat3 = lig_final.select(fdihedral.atom3()).index() fpot = params_final.getParams(fdihedral) ipot = [0.0, 0.0, 0.0] reversemap_at0 = util.search_atom(fat0, reverse_atom_map) reversemap_at1 = util.search_atom(fat1, reverse_atom_map) reversemap_at2 = util.search_atom(fat2, reverse_atom_map) reversemap_at3 = util.search_atom(fat3, reverse_atom_map) outstr = '\tdihedral\n' outstr += '\t\tatom0 %s\n' % reversemap_at0.value() outstr += '\t\tatom1 %s\n' % reversemap_at1.value() outstr += '\t\tatom2 %s\n' % reversemap_at2.value() outstr += '\t\tatom3 %s\n' % reversemap_at3.value() outstr += '\t\tinitial_form %s\n' % ' '.join([str(i) for i in ipot]) outstr += '\t\tfinal_form %s\n' % ' '.join([str(f) for f in fpot]) outstr += '\tenddihedral\n' pertfile.write(outstr) # # Now impropers... # unmapped_fimpropers = impropers_final unmapped_iimpropers = impropers_initial logger.write('\nimpropers:') for improper in impropers_morph: logger.write(improper) mpot = params_morph.getParams(improper) idummy = False fdummy = False allidummy = False allfdummy = False ipot = None at0 = new_morph.select(improper.atom0()) at1 = new_morph.select(improper.atom1()) at2 = new_morph.select(improper.atom2()) at3 = new_morph.select(improper.atom3()) at0i = at0.index() at1i = at1.index() at2i = at2.index() at3i = at3.index() for iimproper in impropers_initial: iat0 = lig_initial.select(iimproper.atom0()).index() iat1 = lig_initial.select(iimproper.atom1()).index() iat2 = lig_initial.select(iimproper.atom2()).index() iat3 = lig_initial.select(iimproper.atom3()).index() # Need different matching rules if ((at0i == iat0 or at0i == iat1 or at0i == iat2 or at0i == iat3) and (at1i == iat0 or at1i == iat1 or at1i == iat2 or at1i == iat3) and (at2i == iat0 or at2i == iat1 or at2i == iat2 or at2i == iat3) and (at3i == iat0 or at3i == iat1 or at3i == iat2 or at3i == iat3)): ipot = params_initial.getParams(iimproper) unmapped_iimpropers.remove(iimproper) break if not ipot: is_dummy = [ at.name().value().startsWith('DU') for at in (at0, at1, at2, at3) ] if any(is_dummy): ipot = 'todefine' idummy = True else: raise errors.SetupError( 'Could not locate improper parameters for ' 'the final state. This is most likely ' 'because the atom mapping would open ' 'up a ring in the intiial state.') allidummy = all(is_dummy) fpot = None map_at0 = util.search_atom(at0i, atom_map) map_at1 = util.search_atom(at1i, atom_map) map_at2 = util.search_atom(at2i, atom_map) map_at3 = util.search_atom(at3i, atom_map) for fimproper in impropers_final: fat0 = lig_final.select(fimproper.atom0()) fat1 = lig_final.select(fimproper.atom1()) fat2 = lig_final.select(fimproper.atom2()) fat3 = lig_final.select(fimproper.atom3()) # The two matching rules below are to catch impropers that have been # defined by walking around the ring in a reverse order as in the # initial ligand # There are many equivalent ways of defining an improper # # 2 # | # 1 # / \ # 0 3 # # 0123 * # 0132 * # 2103 * # 2130 * # 3120 * # 3102 * # 3210 * # 2310 * # 3012 * # 0312 * # 0213 * # 2013 * if ((map_at0 == fat0 or map_at0 == fat1 or map_at0 == fat2 or map_at0 == fat3) and (map_at1 == fat0 or map_at1 == fat1 or map_at1 == fat2 or map_at1 == fat3) and (map_at2 == fat0 or map_at2 == fat1 or map_at2 == fat2 or map_at2 == fat3) and (map_at3 == fat0 or map_at3 == fat1 or map_at3 == fat2 or map_at3 == fat3)): fpot = params_final.getParams(fimproper) unmapped_fimpropers.remove(fimproper) break if not fpot: if not map_at0 or not map_at1 or not map_at2 or not map_at3: fpot = 'todefine' fdummy = True else: # JM 02/13 Found a case where one final improper does not exist, # but it does in the initial state... # R-NH2 --> R-NO2 this is at least for the gaff force field. It # seems then that a valid option is to guess a null improper in # those cases? fpot = [0.0, 0.0, 0.0] # Check if all atoms are dummies in final torsion allfdummy = (not map_at0 and not map_at1 and not map_at2 and not map_at3) samepotential = _isSameDihedralPotential(ipot, fpot) # FIXME: there is a logical problem here! if (not samepotential and ipot != 'todefine' and (not _isSameDihedralPotential(ipot, mpot))): if (at0.name().value() not in zz_atoms or at1.name().value() not in zz_atoms or at2.name().value() not in zz_atoms or at3.name().value() not in zz_atoms): samepotential = False else: raise errors.SetupError( 'BUG: The initial and morph impropers ' 'are different, but the potential does ' 'not involve a dummy atom.') if idummy and fdummy: ipot = [0.0, 0.0, 0.0] fpot = [0.0, 0.0, 0.0] elif idummy: if allidummy and not zero_dih_dummies: ipot = fpot else: ipot = [0.0, 0.0, 0.0] elif fdummy: if allfdummy and not zero_dih_dummies: fpot = ipot else: fpot = [0.0, 0.0, 0.0] ipotstr = '' for val in ipot: ipotstr += '%s ' % val fpotstr = '' for val in fpot: fpotstr += '%s ' % val if ((idummy or fdummy) or (not samepotential)): outstr = '\timproper\n' outstr += '\t\tatom0 %s\n' % at0.name().value() outstr += '\t\tatom1 %s\n' % at1.name().value() outstr += '\t\tatom2 %s\n' % at2.name().value() outstr += '\t\tatom3 %s\n' % at3.name().value() outstr += '\t\tinitial_form %s\n' % ipotstr outstr += '\t\tfinal_form %s\n' % fpotstr outstr += '\tendimproper\n' pertfile.write(outstr) logger.write("Impropers in the final topology that haven't been mapped to " "the initial topology") logger.write(unmapped_fimpropers) for fimproper in unmapped_fimpropers: fat0 = lig_final.select(fimproper.atom0()).index() fat1 = lig_final.select(fimproper.atom1()).index() fat2 = lig_final.select(fimproper.atom2()).index() fat3 = lig_final.select(fimproper.atom3()).index() at0_info = util.search_atominfo(fat0, reverse_atom_map) at1_info = util.search_atominfo(fat1, reverse_atom_map) at2_info = util.search_atominfo(fat2, reverse_atom_map) at3_info = util.search_atominfo(fat3, reverse_atom_map) fpot = params_final.getParams(fimproper) if (not at0_info.atom and not at1_info.atom and not at2_info.atom and not at3_info.atom): ipot = fpot else: ipot = [0.0, 0.0, 0.0] outstr = '\timproper\n' outstr += '\t\tatom0 %s\n' % at0_info.name.value() outstr += '\t\tatom1 %s\n' % at1_info.name.value() outstr += '\t\tatom2 %s\n' % at2_info.name.value() outstr += '\t\tatom3 %s\n' % at3_info.name.value() outstr += '\t\tinitial_form %s\n' % ' '.join(str(i) for i in ipot) outstr += '\t\tfinal_form %s\n' % ' '.join(str(f) for f in fpot) outstr += '\tendimproper\n' pertfile.write(outstr) # # This happens for for instance acetamide --> acetone. # This breaks the assumption of the code that the morph contains all the dofs # of the initial state. It could be because leap applies different rules to # decide whether to include impropers in a molecule, and the morph gets a # different treatment than for the initial state (owing to dummy atoms atoms) # # This suggests that a more robust version of the code should scan every # initial dof (bond/angle/dihedral/improper) match with final dof, and then # build the pert file, using the morph atom names (as opposed to assuming # that morph==initial for dofs and then match final dofs to morph). # # For now, the code below should fix the missing improper problem # logger.write("Impropers in the initial topology that haven't been mapped " "to the morph") logger.write(unmapped_iimpropers) for iimproper in unmapped_iimpropers: iat0 = lig_initial.select(iimproper.atom0()) iat1 = lig_initial.select(iimproper.atom1()) iat2 = lig_initial.select(iimproper.atom2()) iat3 = lig_initial.select(iimproper.atom3()) logger.write('%s %s %s %s' % (iat0, iat1, iat2, iat3)) ipot = params_initial.getParams(iimproper) fpot = [0.0, 0.0, 0.0] outstr = '\timproper\n' outstr += '\t\tatom0 %s\n' % iat0.name().value() outstr += '\t\tatom1 %s\n' % iat1.name().value() outstr += '\t\tatom2 %s\n' % iat2.name().value() outstr += '\t\tatom3 %s\n' % iat3.name().value() outstr += '\t\tinitial_form %s\n' % ' '.join(str(i) for i in ipot) outstr += '\t\tfinal_form %s\n' % ' '.join(str(f) for f in fpot) outstr += '\tendimproper\n' pertfile.write(outstr) pertfile.write('endmolecule\n') pertfile.close()
def make_protein(name, ff, opts): """ Prepare proteins for simulation. """ logger.write('*** Working on %s ***\n' % name) prot = opts[SECT_PROT] load_cmds = '' if opts[SECT_DEF]['user_params']: load_cmds = _param_glob(PARAM_CMDS) vac_model_filename = name + const.MODEL_EXT sol_model_filename = 'solv_' + name + const.MODEL_EXT from_scratch = True workdir = os.path.join(os.getcwd(), const.PROTEIN_WORKDIR, name) if not opts[SECT_DEF]['remake']: model_path = _search_for_model([sol_model_filename, vac_model_filename], const.PROTEIN_WORKDIR) # FIXME: check for KeyError if model_path: model = read_model(model_path) name = model['name'] logger.write('Found model %s, extracting data' % name) # FIXME: only extract when const.PROTEIN_WORKDIR not present? model.extract(direc = workdir) protein = ff.Protein(name, prot['basedir']) protein.charge = float(model['charge.total']) protein.amber_top = model['top.filename'] protein.amber_crd = model['crd.filename'] protein.orig_file = model['crd.original'] if 'top.ssbond_file' in model: protein.ssbond_file = model['top.ssbond_file'] if 'box.dimensions' in model: protein.box_dims = model['box.dimensions'] if os.path.basename(model_path) == vac_model_filename: from_scratch = False else: return protein, load_cmds print('Making biomolecule %s...' % name) if not prot['basedir']: raise dGprepError('[%s] "basedir" must be set' % SECT_PROT) if os.path.isabs(prot['basedir']): src = os.path.join(prot['basedir'], name) else: src = os.path.join(os.getcwd(), prot['basedir'], name) if from_scratch: model = ModelConfig(name) protein = ff.Protein(name, prot['file.name']) model['crd.original'] = protein.mol_file model.add_file(protein.mol_file) with DirManager(workdir): if from_scratch: protein.copy_files((src,), None, opts[SECT_DEF]['overwrite']) if prot['propka']: protein.protonate_propka(pH = prot['propka.pH']) protein.get_charge() # must be done explicitly protein.prepare_top() protein.create_top(boxtype = '') model['charge.total'] = protein.charge model['forcefield'] = 'AMBER' # FIXME model['molecule.type'] = 'biomolecule' save_model(model, protein, vac_model_filename, '..') # FIXME: also check for boxlength and neutralize if prot['box.type']: protein.prepare_top() protein.create_top(boxtype = prot['box.type'], boxlength = prot['box.length'], neutralize = prot['neutralize'], align = prot['align_axes'], addcmd = load_cmds, remove_first = True) if prot['ions.conc'] > 0.0: protein.create_top(boxtype = prot['box.type'], boxlength = prot['box.length'], neutralize = 2, align = prot['align_axes'], addcmd = load_cmds, remove_first = False, conc = prot['ions.conc'], dens = prot['ions.dens']) restr_force = prot['min.restr_force'] nsteps = prot['min.nsteps'] protein.setup_MDEngine(opts[SECT_DEF]['mdengine'][1], opts[SECT_DEF]['mdengine.prefix'], opts[SECT_DEF]['mdengine.postfix']) if nsteps > 0: do_min(protein, prot) press_done = False #protein.md('%SHRINK', 200, 5.0, 1.0, ':LIG', 5.0, wrap = True) nsteps = prot['md.heat.nsteps'] if nsteps > 0: restr_force = prot['md.heat.restr_force'] do_md(protein, prot, 'heat') nsteps = prot['md.constT.nsteps'] if nsteps > 0: restr_force = prot['md.constT.restr_force'] do_md(protein, prot, 'constT') nsteps = prot['md.press.nsteps'] if nsteps > 0: restr_force = prot['md.press.restr_force'] do_md(protein, prot, 'press') press_done = True nrestr = prot['md.relax.nrestr'] if nrestr > 0: if opts[SECT_DEF]['mdengine'][0] == 'namd': protein.md('%RELRES', prot['md.relax.nsteps'], prot['md.relax.T'], prot['md.relax.p'], prot['md.relax.restraint'], restr_force, nrestr, wrap = True) else: sp = restr_force / (nrestr - 1) for k in range(nrestr - 2, -1, -1): if press_done: nmlist = '%PRESS' else: nmlist = '%CONSTT' protein.md(nmlist, prot['md.relax.nsteps'], prot['md.relax.T'], prot['md.relax.p'], prot['md.relax.restraint'], sp * k, wrap = True) if opts[SECT_DEF]['mdengine'][0] != 'amber': if _minmd_done(prot): protein.to_rst7() save_model(model, protein, sol_model_filename, '..') return protein, load_cmds
def make_complex(prot, lig, ff, opts, load_cmds): com = opts[SECT_COM] name = prot.mol_name + const.PROT_LIG_SEP + lig.mol_name vac_model_filename = name + const.MODEL_EXT sol_model_filename = 'solv_' + name + const.MODEL_EXT from_scratch = True workdir = os.path.join(os.getcwd(), const.COMPLEX_WORKDIR, name) if not opts[SECT_DEF]['remake']: model_path = _search_for_model([sol_model_filename, vac_model_filename], const.COMPLEX_WORKDIR) if model_path: model = read_model(model_path) name = model['name'] logger.write('Found model %s, extracting data' % name) # FIXME: only extract when const.COMPLEX_WORKDIR not present? model.extract(direc = workdir) complex = ff.Complex(prot, lig) complex.charge = float(model['charge.total']) complex.amber_top = model['top.filename'] complex.amber_crd = model['crd.filename'] if 'top.ssbond_file' in model: complex.ssbond_file = model['top.ssbond_file'] if 'box.dimensions' in model: complex.box_dims = model['box.dimensions'] if os.path.basename(model_path) == vac_model_filename: from_scratch = False else: return complex, load_cmds print('Making complex from %s and %s...' % (prot.mol_name, lig.mol_name)) if from_scratch: model = ModelConfig(name) if not model_path: complex = ff.Complex(prot, lig) lig_src = os.path.join(os.getcwd(), const.LIGAND_WORKDIR, lig.mol_name) prot_src = os.path.join(os.getcwd(), const.PROTEIN_WORKDIR, prot.mol_name) with DirManager(workdir): if from_scratch: complex.copy_files((lig_src, prot_src), (ligand.orig_file, ligand.frcmod, protein.orig_file, const.LIGAND_AC_FILE, const.SSBOND_FILE), opts[SECT_DEF]['overwrite']) complex.ligand_fmt = lig.mol_fmt complex.prepare_top(gaff=options[SECT_DEF]['gaff']) complex.create_top(boxtype='', addcmd=load_cmds) model['name'] = complex.complex_name model['charge.total'] = complex.charge model['forcefield'] = 'AMBER' # FIXME model['molecule.type'] = 'complex' # FIXME save_model(model, complex, vac_model_filename, '..') # FIXME: also check for boxlength and neutralize if com['box.type']: complex.prepare_top(gaff=options[SECT_DEF]['gaff']) complex.create_top(boxtype=com['box.type'], boxlength=com['box.length'], neutralize=com['neutralize'], align=com['align_axes'], addcmd=load_cmds, remove_first = True) if com['ions.conc'] > 0.0: complex.create_top(boxtype=com['box.type'], boxlength=com['box.length'], neutralize=2, align=com['align_axes'], addcmd=load_cmds, remove_first=False, conc=com['ions.conc'], dens=com['ions.dens']) restr_force = com['min.restr_force'] nsteps = com['min.nsteps'] complex.setup_MDEngine(opts[SECT_DEF]['mdengine'][1], opts[SECT_DEF]['mdengine.prefix'], opts[SECT_DEF]['mdengine.postfix']) if nsteps > 0: do_min(complex, com) if opts[SECT_DEF]['MC_prep']: complex.prot_flex() complex.flatten_rings() press_done = False #complex.md('%SHRINK', 200, 5.0, 1.0, 'bb_lig', 5.0, wrap = True) nsteps = com['md.heat.nsteps'] if nsteps > 0: restr_force = com['md.heat.restr_force'] do_md(complex, com, 'heat') nsteps = com['md.constT.nsteps'] if nsteps > 0: restr_force = com['md.constT.restr_force'] do_md(complex, com, 'constT') nsteps = com['md.press.nsteps'] if nsteps > 0: restr_force = com['md.press.restr_force'] do_md(complex, com, 'press') press_done = True nrestr = com['md.relax.nrestr'] if nrestr > 0: if opts[SECT_DEF]['mdengine'][0] == 'namd': complex.md('%RELRES', com['md.relax.nsteps'], com['md.relax.T'], com['md.relax.p'], com['md.relax.restraint'], restr_force, nrestr, wrap = True) else: sp = restr_force / (nrestr - 1) for k in range(nrestr - 2, -1, -1): if press_done: nmlist = '%PRESS' else: nmlist = '%CONSTT' complex.md(nmlist, com['md.relax.nsteps'], com['md.relax.T'], com['md.relax.p'], com['md.relax.restraint'], sp * k, wrap = True) if opts[SECT_DEF]['mdengine'][0] != 'amber': if _minmd_done(com): complex.to_rst7() save_model(model, complex, sol_model_filename, '..') return complex, load_cmds