def __init__(self,options,tgt_opts,forcefield): super(BindingEnergy,self).__init__(options,tgt_opts,forcefield) self.set_option(None, None, 'inter_txt', os.path.join(self.tgtdir,tgt_opts['inter_txt'])) self.global_opts, self.sys_opts, self.inter_opts = parse_interactions(self.inter_txt) # If the global option doesn't exist in the system / interaction, then it is copied over. for opt in self.global_opts: for sys in self.sys_opts: if opt not in self.sys_opts[sys]: self.sys_opts[sys][opt] = self.global_opts[opt] for inter in self.inter_opts: if opt not in self.inter_opts[inter]: self.inter_opts[inter][opt] = self.global_opts[opt] for inter in self.inter_opts: if 'energy_unit' in self.inter_opts[inter] and self.inter_opts[inter]['energy_unit'].lower() not in ['kilocalorie_per_mole', 'kilocalories_per_mole']: logger.error('Usage of physical units is has been removed, please provide all binding energies in kcal/mole\n') raise RuntimeError self.inter_opts[inter]['reference_physical'] = self.inter_opts[inter]['energy'] if tgt_opts['energy_denom'] == 0.0: self.set_option(None, None, 'energy_denom', val=np.std(np.array([val['reference_physical'] for val in self.inter_opts.values()]))) else: self.set_option(None, None, 'energy_denom', val=tgt_opts['energy_denom']) self.set_option(None, None, 'rmsd_denom', val=tgt_opts['rmsd_denom']) self.set_option(tgt_opts,'cauchy') self.set_option(tgt_opts,'attenuate') ## LPW 2018-02-11: This is set to True if the target calculates ## a single-point property over several existing snapshots. self.loop_over_snapshots = False logger.info("The energy denominator is: %s\n" % str(self.energy_denom)) logger.info("The RMSD denominator is: %s\n" % str(self.rmsd_denom)) if self.cauchy: logger.info("Each contribution to the interaction energy objective function will be scaled by 1.0 / ( denom**2 + reference**2 )\n") if self.attenuate: logger.error('attenuate and cauchy are mutually exclusive\n') raise RuntimeError elif self.attenuate: logger.info("Repulsive interactions beyond energy_denom will be scaled by 1.0 / ( denom**2 + (reference-denom)**2 )\n") ## Build keyword dictionaries to pass to engine. engine_args = OrderedDict(list(self.OptionDict.items()) + list(options.items())) del engine_args['name'] ## Create engine objects. self.engines = OrderedDict() for sysname,sysopt in self.sys_opts.items(): M = Molecule(os.path.join(self.root, self.tgtdir, sysopt['geometry'])) if 'select' in sysopt: atomselect = np.array(uncommadash(sysopt['select'])) M = M.atom_select(atomselect) if self.FF.rigid_water: M.rigid_water() self.engines[sysname] = self.engine_(target=self, mol=M, name=sysname, tinker_key=os.path.join(sysopt['keyfile']), **engine_args)
def prepare_temp_directory(self, options, tgt_opts): abstempdir = os.path.join(self.root,self.tempdir) if self.FF.rigid_water: self.optprog = "optrigid" #LinkFile(os.path.join(self.root,self.tgtdir,"rigid.key"),os.path.join(abstempdir,"rigid.key")) else: self.optprog = "optimize" # Link the necessary programs into the temporary directory LinkFile(os.path.join(options['tinkerpath'],"analyze"),os.path.join(abstempdir,"analyze")) LinkFile(os.path.join(options['tinkerpath'],self.optprog),os.path.join(abstempdir,self.optprog)) LinkFile(os.path.join(options['tinkerpath'],"superpose"),os.path.join(abstempdir,"superpose")) # Link the run parameter file # The master file might be unneeded?? for sysname,sysopt in self.sys_opts.items(): if self.FF.rigid_water: # Make every water molecule rigid # WARNING: Hard coded values here! M = Molecule(os.path.join(self.root, self.tgtdir, sysopt['geometry']),ftype="tinker") for a in range(0, len(M.xyzs[0]), 3): flex = M.xyzs[0] wat = flex[a:a+3] com = wat.mean(0) wat -= com o = wat[0] h1 = wat[1] h2 = wat[2] r1 = h1 - o r2 = h2 - o r1 /= Np.linalg.norm(r1) r2 /= Np.linalg.norm(r2) # Obtain unit vectors. ex = r1 + r2 ey = r1 - r2 ex /= Np.linalg.norm(ex) ey /= Np.linalg.norm(ey) Bond = 0.9572 Ang = Np.pi * 104.52 / 2 / 180 cosx = Np.cos(Ang) cosy = Np.sin(Ang) h1 = o + Bond*ex*cosx + Bond*ey*cosy h2 = o + Bond*ex*cosx - Bond*ey*cosy rig = Np.array([o, h1, h2]) + com M.xyzs[0][a:a+3] = rig M.write(os.path.join(abstempdir,sysopt['geometry']),ftype="tinker") else: M = Molecule(os.path.join(self.root, self.tgtdir, sysopt['geometry']),ftype="tinker") if 'select' in sysopt: atomselect = Np.array(uncommadash(sysopt['select'])) #atomselect = eval("Np.arange(M.na)"+sysopt['select']) M = M.atom_select(atomselect) M.write(os.path.join(abstempdir,sysname+".xyz"),ftype="tinker") write_key_with_prm(os.path.join(self.root,self.tgtdir,sysopt['keyfile']),os.path.join(abstempdir,sysname+".key"),ffobj=self.FF)
def __init__(self,options,tgt_opts,forcefield): super(BindingEnergy,self).__init__(options,tgt_opts,forcefield) self.set_option(None, None, 'inter_txt', os.path.join(self.tgtdir,tgt_opts['inter_txt'])) self.global_opts, self.sys_opts, self.inter_opts = parse_interactions(self.inter_txt) # If the global option doesn't exist in the system / interaction, then it is copied over. for opt in self.global_opts: for sys in self.sys_opts: if opt not in self.sys_opts[sys]: self.sys_opts[sys][opt] = self.global_opts[opt] for inter in self.inter_opts: if opt not in self.inter_opts[inter]: self.inter_opts[inter][opt] = self.global_opts[opt] for inter in self.inter_opts: if 'energy_unit' in self.inter_opts[inter] and self.inter_opts[inter]['energy_unit'].lower() not in ['kilocalorie_per_mole', 'kilocalories_per_mole']: logger.error('Usage of physical units is has been removed, please provide all binding energies in kcal/mole\n') raise RuntimeError self.inter_opts[inter]['reference_physical'] = self.inter_opts[inter]['energy'] if tgt_opts['energy_denom'] == 0.0: self.set_option(None, None, 'energy_denom', val=np.std(np.array([val['reference_physical'] for val in self.inter_opts.values()]))) else: self.set_option(None, None, 'energy_denom', val=tgt_opts['energy_denom']) self.set_option(None, None, 'rmsd_denom', val=tgt_opts['rmsd_denom']) self.set_option(tgt_opts,'cauchy') self.set_option(tgt_opts,'attenuate') logger.info("The energy denominator is: %s\n" % str(self.energy_denom)) logger.info("The RMSD denominator is: %s\n" % str(self.rmsd_denom)) if self.cauchy: logger.info("Each contribution to the interaction energy objective function will be scaled by 1.0 / ( denom**2 + reference**2 )\n") if self.attenuate: logger.error('attenuate and cauchy are mutually exclusive\n') raise RuntimeError elif self.attenuate: logger.info("Repulsive interactions beyond energy_denom will be scaled by 1.0 / ( denom**2 + (reference-denom)**2 )\n") ## Build keyword dictionaries to pass to engine. engine_args = OrderedDict(self.OptionDict.items() + options.items()) del engine_args['name'] ## Create engine objects. self.engines = OrderedDict() for sysname,sysopt in self.sys_opts.items(): M = Molecule(os.path.join(self.root, self.tgtdir, sysopt['geometry'])) if 'select' in sysopt: atomselect = np.array(uncommadash(sysopt['select'])) M = M.atom_select(atomselect) if self.FF.rigid_water: M.rigid_water() self.engines[sysname] = self.engine_(target=self, mol=M, name=sysname, tinker_key=os.path.join(sysopt['keyfile']), **engine_args)
def __init__(self,options,tgt_opts,forcefield): super(BindingEnergy,self).__init__(options,tgt_opts,forcefield) self.set_option(None, None, 'inter_txt', os.path.join(self.tgtdir,tgt_opts['inter_txt'])) self.global_opts, self.sys_opts, self.inter_opts = parse_interactions(self.inter_txt) # If the global option doesn't exist in the system / interaction, then it is copied over. for opt in self.global_opts: for sys in self.sys_opts: if opt not in self.sys_opts[sys]: self.sys_opts[sys][opt] = self.global_opts[opt] for inter in self.inter_opts: if opt not in self.inter_opts[inter]: self.inter_opts[inter][opt] = self.global_opts[opt] for inter in self.inter_opts: if 'energy_unit' in self.inter_opts[inter] and self.inter_opts[inter]['energy_unit'].lower() not in ['kilocalorie_per_mole', 'kilocalories_per_mole']: logger.error('Usage of physical units is has been removed, please provide all binding energies in kcal/mole\n') raise RuntimeError self.inter_opts[inter]['reference_physical'] = self.inter_opts[inter]['energy'] if tgt_opts['energy_denom'] == 0.0: self.set_option(None, None, 'energy_denom', val=np.std(np.array([val['reference_physical'] for val in self.inter_opts.values()]))) else: self.set_option(None, None, 'energy_denom', val=tgt_opts['energy_denom']) self.set_option(None, None, 'rmsd_denom', val=tgt_opts['rmsd_denom']) self.set_option(tgt_opts,'attenuate') ## LPW 2018-02-11: This is set to True if the target calculates ## a single-point property over several existing snapshots. self.loop_over_snapshots = False logger.info("The energy denominator is: %s\n" % str(self.energy_denom)) logger.info("The RMSD denominator is: %s\n" % str(self.rmsd_denom)) if self.attenuate: denom = self.energy_denom logger.info("Interaction energies more positive than %.1f will have reduced weight going as: 1.0 / (%.1f^2 + (energy-%.1f)^2)\n" % (denom, denom, denom)) ## Build keyword dictionaries to pass to engine. engine_args = OrderedDict(list(self.OptionDict.items()) + list(options.items())) engine_args.pop('name', None) ## Create engine objects. self.engines = OrderedDict() for sysname,sysopt in self.sys_opts.items(): M = Molecule(os.path.join(self.root, self.tgtdir, sysopt['geometry'])) if 'select' in sysopt: atomselect = np.array(uncommadash(sysopt['select'])) M = M.atom_select(atomselect) if self.FF.rigid_water: M.rigid_water() self.engines[sysname] = self.engine_(target=self, mol=M, name=sysname, tinker_key=os.path.join(sysopt['keyfile']), **engine_args)
class AMBER(Engine): """ Engine for carrying out general purpose AMBER calculations. """ def __init__(self, name="amber", **kwargs): ## Keyword args that aren't in this list are filtered out. self.valkwd = ['amberhome', 'pdb', 'mol2', 'frcmod', 'leapcmd'] super(AMBER,self).__init__(name=name, **kwargs) def setopts(self, **kwargs): """ Called by __init__ ; Set AMBER-specific options. """ ## The directory containing TINKER executables (e.g. dynamic) if 'amberhome' in kwargs: self.amberhome = kwargs['amberhome'] if not os.path.exists(os.path.join(self.amberhome,"sander")): warn_press_key("The 'sander' executable indicated by %s doesn't exist! (Check amberhome)" \ % os.path.join(self.amberhome,"sander")) else: warn_once("The 'amberhome' option was not specified; using default.") if which('sander') == '': warn_press_key("Please add AMBER executables to the PATH or specify amberhome.") self.amberhome = os.path.split(which('sander'))[0] with wopen('.quit.leap') as f: print >> f, 'quit' # AMBER search path self.spath = [] for line in self.callamber('tleap -f .quit.leap'): if 'Adding' in line and 'to search path' in line: self.spath.append(line.split('Adding')[1].split()[0]) os.remove('.quit.leap') def readsrc(self, **kwargs): """ Called by __init__ ; read files from the source directory. """ self.leapcmd = onefile(kwargs.get('leapcmd'), 'leap', err=True) self.absleap = os.path.abspath(self.leapcmd) # Name of the molecule, currently just call it a default name. self.mname = 'molecule' if 'mol' in kwargs: self.mol = kwargs['mol'] elif 'coords' in kwargs: crdfile = onefile(kwargs.get('coords'), None, err=True) self.mol = Molecule(crdfile, build_topology=False) # AMBER has certain PDB requirements, so we will absolutely require one. needpdb = True # if hasattr(self, 'mol') and all([i in self.mol.Data.keys() for i in ["chain", "atomname", "resid", "resname", "elem"]]): # needpdb = False # Determine the PDB file name. # If 'pdb' is provided to Engine initialization, it will be used to # copy over topology information (chain, atomname etc.). If mol/coords # is not provided, then it will also provide the coordinates. pdbfnm = onefile(kwargs.get('pdb'), 'pdb' if needpdb else None, err=needpdb) if pdbfnm != None: mpdb = Molecule(pdbfnm, build_topology=False) if hasattr(self, 'mol'): for i in ["chain", "atomname", "resid", "resname", "elem"]: self.mol.Data[i] = mpdb.Data[i] else: self.mol = copy.deepcopy(mpdb) self.abspdb = os.path.abspath(pdbfnm) # Write the PDB that AMBER is going to read in. # This may or may not be identical to the one used to initialize the engine. # self.mol.write('%s.pdb' % self.name) # self.abspdb = os.path.abspath('%s.pdb' % self.name) def callamber(self, command, stdin=None, print_to_screen=False, print_command=False, **kwargs): """ Call TINKER; prepend the amberhome to calling the TINKER program. """ csplit = command.split() # Sometimes the engine changes dirs and the inpcrd/prmtop go missing, so we link it. # Prepend the AMBER path to the program call. prog = os.path.join(self.amberhome, "bin", csplit[0]) csplit[0] = prog # No need to catch exceptions since failed AMBER calculations will return nonzero exit status. o = _exec(' '.join(csplit), stdin=stdin, print_to_screen=print_to_screen, print_command=print_command, rbytes=1024, **kwargs) return o def leap(self, name=None, delcheck=False): if not os.path.exists(self.leapcmd): LinkFile(self.absleap, self.leapcmd) pdb = os.path.basename(self.abspdb) if not os.path.exists(pdb): LinkFile(self.abspdb, pdb) if name == None: name = self.name write_leap(self.leapcmd, mol2=self.mol2, frcmod=self.frcmod, pdb=pdb, prefix=name, spath=self.spath, delcheck=delcheck) self.callamber("tleap -f %s_" % self.leapcmd) def prepare(self, pbc=False, **kwargs): """ Called by __init__ ; prepare the temp directory and figure out the topology. """ if hasattr(self,'FF'): if not (os.path.exists(self.FF.amber_frcmod) and os.path.exists(self.FF.amber_mol2)): # If the parameter files don't already exist, create them for the purpose of # preparing the engine, but then delete them afterward. prmtmp = True self.FF.make(np.zeros(self.FF.np)) # Currently force field object only allows one mol2 and frcmod file although this can be lifted. self.mol2 = [self.FF.amber_mol2] self.frcmod = [self.FF.amber_frcmod] if 'mol2' in kwargs: logger.error("FF object is provided, which overrides mol2 keyword argument") raise RuntimeError if 'frcmod' in kwargs: logger.error("FF object is provided, which overrides frcmod keyword argument") raise RuntimeError else: prmtmp = False self.mol2 = listfiles(kwargs.get('mol2'), 'mol2', err=True) self.frcmod = listfiles(kwargs.get('frcmod'), 'frcmod', err=True) # Figure out the topology information. self.leap() o = self.callamber("rdparm %s.prmtop" % self.name, stdin="printAtoms\nprintBonds\nexit\n", persist=True, print_error=False) # Once we do this, we don't need the prmtop and inpcrd anymore os.unlink("%s.inpcrd" % self.name) os.unlink("%s.prmtop" % self.name) os.unlink("leap.log") mode = 'None' self.AtomLists = defaultdict(list) G = nx.Graph() for line in o: s = line.split() if 'Atom' in line: mode = 'Atom' elif 'Bond' in line: mode = 'Bond' elif 'RDPARM MENU' in line: continue elif 'EXITING' in line: break elif len(s) == 0: continue elif mode == 'Atom': # Based on parsing lines like these: """ 327: HA -0.01462 1.0 ( 23:HIP ) H1 E 328: CB -0.33212 12.0 ( 23:HIP ) CT 3 329: HB2 0.10773 1.0 ( 23:HIP ) HC E 330: HB3 0.10773 1.0 ( 23:HIP ) HC E 331: CG 0.18240 12.0 ( 23:HIP ) CC B """ # Based on variable width fields. atom_number = int(line.split(':')[0]) atom_name = line.split()[1] atom_charge = float(line.split()[2]) atom_mass = float(line.split()[3]) rnn = line.split('(')[1].split(')')[0].split(':') residue_number = int(rnn[0]) residue_name = rnn[1] atom_type = line.split(')')[1].split()[0] self.AtomLists['Name'].append(atom_name) self.AtomLists['Charge'].append(atom_charge) self.AtomLists['Mass'].append(atom_mass) self.AtomLists['ResidueNumber'].append(residue_number) self.AtomLists['ResidueName'].append(residue_name) # Not sure if this works G.add_node(atom_number) elif mode == 'Bond': a, b = (int(i) for i in (line.split('(')[1].split(')')[0].split(','))) G.add_edge(a, b) self.AtomMask = [a == 'A' for a in self.AtomLists['ParticleType']] # Use networkx to figure out a list of molecule numbers. # gs = nx.connected_component_subgraphs(G) # tmols = [gs[i] for i in np.argsort(np.array([min(g.nodes()) for g in gs]))] # mnodes = [m.nodes() for m in tmols] # self.AtomLists['MoleculeNumber'] = [[i+1 in m for m in mnodes].index(1) for i in range(self.mol.na)] ## Write out the trajectory coordinates to a .mdcrd file. # I also need to write the trajectory if 'boxes' in self.mol.Data.keys(): warn_press_key("Writing %s-all.crd file with no periodic box information" % self.name) del self.mol.Data['boxes'] if hasattr(self, 'target') and hasattr(self.target,'shots'): self.qmatoms = target.qmatoms self.mol.write("%s-all.crd" % self.name, select=range(self.target.shots), ftype="mdcrd") else: self.qmatoms = self.mol.na self.mol.write("%s-all.crd" % self.name, ftype="mdcrd") if prmtmp: for f in self.FF.fnms: os.unlink(f) def evaluate_(self, crdin, force=False): """ Utility function for computing energy and forces using AMBER. Inputs: crdin: AMBER .mdcrd file name. force: Switch for parsing the force. (Currently it always calculates the forces.) Outputs: Result: Dictionary containing energies (and optionally) forces. """ force_mdin="""Loop over conformations and compute energy and force (use ioutfnm=1 for netcdf, ntb=0 for no box) &cntrl imin = 5, ntb = 0, cut=9, nstlim = 0, nsnb = 0 / &debugf do_debugf = 1, dumpfrc = 1 / """ with wopen("%s-force.mdin" % self.name) as f: print >> f, force_mdin ## This line actually runs AMBER. self.leap(delcheck=True) self.callamber("sander -i %s-force.mdin -o %s-force.mdout -p %s.prmtop -c %s.inpcrd -y %s -O" % (self.name, self.name, self.name, self.name, crdin)) ParseMode = 0 Result = {} Energies = [] Forces = [] Force = [] for line in open('forcedump.dat'): line = line.strip() sline = line.split() if ParseMode == 1: if len(sline) == 1 and isfloat(sline[0]): Energies.append(float(sline[0]) * 4.184) ParseMode = 0 if ParseMode == 2: if len(sline) == 3 and all(isfloat(sline[i]) for i in range(3)): Force += [float(sline[i]) * 4.184 * 10 for i in range(3)] if len(Force) == 3*self.qmatoms: Forces.append(np.array(Force)) Force = [] ParseMode = 0 if line == '0 START of Energies': ParseMode = 1 elif line == '1 Total Force': ParseMode = 2 Result["Energy"] = np.array(Energies[1:]) Result["Force"] = np.array(Forces[1:]) return Result def energy_force_one(self, shot): """ Computes the energy and force using TINKER for one snapshot. """ self.mol[shot].write("%s.xyz" % self.name, ftype="tinker") Result = self.evaluate_("%s.xyz" % self.name, force=True) return np.hstack((Result["Energy"].reshape(-1,1), Result["Force"])) def energy(self): """ Computes the energy using TINKER over a trajectory. """ if hasattr(self, 'md_trajectory') : x = self.md_trajectory else: x = "%s-all.crd" % self.name self.mol.write(x, ftype="tinker") return self.evaluate_(x)["Energy"] def energy_force(self): """ Computes the energy and force using AMBER over a trajectory. """ if hasattr(self, 'md_trajectory') : x = self.md_trajectory else: x = "%s-all.crd" % self.name Result = self.evaluate_(x, force=True) return np.hstack((Result["Energy"].reshape(-1,1), Result["Force"])) def energy_dipole(self): """ Computes the energy and dipole using TINKER over a trajectory. """ logger.error('Dipole moments are not yet implemented in AMBER interface') raise NotImplementedError if hasattr(self, 'md_trajectory') : x = self.md_trajectory else: x = "%s.xyz" % self.name self.mol.write(x, ftype="tinker") Result = self.evaluate_(x, dipole=True) return np.hstack((Result["Energy"].reshape(-1,1), Result["Dipole"])) def optimize(self, shot=0, method="newton", crit=1e-4): """ Optimize the geometry and align the optimized geometry to the starting geometry. """ logger.error('Geometry optimizations are not yet implemented in AMBER interface') raise NotImplementedError # Code from tinkerio.py if os.path.exists('%s.xyz_2' % self.name): os.unlink('%s.xyz_2' % self.name) self.mol[shot].write('%s.xyz' % self.name, ftype="tinker") if method == "newton": if self.rigid: optprog = "optrigid" else: optprog = "optimize" elif method == "bfgs": if self.rigid: optprog = "minrigid" else: optprog = "minimize" o = self.calltinker("%s %s.xyz %f" % (optprog, self.name, crit)) # Silently align the optimized geometry. M12 = Molecule("%s.xyz" % self.name, ftype="tinker") + Molecule("%s.xyz_2" % self.name, ftype="tinker") if not self.pbc: M12.align(center=False) M12[1].write("%s.xyz_2" % self.name, ftype="tinker") rmsd = M12.ref_rmsd(0)[1] cnvgd = 0 mode = 0 for line in o: s = line.split() if len(s) == 0: continue if "Optimally Conditioned Variable Metric Optimization" in line: mode = 1 if "Limited Memory BFGS Quasi-Newton Optimization" in line: mode = 1 if mode == 1 and isint(s[0]): mode = 2 if mode == 2: if isint(s[0]): E = float(s[1]) else: mode = 0 if "Normal Termination" in line: cnvgd = 1 if not cnvgd: for line in o: logger.info(str(line) + '\n') logger.info("The minimization did not converge in the geometry optimization - printout is above.\n") return E, rmsd def normal_modes(self, shot=0, optimize=True): logger.error('Normal modes are not yet implemented in AMBER interface') raise NotImplementedError # Copied from tinkerio.py if optimize: self.optimize(shot, crit=1e-6) o = self.calltinker("vibrate %s.xyz_2 a" % (self.name)) else: warn_once("Asking for normal modes without geometry optimization?") self.mol[shot].write('%s.xyz' % self.name, ftype="tinker") o = self.calltinker("vibrate %s.xyz a" % (self.name)) # Read the TINKER output. The vibrational frequencies are ordered. # The six modes with frequencies closest to zero are ignored readev = False calc_eigvals = [] calc_eigvecs = [] for line in o: s = line.split() if "Vibrational Normal Mode" in line: freq = float(s[-2]) readev = False calc_eigvals.append(freq) calc_eigvecs.append([]) elif "Atom" in line and "Delta X" in line: readev = True elif readev and len(s) == 4 and all([isint(s[0]), isfloat(s[1]), isfloat(s[2]), isfloat(s[3])]): calc_eigvecs[-1].append([float(i) for i in s[1:]]) calc_eigvals = np.array(calc_eigvals) calc_eigvecs = np.array(calc_eigvecs) # Sort by frequency absolute value and discard the six that are closest to zero calc_eigvecs = calc_eigvecs[np.argsort(np.abs(calc_eigvals))][6:] calc_eigvals = calc_eigvals[np.argsort(np.abs(calc_eigvals))][6:] # Sort again by frequency calc_eigvecs = calc_eigvecs[np.argsort(calc_eigvals)] calc_eigvals = calc_eigvals[np.argsort(calc_eigvals)] os.system("rm -rf *.xyz_* *.[0-9][0-9][0-9]") return calc_eigvals, calc_eigvecs def multipole_moments(self, shot=0, optimize=True, polarizability=False): logger.error('Multipole moments are not yet implemented in AMBER interface') raise NotImplementedError """ Return the multipole moments of the 1st snapshot in Debye and Buckingham units. """ # This line actually runs TINKER if optimize: self.optimize(shot, crit=1e-6) o = self.calltinker("analyze %s.xyz_2 M" % (self.name)) else: self.mol[shot].write('%s.xyz' % self.name, ftype="tinker") o = self.calltinker("analyze %s.xyz M" % (self.name)) # Read the TINKER output. qn = -1 ln = 0 for line in o: s = line.split() if "Dipole X,Y,Z-Components" in line: dipole_dict = OrderedDict(zip(['x','y','z'], [float(i) for i in s[-3:]])) elif "Quadrupole Moment Tensor" in line: qn = ln quadrupole_dict = OrderedDict([('xx',float(s[-3]))]) elif qn > 0 and ln == qn + 1: quadrupole_dict['xy'] = float(s[-3]) quadrupole_dict['yy'] = float(s[-2]) elif qn > 0 and ln == qn + 2: quadrupole_dict['xz'] = float(s[-3]) quadrupole_dict['yz'] = float(s[-2]) quadrupole_dict['zz'] = float(s[-1]) ln += 1 calc_moments = OrderedDict([('dipole', dipole_dict), ('quadrupole', quadrupole_dict)]) if polarizability: if optimize: o = self.calltinker("polarize %s.xyz_2" % (self.name)) else: o = self.calltinker("polarize %s.xyz" % (self.name)) # Read the TINKER output. pn = -1 ln = 0 polarizability_dict = OrderedDict() for line in o: s = line.split() if "Total Polarizability Tensor" in line: pn = ln elif pn > 0 and ln == pn + 2: polarizability_dict['xx'] = float(s[-3]) polarizability_dict['yx'] = float(s[-2]) polarizability_dict['zx'] = float(s[-1]) elif pn > 0 and ln == pn + 3: polarizability_dict['xy'] = float(s[-3]) polarizability_dict['yy'] = float(s[-2]) polarizability_dict['zy'] = float(s[-1]) elif pn > 0 and ln == pn + 4: polarizability_dict['xz'] = float(s[-3]) polarizability_dict['yz'] = float(s[-2]) polarizability_dict['zz'] = float(s[-1]) ln += 1 calc_moments['polarizability'] = polarizability_dict os.system("rm -rf *.xyz_* *.[0-9][0-9][0-9]") return calc_moments def energy_rmsd(self, shot=0, optimize=True): """ Calculate energy of the selected structure (optionally minimize and return the minimized energy and RMSD). In kcal/mol. """ logger.error('Geometry optimization is not yet implemented in AMBER interface') raise NotImplementedError rmsd = 0.0 # This line actually runs TINKER # xyzfnm = sysname+".xyz" if optimize: E_, rmsd = self.optimize(shot) o = self.calltinker("analyze %s.xyz_2 E" % self.name) #---- # Two equivalent ways to get the RMSD, here for reference. #---- # M1 = Molecule("%s.xyz" % self.name, ftype="tinker") # M2 = Molecule("%s.xyz_2" % self.name, ftype="tinker") # M1 += M2 # rmsd = M1.ref_rmsd(0)[1] #---- # oo = self.calltinker("superpose %s.xyz %s.xyz_2 1 y u n 0" % (self.name, self.name)) # for line in oo: # if "Root Mean Square Distance" in line: # rmsd = float(line.split()[-1]) #---- os.system("rm %s.xyz_2" % self.name) else: o = self.calltinker("analyze %s.xyz E" % self.name) # Read the TINKER output. E = None for line in o: if "Total Potential Energy" in line: E = float(line.split()[-2].replace('D','e')) if E == None: logger.error("Total potential energy wasn't encountered when calling analyze!\n") raise RuntimeError if optimize and abs(E-E_) > 0.1: warn_press_key("Energy from optimize and analyze aren't the same (%.3f vs. %.3f)" % (E, E_)) return E, rmsd def interaction_energy(self, fraga, fragb): """ Calculate the interaction energy for two fragments. """ logger.error('Interaction energy is not yet implemented in AMBER interface') raise NotImplementedError self.A = TINKER(name="A", mol=self.mol.atom_select(fraga), tinker_key="%s.key" % self.name, tinkerpath=self.tinkerpath) self.B = TINKER(name="B", mol=self.mol.atom_select(fragb), tinker_key="%s.key" % self.name, tinkerpath=self.tinkerpath) # Interaction energy needs to be in kcal/mol. return (self.energy() - self.A.energy() - self.B.energy()) / 4.184 def molecular_dynamics(self, nsteps, timestep, temperature=None, pressure=None, nequil=0, nsave=1000, minimize=True, anisotropic=False, threads=1, verbose=False, **kwargs): """ Method for running a molecular dynamics simulation. Required arguments: nsteps = (int) Number of total time steps timestep = (float) Time step in FEMTOSECONDS temperature = (float) Temperature control (Kelvin) pressure = (float) Pressure control (atmospheres) nequil = (int) Number of additional time steps at the beginning for equilibration nsave = (int) Step interval for saving and printing data minimize = (bool) Perform an energy minimization prior to dynamics threads = (int) Specify how many OpenMP threads to use Returns simulation data: Rhos = (array) Density in kilogram m^-3 Potentials = (array) Potential energies Kinetics = (array) Kinetic energies Volumes = (array) Box volumes Dips = (3xN array) Dipole moments EComps = (dict) Energy components """ logger.error('Molecular dynamics not yet implemented in AMBER interface') raise NotImplementedError md_defs = OrderedDict() md_opts = OrderedDict() # Print out averages only at the end. md_opts["printout"] = nsave md_opts["openmp-threads"] = threads # Langevin dynamics for temperature control. if temperature != None: md_defs["integrator"] = "stochastic" else: md_defs["integrator"] = "beeman" md_opts["thermostat"] = None # Periodic boundary conditions. if self.pbc: md_opts["vdw-correction"] = '' if temperature != None and pressure != None: md_defs["integrator"] = "beeman" md_defs["thermostat"] = "bussi" md_defs["barostat"] = "montecarlo" if anisotropic: md_opts["aniso-pressure"] = '' elif pressure != None: warn_once("Pressure is ignored because temperature is turned off.") else: if pressure != None: warn_once("Pressure is ignored because pbc is set to False.") # Use stochastic dynamics for the gas phase molecule. # If we use the regular integrators it may miss # six degrees of freedom in calculating the kinetic energy. md_opts["barostat"] = None eq_opts = deepcopy(md_opts) if self.pbc and temperature != None and pressure != None: eq_opts["integrator"] = "beeman" eq_opts["thermostat"] = "bussi" eq_opts["barostat"] = "berendsen" if minimize: if verbose: logger.info("Minimizing the energy...") self.optimize(method="bfgs", crit=1) os.system("mv %s.xyz_2 %s.xyz" % (self.name, self.name)) if verbose: logger.info("Done\n") # Run equilibration. if nequil > 0: write_key("%s-eq.key" % self.name, eq_opts, "%s.key" % self.name, md_defs) if verbose: printcool("Running equilibration dynamics", color=0) if self.pbc and pressure != None: self.calltinker("dynamic %s -k %s-eq %i %f %f 4 %f %f" % (self.name, self.name, nequil, timestep, float(nsave*timestep)/1000, temperature, pressure), print_to_screen=verbose) else: self.calltinker("dynamic %s -k %s-eq %i %f %f 2 %f" % (self.name, self.name, nequil, timestep, float(nsave*timestep)/1000, temperature), print_to_screen=verbose) os.system("rm -f %s.arc" % (self.name)) # Run production. if verbose: printcool("Running production dynamics", color=0) write_key("%s-md.key" % self.name, md_opts, "%s.key" % self.name, md_defs) if self.pbc and pressure != None: odyn = self.calltinker("dynamic %s -k %s-md %i %f %f 4 %f %f" % (self.name, self.name, nsteps, timestep, float(nsave*timestep/1000), temperature, pressure), print_to_screen=verbose) else: odyn = self.calltinker("dynamic %s -k %s-md %i %f %f 2 %f" % (self.name, self.name, nsteps, timestep, float(nsave*timestep/1000), temperature), print_to_screen=verbose) # Gather information. os.system("mv %s.arc %s-md.arc" % (self.name, self.name)) self.md_trajectory = "%s-md.arc" % self.name edyn = [] kdyn = [] temps = [] for line in odyn: s = line.split() if 'Current Potential' in line: edyn.append(float(s[2])) if 'Current Kinetic' in line: kdyn.append(float(s[2])) if len(s) > 0 and s[0] == 'Temperature' and s[2] == 'Kelvin': temps.append(float(s[1])) # Potential and kinetic energies converted to kJ/mol. edyn = np.array(edyn) * 4.184 kdyn = np.array(kdyn) * 4.184 temps = np.array(temps) if verbose: logger.info("Post-processing to get the dipole moments\n") oanl = self.calltinker("analyze %s-md.arc" % self.name, stdin="G,E,M", print_to_screen=False) # Read potential energy and dipole from file. eanl = [] dip = [] mass = 0.0 ecomp = OrderedDict() havekeys = set() first_shot = True for ln, line in enumerate(oanl): strip = line.strip() s = line.split() if 'Total System Mass' in line: mass = float(s[-1]) if 'Total Potential Energy : ' in line: eanl.append(float(s[4])) if 'Dipole X,Y,Z-Components :' in line: dip.append([float(s[i]) for i in range(-3,0)]) if first_shot: for key in eckeys: if strip.startswith(key): if key in ecomp: ecomp[key].append(float(s[-2])*4.184) else: ecomp[key] = [float(s[-2])*4.184] if key in havekeys: first_shot = False havekeys.add(key) else: for key in havekeys: if strip.startswith(key): if key in ecomp: ecomp[key].append(float(s[-2])*4.184) else: ecomp[key] = [float(s[-2])*4.184] for key in ecomp: ecomp[key] = np.array(ecomp[key]) ecomp["Potential Energy"] = edyn ecomp["Kinetic Energy"] = kdyn ecomp["Temperature"] = temps ecomp["Total Energy"] = edyn+kdyn # Energies in kilojoules per mole eanl = np.array(eanl) * 4.184 # Dipole moments in debye dip = np.array(dip) # Volume of simulation boxes in cubic nanometers # Conversion factor derived from the following: # In [22]: 1.0 * gram / mole / (1.0 * nanometer)**3 / AVOGADRO_CONSTANT_NA / (kilogram/meter**3) # Out[22]: 1.6605387831627252 conv = 1.6605387831627252 if self.pbc: vol = np.array([BuildLatticeFromLengthsAngles(*[float(j) for j in line.split()]).V \ for line in open("%s-md.arc" % self.name).readlines() \ if (len(line.split()) == 6 and isfloat(line.split()[1]) \ and all([isfloat(i) for i in line.split()[:6]]))]) / 1000 rho = conv * mass / vol else: vol = None rho = None prop_return = OrderedDict() prop_return.update({'Rhos': rho, 'Potentials': edyn, 'Kinetics': kdyn, 'Volumes': vol, 'Dips': dip, 'Ecomps': ecomp}) return prop_return
class TINKER(Engine): """ Engine for carrying out general purpose TINKER calculations. """ def __init__(self, name="tinker", **kwargs): ## Keyword args that aren't in this list are filtered out. self.valkwd = ['tinker_key', 'tinkerpath', 'tinker_prm'] self.warn_vn = False super(TINKER,self).__init__(name=name, **kwargs) def setopts(self, **kwargs): """ Called by __init__ ; Set TINKER-specific options. """ ## The directory containing TINKER executables (e.g. dynamic) if 'tinkerpath' in kwargs: self.tinkerpath = kwargs['tinkerpath'] if not os.path.exists(os.path.join(self.tinkerpath,"dynamic")): warn_press_key("The 'dynamic' executable indicated by %s doesn't exist! (Check tinkerpath)" \ % os.path.join(self.tinkerpath,"dynamic")) else: warn_once("The 'tinkerpath' option was not specified; using default.") if which('mdrun') == '': warn_press_key("Please add TINKER executables to the PATH or specify tinkerpath.") self.tinkerpath = which('dynamic') def readsrc(self, **kwargs): """ Called by __init__ ; read files from the source directory. """ self.key = onefile(kwargs.get('tinker_key'), 'key') self.prm = onefile(kwargs.get('tinker_prm'), 'prm') if 'mol' in kwargs: self.mol = kwargs['mol'] else: crdfile = onefile(kwargs.get('coords'), 'arc', err=True) self.mol = Molecule(crdfile) def calltinker(self, command, stdin=None, print_to_screen=False, print_command=False, **kwargs): """ Call TINKER; prepend the tinkerpath to calling the TINKER program. """ csplit = command.split() # Sometimes the engine changes dirs and the key goes missing, so we link it. if "%s.key" % self.name in csplit and not os.path.exists("%s.key" % self.name): LinkFile(self.abskey, "%s.key" % self.name) prog = os.path.join(self.tinkerpath, csplit[0]) csplit[0] = prog o = _exec(' '.join(csplit), stdin=stdin, print_to_screen=print_to_screen, print_command=print_command, rbytes=1024, **kwargs) # Determine the TINKER version number. for line in o[:10]: if "Version" in line: vw = line.split()[2] if len(vw.split('.')) <= 2: vn = float(vw) else: vn = float(vw.split('.')[:2]) vn_need = 6.3 try: if vn < vn_need: if self.warn_vn: warn_press_key("ForceBalance requires TINKER %.1f - unexpected behavior with older versions!" % vn_need) self.warn_vn = True except: logger.error("Unable to determine TINKER version number!\n") raise RuntimeError for line in o[-10:]: # Catch exceptions since TINKER does not have exit status. if "TINKER is Unable to Continue" in line: for l in o: logger.error("%s\n" % l) time.sleep(1) logger.error("TINKER may have crashed! (See above output)\nThe command was: %s\nThe directory was: %s\n" % (' '.join(csplit), os.getcwd())) raise RuntimeError break for line in o: if 'D+' in line: logger.info(line+'\n') warn_press_key("TINKER returned a very large floating point number! (See above line; will give error on parse)") return o def prepare(self, pbc=False, **kwargs): """ Called by __init__ ; prepare the temp directory and figure out the topology. """ # Call TINKER but do nothing to figure out the version number. o = self.calltinker("dynamic", persist=1, print_error=False) self.rigid = False ## Attempt to set some TINKER options. tk_chk = [] tk_opts = OrderedDict([("digits", "10"), ("archive", "")]) tk_defs = OrderedDict() prmtmp = False if hasattr(self,'FF'): if not os.path.exists(self.FF.tinkerprm): # If the parameter files don't already exist, create them for the purpose of # preparing the engine, but then delete them afterward. prmtmp = True self.FF.make(np.zeros(self.FF.np)) if self.FF.rigid_water: tk_opts["rattle"] = "water" self.rigid = True if self.FF.amoeba_pol == 'mutual': tk_opts['polarization'] = 'mutual' if self.FF.amoeba_eps != None: tk_opts['polar-eps'] = str(self.FF.amoeba_eps) else: tk_defs['polar-eps'] = '1e-6' elif self.FF.amoeba_pol == 'direct': tk_opts['polarization'] = 'direct' else: warn_press_key("Using TINKER without explicitly specifying AMOEBA settings. Are you sure?") self.prm = self.FF.tinkerprm prmfnm = self.FF.tinkerprm elif self.prm: prmfnm = self.prm else: prmfnm = None # Periodic boundary conditions may come from the TINKER .key file. keypbc = False minbox = 1e10 if self.key: for line in open(os.path.join(self.srcdir, self.key)).readlines(): s = line.split() if len(s) > 0 and s[0].lower() == 'a-axis': keypbc = True minbox = float(s[1]) if len(s) > 0 and s[0].lower() == 'b-axis' and float(s[1]) < minbox: minbox = float(s[1]) if len(s) > 0 and s[0].lower() == 'c-axis' and float(s[1]) < minbox: minbox = float(s[1]) if keypbc and (not pbc): warn_once("Deleting PBC options from the .key file.") tk_opts['a-axis'] = None tk_opts['b-axis'] = None tk_opts['c-axis'] = None tk_opts['alpha'] = None tk_opts['beta'] = None tk_opts['gamma'] = None if pbc: if (not keypbc) and 'boxes' not in self.mol.Data: logger.error("Periodic boundary conditions require either (1) a-axis to be in the .key file or (b) boxes to be in the coordinate file.\n") raise RuntimeError self.pbc = pbc if pbc: tk_opts['ewald'] = '' if minbox <= 10: warn_press_key("Periodic box is set to less than 10 Angstroms across") # TINKER likes to use up to 7.0 Angstrom for PME cutoffs rpme = 0.05*(float(int(minbox - 1))) if minbox <= 15 else 7.0 tk_defs['ewald-cutoff'] = "%f" % rpme # TINKER likes to use up to 9.0 Angstrom for vdW cutoffs rvdw = 0.05*(float(int(minbox - 1))) if minbox <= 19 else 9.0 tk_defs['vdw-cutoff'] = "%f" % rvdw if (minbox*0.5 - rpme) > 2.5 and (minbox*0.5 - rvdw) > 2.5: tk_defs['neighbor-list'] = '' elif (minbox*0.5 - rpme) > 2.5: tk_defs['mpole-list'] = '' else: tk_opts['ewald'] = None tk_opts['ewald-cutoff'] = None tk_opts['vdw-cutoff'] = None # This seems to have no effect on the kinetic energy. # tk_opts['remove-inertia'] = '0' write_key("%s.key" % self.name, tk_opts, os.path.join(self.srcdir, self.key) if self.key else None, tk_defs, verbose=False, prmfnm=prmfnm) self.abskey = os.path.abspath("%s.key") self.mol[0].write(os.path.join("%s.xyz" % self.name), ftype="tinker") ## If the coordinates do not come with TINKER suffixes then throw an error. self.mol.require('tinkersuf') ## Call analyze to read information needed to build the atom lists. o = self.calltinker("analyze %s.xyz P,C" % (self.name), stdin="ALL") ## Parse the output of analyze. mode = 0 self.AtomMask = [] self.AtomLists = defaultdict(list) ptype_dict = {'atom': 'A', 'vsite': 'D'} G = nx.Graph() for line in o: s = line.split() if len(s) == 0: continue if "Atom Type Definition Parameters" in line: mode = 1 if mode == 1: if isint(s[0]): mode = 2 if mode == 2: if isint(s[0]): mass = float(s[5]) self.AtomLists['Mass'].append(mass) if mass < 1.0: # Particles with mass less than one count as virtual sites. self.AtomLists['ParticleType'].append('D') else: self.AtomLists['ParticleType'].append('A') self.AtomMask.append(mass >= 1.0) else: mode = 0 if "List of 1-2 Connected Atomic Interactions" in line: mode = 3 if mode == 3: if isint(s[0]): mode = 4 if mode == 4: if isint(s[0]): a = int(s[0]) b = int(s[1]) G.add_node(a) G.add_node(b) G.add_edge(a, b) else: mode = 0 # Use networkx to figure out a list of molecule numbers. if len(G.nodes()) > 0: # The following code only works in TINKER 6.2 gs = nx.connected_component_subgraphs(G) tmols = [gs[i] for i in np.argsort(np.array([min(g.nodes()) for g in gs]))] mnodes = [m.nodes() for m in tmols] self.AtomLists['MoleculeNumber'] = [[i+1 in m for m in mnodes].index(1) for i in range(self.mol.na)] else: grouped = [i.L() for i in self.mol.molecules] self.AtomLists['MoleculeNumber'] = [[i in g for g in grouped].index(1) for i in range(self.mol.na)] if prmtmp: for f in self.FF.fnms: os.unlink(f) def optimize(self, shot=0, method="newton", crit=1e-4): """ Optimize the geometry and align the optimized geometry to the starting geometry. """ if os.path.exists('%s.xyz_2' % self.name): os.unlink('%s.xyz_2' % self.name) self.mol[shot].write('%s.xyz' % self.name, ftype="tinker") if method == "newton": if self.rigid: optprog = "optrigid" else: optprog = "optimize" elif method == "bfgs": if self.rigid: optprog = "minrigid" else: optprog = "minimize" o = self.calltinker("%s %s.xyz %f" % (optprog, self.name, crit)) # Silently align the optimized geometry. M12 = Molecule("%s.xyz" % self.name, ftype="tinker") + Molecule("%s.xyz_2" % self.name, ftype="tinker") if not self.pbc: M12.align(center=False) M12[1].write("%s.xyz_2" % self.name, ftype="tinker") rmsd = M12.ref_rmsd(0)[1] cnvgd = 0 mode = 0 for line in o: s = line.split() if len(s) == 0: continue if "Optimally Conditioned Variable Metric Optimization" in line: mode = 1 if "Limited Memory BFGS Quasi-Newton Optimization" in line: mode = 1 if mode == 1 and isint(s[0]): mode = 2 if mode == 2: if isint(s[0]): E = float(s[1]) else: mode = 0 if "Normal Termination" in line: cnvgd = 1 if not cnvgd: for line in o: logger.info(str(line) + '\n') logger.info("The minimization did not converge in the geometry optimization - printout is above.\n") return E, rmsd def evaluate_(self, xyzin, force=False, dipole=False): """ Utility function for computing energy, and (optionally) forces and dipoles using TINKER. Inputs: xyzin: TINKER .xyz file name. force: Switch for calculating the force. dipole: Switch for calculating the dipole. Outputs: Result: Dictionary containing energies, forces and/or dipoles. """ Result = OrderedDict() # If we want the dipoles (or just energies), analyze is the way to go. if dipole or (not force): oanl = self.calltinker("analyze %s -k %s" % (xyzin, self.name), stdin="G,E,M", print_to_screen=False) # Read potential energy and dipole from file. eanl = [] dip = [] for line in oanl: s = line.split() if 'Total Potential Energy : ' in line: eanl.append(float(s[4]) * 4.184) if dipole: if 'Dipole X,Y,Z-Components :' in line: dip.append([float(s[i]) for i in range(-3,0)]) Result["Energy"] = np.array(eanl) Result["Dipole"] = np.array(dip) # If we want forces, then we need to call testgrad. if force: E = [] F = [] Fi = [] o = self.calltinker("testgrad %s -k %s y n n" % (xyzin, self.name)) i = 0 ReadFrc = 0 for line in o: s = line.split() if "Total Potential Energy" in line: E.append(float(s[4]) * 4.184) if "Cartesian Gradient Breakdown over Individual Atoms" in line: ReadFrc = 1 if ReadFrc and len(s) == 6 and all([s[0] == 'Anlyt',isint(s[1]),isfloat(s[2]),isfloat(s[3]),isfloat(s[4]),isfloat(s[5])]): ReadFrc = 2 if self.AtomMask[i]: Fi += [-1 * float(j) * 4.184 * 10 for j in s[2:5]] i += 1 if ReadFrc == 2 and len(s) < 6: ReadFrc = 0 F.append(Fi) Fi = [] i = 0 Result["Energy"] = np.array(E) Result["Force"] = np.array(F) return Result def energy_force_one(self, shot): """ Computes the energy and force using TINKER for one snapshot. """ self.mol[shot].write("%s.xyz" % self.name, ftype="tinker") Result = self.evaluate_("%s.xyz" % self.name, force=True) return np.hstack((Result["Energy"].reshape(-1,1), Result["Force"])) def energy(self): """ Computes the energy using TINKER over a trajectory. """ if hasattr(self, 'md_trajectory') : x = self.md_trajectory else: x = "%s.xyz" % self.name self.mol.write(x, ftype="tinker") return self.evaluate_(x)["Energy"] def energy_force(self): """ Computes the energy and force using TINKER over a trajectory. """ if hasattr(self, 'md_trajectory') : x = self.md_trajectory else: x = "%s.xyz" % self.name self.mol.write(x, ftype="tinker") Result = self.evaluate_(x, force=True) return np.hstack((Result["Energy"].reshape(-1,1), Result["Force"])) def energy_dipole(self): """ Computes the energy and dipole using TINKER over a trajectory. """ if hasattr(self, 'md_trajectory') : x = self.md_trajectory else: x = "%s.xyz" % self.name self.mol.write(x, ftype="tinker") Result = self.evaluate_(x, dipole=True) return np.hstack((Result["Energy"].reshape(-1,1), Result["Dipole"])) def normal_modes(self, shot=0, optimize=True): # This line actually runs TINKER if optimize: self.optimize(shot, crit=1e-6) o = self.calltinker("vibrate %s.xyz_2 a" % (self.name)) else: warn_once("Asking for normal modes without geometry optimization?") self.mol[shot].write('%s.xyz' % self.name, ftype="tinker") o = self.calltinker("vibrate %s.xyz a" % (self.name)) # Read the TINKER output. The vibrational frequencies are ordered. # The six modes with frequencies closest to zero are ignored readev = False calc_eigvals = [] calc_eigvecs = [] for line in o: s = line.split() if "Vibrational Normal Mode" in line: freq = float(s[-2]) readev = False calc_eigvals.append(freq) calc_eigvecs.append([]) elif "Atom" in line and "Delta X" in line: readev = True elif readev and len(s) == 4 and all([isint(s[0]), isfloat(s[1]), isfloat(s[2]), isfloat(s[3])]): calc_eigvecs[-1].append([float(i) for i in s[1:]]) calc_eigvals = np.array(calc_eigvals) calc_eigvecs = np.array(calc_eigvecs) # Sort by frequency absolute value and discard the six that are closest to zero calc_eigvecs = calc_eigvecs[np.argsort(np.abs(calc_eigvals))][6:] calc_eigvals = calc_eigvals[np.argsort(np.abs(calc_eigvals))][6:] # Sort again by frequency calc_eigvecs = calc_eigvecs[np.argsort(calc_eigvals)] calc_eigvals = calc_eigvals[np.argsort(calc_eigvals)] os.system("rm -rf *.xyz_* *.[0-9][0-9][0-9]") return calc_eigvals, calc_eigvecs def multipole_moments(self, shot=0, optimize=True, polarizability=False): """ Return the multipole moments of the 1st snapshot in Debye and Buckingham units. """ # This line actually runs TINKER if optimize: self.optimize(shot, crit=1e-6) o = self.calltinker("analyze %s.xyz_2 M" % (self.name)) else: self.mol[shot].write('%s.xyz' % self.name, ftype="tinker") o = self.calltinker("analyze %s.xyz M" % (self.name)) # Read the TINKER output. qn = -1 ln = 0 for line in o: s = line.split() if "Dipole X,Y,Z-Components" in line: dipole_dict = OrderedDict(zip(['x','y','z'], [float(i) for i in s[-3:]])) elif "Quadrupole Moment Tensor" in line: qn = ln quadrupole_dict = OrderedDict([('xx',float(s[-3]))]) elif qn > 0 and ln == qn + 1: quadrupole_dict['xy'] = float(s[-3]) quadrupole_dict['yy'] = float(s[-2]) elif qn > 0 and ln == qn + 2: quadrupole_dict['xz'] = float(s[-3]) quadrupole_dict['yz'] = float(s[-2]) quadrupole_dict['zz'] = float(s[-1]) ln += 1 calc_moments = OrderedDict([('dipole', dipole_dict), ('quadrupole', quadrupole_dict)]) if polarizability: if optimize: o = self.calltinker("polarize %s.xyz_2" % (self.name)) else: o = self.calltinker("polarize %s.xyz" % (self.name)) # Read the TINKER output. pn = -1 ln = 0 polarizability_dict = OrderedDict() for line in o: s = line.split() if "Molecular Polarizability Tensor" in line: pn = ln elif pn > 0 and ln == pn + 2: polarizability_dict['xx'] = float(s[-3]) polarizability_dict['yx'] = float(s[-2]) polarizability_dict['zx'] = float(s[-1]) elif pn > 0 and ln == pn + 3: polarizability_dict['xy'] = float(s[-3]) polarizability_dict['yy'] = float(s[-2]) polarizability_dict['zy'] = float(s[-1]) elif pn > 0 and ln == pn + 4: polarizability_dict['xz'] = float(s[-3]) polarizability_dict['yz'] = float(s[-2]) polarizability_dict['zz'] = float(s[-1]) ln += 1 calc_moments['polarizability'] = polarizability_dict os.system("rm -rf *.xyz_* *.[0-9][0-9][0-9]") print polarizability print calc_moments return calc_moments def energy_rmsd(self, shot=0, optimize=True): """ Calculate energy of the selected structure (optionally minimize and return the minimized energy and RMSD). In kcal/mol. """ rmsd = 0.0 # This line actually runs TINKER # xyzfnm = sysname+".xyz" if optimize: E_, rmsd = self.optimize(shot) o = self.calltinker("analyze %s.xyz_2 E" % self.name) #---- # Two equivalent ways to get the RMSD, here for reference. #---- # M1 = Molecule("%s.xyz" % self.name, ftype="tinker") # M2 = Molecule("%s.xyz_2" % self.name, ftype="tinker") # M1 += M2 # rmsd = M1.ref_rmsd(0)[1] #---- # oo = self.calltinker("superpose %s.xyz %s.xyz_2 1 y u n 0" % (self.name, self.name)) # for line in oo: # if "Root Mean Square Distance" in line: # rmsd = float(line.split()[-1]) #---- os.system("rm %s.xyz_2" % self.name) else: o = self.calltinker("analyze %s.xyz E" % self.name) # Read the TINKER output. E = None for line in o: if "Total Potential Energy" in line: E = float(line.split()[-2].replace('D','e')) if E == None: logger.error("Total potential energy wasn't encountered when calling analyze!\n") raise RuntimeError if optimize and abs(E-E_) > 0.1: warn_press_key("Energy from optimize and analyze aren't the same (%.3f vs. %.3f)" % (E, E_)) return E, rmsd def interaction_energy(self, fraga, fragb): """ Calculate the interaction energy for two fragments. """ self.A = TINKER(name="A", mol=self.mol.atom_select(fraga), tinker_key="%s.key" % self.name, tinkerpath=self.tinkerpath) self.B = TINKER(name="B", mol=self.mol.atom_select(fragb), tinker_key="%s.key" % self.name, tinkerpath=self.tinkerpath) # Interaction energy needs to be in kcal/mol. return (self.energy() - self.A.energy() - self.B.energy()) / 4.184 def molecular_dynamics(self, nsteps, timestep, temperature=None, pressure=None, nequil=0, nsave=1000, minimize=True, anisotropic=False, threads=1, verbose=False, **kwargs): """ Method for running a molecular dynamics simulation. Required arguments: nsteps = (int) Number of total time steps timestep = (float) Time step in FEMTOSECONDS temperature = (float) Temperature control (Kelvin) pressure = (float) Pressure control (atmospheres) nequil = (int) Number of additional time steps at the beginning for equilibration nsave = (int) Step interval for saving and printing data minimize = (bool) Perform an energy minimization prior to dynamics threads = (int) Specify how many OpenMP threads to use Returns simulation data: Rhos = (array) Density in kilogram m^-3 Potentials = (array) Potential energies Kinetics = (array) Kinetic energies Volumes = (array) Box volumes Dips = (3xN array) Dipole moments EComps = (dict) Energy components """ md_defs = OrderedDict() md_opts = OrderedDict() # Print out averages only at the end. md_opts["printout"] = nsave md_opts["openmp-threads"] = threads # Langevin dynamics for temperature control. if temperature != None: md_defs["integrator"] = "stochastic" else: md_defs["integrator"] = "beeman" md_opts["thermostat"] = None # Periodic boundary conditions. if self.pbc: md_opts["vdw-correction"] = '' if temperature != None and pressure != None: md_defs["integrator"] = "beeman" md_defs["thermostat"] = "bussi" md_defs["barostat"] = "montecarlo" if anisotropic: md_opts["aniso-pressure"] = '' elif pressure != None: warn_once("Pressure is ignored because temperature is turned off.") else: if pressure != None: warn_once("Pressure is ignored because pbc is set to False.") # Use stochastic dynamics for the gas phase molecule. # If we use the regular integrators it may miss # six degrees of freedom in calculating the kinetic energy. md_opts["barostat"] = None eq_opts = deepcopy(md_opts) if self.pbc and temperature != None and pressure != None: eq_opts["integrator"] = "beeman" eq_opts["thermostat"] = "bussi" eq_opts["barostat"] = "berendsen" if minimize: if verbose: logger.info("Minimizing the energy...") self.optimize(method="bfgs", crit=1) os.system("mv %s.xyz_2 %s.xyz" % (self.name, self.name)) if verbose: logger.info("Done\n") # Run equilibration. if nequil > 0: write_key("%s-eq.key" % self.name, eq_opts, "%s.key" % self.name, md_defs) if verbose: printcool("Running equilibration dynamics", color=0) if self.pbc and pressure != None: self.calltinker("dynamic %s -k %s-eq %i %f %f 4 %f %f" % (self.name, self.name, nequil, timestep, float(nsave*timestep)/1000, temperature, pressure), print_to_screen=verbose) else: self.calltinker("dynamic %s -k %s-eq %i %f %f 2 %f" % (self.name, self.name, nequil, timestep, float(nsave*timestep)/1000, temperature), print_to_screen=verbose) os.system("rm -f %s.arc" % (self.name)) # Run production. if verbose: printcool("Running production dynamics", color=0) write_key("%s-md.key" % self.name, md_opts, "%s.key" % self.name, md_defs) if self.pbc and pressure != None: odyn = self.calltinker("dynamic %s -k %s-md %i %f %f 4 %f %f" % (self.name, self.name, nsteps, timestep, float(nsave*timestep/1000), temperature, pressure), print_to_screen=verbose) else: odyn = self.calltinker("dynamic %s -k %s-md %i %f %f 2 %f" % (self.name, self.name, nsteps, timestep, float(nsave*timestep/1000), temperature), print_to_screen=verbose) # Gather information. os.system("mv %s.arc %s-md.arc" % (self.name, self.name)) self.md_trajectory = "%s-md.arc" % self.name edyn = [] kdyn = [] temps = [] for line in odyn: s = line.split() if 'Current Potential' in line: edyn.append(float(s[2])) if 'Current Kinetic' in line: kdyn.append(float(s[2])) if len(s) > 0 and s[0] == 'Temperature' and s[2] == 'Kelvin': temps.append(float(s[1])) # Potential and kinetic energies converted to kJ/mol. edyn = np.array(edyn) * 4.184 kdyn = np.array(kdyn) * 4.184 temps = np.array(temps) if verbose: logger.info("Post-processing to get the dipole moments\n") oanl = self.calltinker("analyze %s-md.arc" % self.name, stdin="G,E,M", print_to_screen=False) # Read potential energy and dipole from file. eanl = [] dip = [] mass = 0.0 ecomp = OrderedDict() havekeys = set() first_shot = True for ln, line in enumerate(oanl): strip = line.strip() s = line.split() if 'Total System Mass' in line: mass = float(s[-1]) if 'Total Potential Energy : ' in line: eanl.append(float(s[4])) if 'Dipole X,Y,Z-Components :' in line: dip.append([float(s[i]) for i in range(-3,0)]) if first_shot: for key in eckeys: if strip.startswith(key): if key in ecomp: ecomp[key].append(float(s[-2])*4.184) else: ecomp[key] = [float(s[-2])*4.184] if key in havekeys: first_shot = False havekeys.add(key) else: for key in havekeys: if strip.startswith(key): if key in ecomp: ecomp[key].append(float(s[-2])*4.184) else: ecomp[key] = [float(s[-2])*4.184] for key in ecomp: ecomp[key] = np.array(ecomp[key]) ecomp["Potential Energy"] = edyn ecomp["Kinetic Energy"] = kdyn ecomp["Temperature"] = temps ecomp["Total Energy"] = edyn+kdyn # Energies in kilojoules per mole eanl = np.array(eanl) * 4.184 # Dipole moments in debye dip = np.array(dip) # Volume of simulation boxes in cubic nanometers # Conversion factor derived from the following: # In [22]: 1.0 * gram / mole / (1.0 * nanometer)**3 / AVOGADRO_CONSTANT_NA / (kilogram/meter**3) # Out[22]: 1.6605387831627252 conv = 1.6605387831627252 if self.pbc: vol = np.array([BuildLatticeFromLengthsAngles(*[float(j) for j in line.split()]).V \ for line in open("%s-md.arc" % self.name).readlines() \ if (len(line.split()) == 6 and isfloat(line.split()[1]) \ and all([isfloat(i) for i in line.split()[:6]]))]) / 1000 rho = conv * mass / vol else: vol = None rho = None prop_return = OrderedDict() prop_return.update({'Rhos': rho, 'Potentials': edyn, 'Kinetics': kdyn, 'Volumes': vol, 'Dips': dip, 'Ecomps': ecomp}) return prop_return
class AMBER(Engine): """ Engine for carrying out general purpose AMBER calculations. """ def __init__(self, name="amber", **kwargs): ## Keyword args that aren't in this list are filtered out. self.valkwd = ['amberhome', 'pdb', 'mol2', 'frcmod', 'leapcmd'] super(AMBER, self).__init__(name=name, **kwargs) def setopts(self, **kwargs): """ Called by __init__ ; Set AMBER-specific options. """ ## The directory containing TINKER executables (e.g. dynamic) if 'amberhome' in kwargs: self.amberhome = kwargs['amberhome'] if not os.path.exists(os.path.join(self.amberhome, "sander")): warn_press_key("The 'sander' executable indicated by %s doesn't exist! (Check amberhome)" \ % os.path.join(self.amberhome,"sander")) else: warn_once( "The 'amberhome' option was not specified; using default.") if which('sander') == '': warn_press_key( "Please add AMBER executables to the PATH or specify amberhome." ) self.amberhome = os.path.split(which('sander'))[0] with wopen('.quit.leap') as f: print >> f, 'quit' # AMBER search path self.spath = [] for line in self.callamber('tleap -f .quit.leap'): if 'Adding' in line and 'to search path' in line: self.spath.append(line.split('Adding')[1].split()[0]) os.remove('.quit.leap') def readsrc(self, **kwargs): """ Called by __init__ ; read files from the source directory. """ self.leapcmd = onefile(kwargs.get('leapcmd'), 'leap', err=True) self.absleap = os.path.abspath(self.leapcmd) # Name of the molecule, currently just call it a default name. self.mname = 'molecule' if 'mol' in kwargs: self.mol = kwargs['mol'] elif 'coords' in kwargs: crdfile = onefile(kwargs.get('coords'), None, err=True) self.mol = Molecule(crdfile, build_topology=False) # AMBER has certain PDB requirements, so we will absolutely require one. needpdb = True # if hasattr(self, 'mol') and all([i in self.mol.Data.keys() for i in ["chain", "atomname", "resid", "resname", "elem"]]): # needpdb = False # Determine the PDB file name. # If 'pdb' is provided to Engine initialization, it will be used to # copy over topology information (chain, atomname etc.). If mol/coords # is not provided, then it will also provide the coordinates. pdbfnm = onefile(kwargs.get('pdb'), 'pdb' if needpdb else None, err=needpdb) if pdbfnm != None: mpdb = Molecule(pdbfnm, build_topology=False) if hasattr(self, 'mol'): for i in ["chain", "atomname", "resid", "resname", "elem"]: self.mol.Data[i] = mpdb.Data[i] else: self.mol = copy.deepcopy(mpdb) self.abspdb = os.path.abspath(pdbfnm) # Write the PDB that AMBER is going to read in. # This may or may not be identical to the one used to initialize the engine. # self.mol.write('%s.pdb' % self.name) # self.abspdb = os.path.abspath('%s.pdb' % self.name) def callamber(self, command, stdin=None, print_to_screen=False, print_command=False, **kwargs): """ Call TINKER; prepend the amberhome to calling the TINKER program. """ csplit = command.split() # Sometimes the engine changes dirs and the inpcrd/prmtop go missing, so we link it. # Prepend the AMBER path to the program call. prog = os.path.join(self.amberhome, "bin", csplit[0]) csplit[0] = prog # No need to catch exceptions since failed AMBER calculations will return nonzero exit status. o = _exec(' '.join(csplit), stdin=stdin, print_to_screen=print_to_screen, print_command=print_command, rbytes=1024, **kwargs) return o def leap(self, name=None, delcheck=False): if not os.path.exists(self.leapcmd): LinkFile(self.absleap, self.leapcmd) pdb = os.path.basename(self.abspdb) if not os.path.exists(pdb): LinkFile(self.abspdb, pdb) if name == None: name = self.name write_leap(self.leapcmd, mol2=self.mol2, frcmod=self.frcmod, pdb=pdb, prefix=name, spath=self.spath, delcheck=delcheck) self.callamber("tleap -f %s_" % self.leapcmd) def prepare(self, pbc=False, **kwargs): """ Called by __init__ ; prepare the temp directory and figure out the topology. """ if hasattr(self, 'FF'): if not (os.path.exists(self.FF.amber_frcmod) and os.path.exists(self.FF.amber_mol2)): # If the parameter files don't already exist, create them for the purpose of # preparing the engine, but then delete them afterward. prmtmp = True self.FF.make(np.zeros(self.FF.np)) # Currently force field object only allows one mol2 and frcmod file although this can be lifted. self.mol2 = [self.FF.amber_mol2] self.frcmod = [self.FF.amber_frcmod] if 'mol2' in kwargs: logger.error( "FF object is provided, which overrides mol2 keyword argument" ) raise RuntimeError if 'frcmod' in kwargs: logger.error( "FF object is provided, which overrides frcmod keyword argument" ) raise RuntimeError else: prmtmp = False self.mol2 = listfiles(kwargs.get('mol2'), 'mol2', err=True) self.frcmod = listfiles(kwargs.get('frcmod'), 'frcmod', err=True) # Figure out the topology information. self.leap() o = self.callamber("rdparm %s.prmtop" % self.name, stdin="printAtoms\nprintBonds\nexit\n", persist=True, print_error=False) # Once we do this, we don't need the prmtop and inpcrd anymore os.unlink("%s.inpcrd" % self.name) os.unlink("%s.prmtop" % self.name) os.unlink("leap.log") mode = 'None' self.AtomLists = defaultdict(list) G = nx.Graph() for line in o: s = line.split() if 'Atom' in line: mode = 'Atom' elif 'Bond' in line: mode = 'Bond' elif 'RDPARM MENU' in line: continue elif 'EXITING' in line: break elif len(s) == 0: continue elif mode == 'Atom': # Based on parsing lines like these: """ 327: HA -0.01462 1.0 ( 23:HIP ) H1 E 328: CB -0.33212 12.0 ( 23:HIP ) CT 3 329: HB2 0.10773 1.0 ( 23:HIP ) HC E 330: HB3 0.10773 1.0 ( 23:HIP ) HC E 331: CG 0.18240 12.0 ( 23:HIP ) CC B """ # Based on variable width fields. atom_number = int(line.split(':')[0]) atom_name = line.split()[1] atom_charge = float(line.split()[2]) atom_mass = float(line.split()[3]) rnn = line.split('(')[1].split(')')[0].split(':') residue_number = int(rnn[0]) residue_name = rnn[1] atom_type = line.split(')')[1].split()[0] self.AtomLists['Name'].append(atom_name) self.AtomLists['Charge'].append(atom_charge) self.AtomLists['Mass'].append(atom_mass) self.AtomLists['ResidueNumber'].append(residue_number) self.AtomLists['ResidueName'].append(residue_name) # Not sure if this works G.add_node(atom_number) elif mode == 'Bond': a, b = (int(i) for i in (line.split('(')[1].split(')')[0].split(','))) G.add_edge(a, b) self.AtomMask = [a == 'A' for a in self.AtomLists['ParticleType']] # Use networkx to figure out a list of molecule numbers. # gs = nx.connected_component_subgraphs(G) # tmols = [gs[i] for i in np.argsort(np.array([min(g.nodes()) for g in gs]))] # mnodes = [m.nodes() for m in tmols] # self.AtomLists['MoleculeNumber'] = [[i+1 in m for m in mnodes].index(1) for i in range(self.mol.na)] ## Write out the trajectory coordinates to a .mdcrd file. # I also need to write the trajectory if 'boxes' in self.mol.Data.keys(): warn_press_key( "Writing %s-all.crd file with no periodic box information" % self.name) del self.mol.Data['boxes'] if hasattr(self, 'target') and hasattr(self.target, 'shots'): self.qmatoms = target.qmatoms self.mol.write("%s-all.crd" % self.name, select=range(self.target.shots), ftype="mdcrd") else: self.qmatoms = self.mol.na self.mol.write("%s-all.crd" % self.name, ftype="mdcrd") if prmtmp: for f in self.FF.fnms: os.unlink(f) def evaluate_(self, crdin, force=False): """ Utility function for computing energy and forces using AMBER. Inputs: crdin: AMBER .mdcrd file name. force: Switch for parsing the force. (Currently it always calculates the forces.) Outputs: Result: Dictionary containing energies (and optionally) forces. """ force_mdin = """Loop over conformations and compute energy and force (use ioutfnm=1 for netcdf, ntb=0 for no box) &cntrl imin = 5, ntb = 0, cut=9, nstlim = 0, nsnb = 0 / &debugf do_debugf = 1, dumpfrc = 1 / """ with wopen("%s-force.mdin" % self.name) as f: print >> f, force_mdin ## This line actually runs AMBER. self.leap(delcheck=True) self.callamber( "sander -i %s-force.mdin -o %s-force.mdout -p %s.prmtop -c %s.inpcrd -y %s -O" % (self.name, self.name, self.name, self.name, crdin)) ParseMode = 0 Result = {} Energies = [] Forces = [] Force = [] for line in open('forcedump.dat'): line = line.strip() sline = line.split() if ParseMode == 1: if len(sline) == 1 and isfloat(sline[0]): Energies.append(float(sline[0]) * 4.184) ParseMode = 0 if ParseMode == 2: if len(sline) == 3 and all( isfloat(sline[i]) for i in range(3)): Force += [float(sline[i]) * 4.184 * 10 for i in range(3)] if len(Force) == 3 * self.qmatoms: Forces.append(np.array(Force)) Force = [] ParseMode = 0 if line == '0 START of Energies': ParseMode = 1 elif line == '1 Total Force': ParseMode = 2 Result["Energy"] = np.array(Energies[1:]) Result["Force"] = np.array(Forces[1:]) return Result def energy_force_one(self, shot): """ Computes the energy and force using TINKER for one snapshot. """ self.mol[shot].write("%s.xyz" % self.name, ftype="tinker") Result = self.evaluate_("%s.xyz" % self.name, force=True) return np.hstack((Result["Energy"].reshape(-1, 1), Result["Force"])) def energy(self): """ Computes the energy using TINKER over a trajectory. """ if hasattr(self, 'md_trajectory'): x = self.md_trajectory else: x = "%s-all.crd" % self.name self.mol.write(x, ftype="tinker") return self.evaluate_(x)["Energy"] def energy_force(self): """ Computes the energy and force using AMBER over a trajectory. """ if hasattr(self, 'md_trajectory'): x = self.md_trajectory else: x = "%s-all.crd" % self.name Result = self.evaluate_(x, force=True) return np.hstack((Result["Energy"].reshape(-1, 1), Result["Force"])) def energy_dipole(self): """ Computes the energy and dipole using TINKER over a trajectory. """ logger.error( 'Dipole moments are not yet implemented in AMBER interface') raise NotImplementedError if hasattr(self, 'md_trajectory'): x = self.md_trajectory else: x = "%s.xyz" % self.name self.mol.write(x, ftype="tinker") Result = self.evaluate_(x, dipole=True) return np.hstack((Result["Energy"].reshape(-1, 1), Result["Dipole"])) def optimize(self, shot=0, method="newton", crit=1e-4): """ Optimize the geometry and align the optimized geometry to the starting geometry. """ logger.error( 'Geometry optimizations are not yet implemented in AMBER interface' ) raise NotImplementedError # Code from tinkerio.py if os.path.exists('%s.xyz_2' % self.name): os.unlink('%s.xyz_2' % self.name) self.mol[shot].write('%s.xyz' % self.name, ftype="tinker") if method == "newton": if self.rigid: optprog = "optrigid" else: optprog = "optimize" elif method == "bfgs": if self.rigid: optprog = "minrigid" else: optprog = "minimize" o = self.calltinker("%s %s.xyz %f" % (optprog, self.name, crit)) # Silently align the optimized geometry. M12 = Molecule("%s.xyz" % self.name, ftype="tinker") + Molecule( "%s.xyz_2" % self.name, ftype="tinker") if not self.pbc: M12.align(center=False) M12[1].write("%s.xyz_2" % self.name, ftype="tinker") rmsd = M12.ref_rmsd(0)[1] cnvgd = 0 mode = 0 for line in o: s = line.split() if len(s) == 0: continue if "Optimally Conditioned Variable Metric Optimization" in line: mode = 1 if "Limited Memory BFGS Quasi-Newton Optimization" in line: mode = 1 if mode == 1 and isint(s[0]): mode = 2 if mode == 2: if isint(s[0]): E = float(s[1]) else: mode = 0 if "Normal Termination" in line: cnvgd = 1 if not cnvgd: for line in o: logger.info(str(line) + '\n') logger.info( "The minimization did not converge in the geometry optimization - printout is above.\n" ) return E, rmsd def normal_modes(self, shot=0, optimize=True): logger.error('Normal modes are not yet implemented in AMBER interface') raise NotImplementedError # Copied from tinkerio.py if optimize: self.optimize(shot, crit=1e-6) o = self.calltinker("vibrate %s.xyz_2 a" % (self.name)) else: warn_once("Asking for normal modes without geometry optimization?") self.mol[shot].write('%s.xyz' % self.name, ftype="tinker") o = self.calltinker("vibrate %s.xyz a" % (self.name)) # Read the TINKER output. The vibrational frequencies are ordered. # The six modes with frequencies closest to zero are ignored readev = False calc_eigvals = [] calc_eigvecs = [] for line in o: s = line.split() if "Vibrational Normal Mode" in line: freq = float(s[-2]) readev = False calc_eigvals.append(freq) calc_eigvecs.append([]) elif "Atom" in line and "Delta X" in line: readev = True elif readev and len(s) == 4 and all( [isint(s[0]), isfloat(s[1]), isfloat(s[2]), isfloat(s[3])]): calc_eigvecs[-1].append([float(i) for i in s[1:]]) calc_eigvals = np.array(calc_eigvals) calc_eigvecs = np.array(calc_eigvecs) # Sort by frequency absolute value and discard the six that are closest to zero calc_eigvecs = calc_eigvecs[np.argsort(np.abs(calc_eigvals))][6:] calc_eigvals = calc_eigvals[np.argsort(np.abs(calc_eigvals))][6:] # Sort again by frequency calc_eigvecs = calc_eigvecs[np.argsort(calc_eigvals)] calc_eigvals = calc_eigvals[np.argsort(calc_eigvals)] os.system("rm -rf *.xyz_* *.[0-9][0-9][0-9]") return calc_eigvals, calc_eigvecs def multipole_moments(self, shot=0, optimize=True, polarizability=False): logger.error( 'Multipole moments are not yet implemented in AMBER interface') raise NotImplementedError """ Return the multipole moments of the 1st snapshot in Debye and Buckingham units. """ # This line actually runs TINKER if optimize: self.optimize(shot, crit=1e-6) o = self.calltinker("analyze %s.xyz_2 M" % (self.name)) else: self.mol[shot].write('%s.xyz' % self.name, ftype="tinker") o = self.calltinker("analyze %s.xyz M" % (self.name)) # Read the TINKER output. qn = -1 ln = 0 for line in o: s = line.split() if "Dipole X,Y,Z-Components" in line: dipole_dict = OrderedDict( zip(['x', 'y', 'z'], [float(i) for i in s[-3:]])) elif "Quadrupole Moment Tensor" in line: qn = ln quadrupole_dict = OrderedDict([('xx', float(s[-3]))]) elif qn > 0 and ln == qn + 1: quadrupole_dict['xy'] = float(s[-3]) quadrupole_dict['yy'] = float(s[-2]) elif qn > 0 and ln == qn + 2: quadrupole_dict['xz'] = float(s[-3]) quadrupole_dict['yz'] = float(s[-2]) quadrupole_dict['zz'] = float(s[-1]) ln += 1 calc_moments = OrderedDict([('dipole', dipole_dict), ('quadrupole', quadrupole_dict)]) if polarizability: if optimize: o = self.calltinker("polarize %s.xyz_2" % (self.name)) else: o = self.calltinker("polarize %s.xyz" % (self.name)) # Read the TINKER output. pn = -1 ln = 0 polarizability_dict = OrderedDict() for line in o: s = line.split() if "Total Polarizability Tensor" in line: pn = ln elif pn > 0 and ln == pn + 2: polarizability_dict['xx'] = float(s[-3]) polarizability_dict['yx'] = float(s[-2]) polarizability_dict['zx'] = float(s[-1]) elif pn > 0 and ln == pn + 3: polarizability_dict['xy'] = float(s[-3]) polarizability_dict['yy'] = float(s[-2]) polarizability_dict['zy'] = float(s[-1]) elif pn > 0 and ln == pn + 4: polarizability_dict['xz'] = float(s[-3]) polarizability_dict['yz'] = float(s[-2]) polarizability_dict['zz'] = float(s[-1]) ln += 1 calc_moments['polarizability'] = polarizability_dict os.system("rm -rf *.xyz_* *.[0-9][0-9][0-9]") return calc_moments def energy_rmsd(self, shot=0, optimize=True): """ Calculate energy of the selected structure (optionally minimize and return the minimized energy and RMSD). In kcal/mol. """ logger.error( 'Geometry optimization is not yet implemented in AMBER interface') raise NotImplementedError rmsd = 0.0 # This line actually runs TINKER # xyzfnm = sysname+".xyz" if optimize: E_, rmsd = self.optimize(shot) o = self.calltinker("analyze %s.xyz_2 E" % self.name) #---- # Two equivalent ways to get the RMSD, here for reference. #---- # M1 = Molecule("%s.xyz" % self.name, ftype="tinker") # M2 = Molecule("%s.xyz_2" % self.name, ftype="tinker") # M1 += M2 # rmsd = M1.ref_rmsd(0)[1] #---- # oo = self.calltinker("superpose %s.xyz %s.xyz_2 1 y u n 0" % (self.name, self.name)) # for line in oo: # if "Root Mean Square Distance" in line: # rmsd = float(line.split()[-1]) #---- os.system("rm %s.xyz_2" % self.name) else: o = self.calltinker("analyze %s.xyz E" % self.name) # Read the TINKER output. E = None for line in o: if "Total Potential Energy" in line: E = float(line.split()[-2].replace('D', 'e')) if E == None: logger.error( "Total potential energy wasn't encountered when calling analyze!\n" ) raise RuntimeError if optimize and abs(E - E_) > 0.1: warn_press_key( "Energy from optimize and analyze aren't the same (%.3f vs. %.3f)" % (E, E_)) return E, rmsd def interaction_energy(self, fraga, fragb): """ Calculate the interaction energy for two fragments. """ logger.error( 'Interaction energy is not yet implemented in AMBER interface') raise NotImplementedError self.A = TINKER(name="A", mol=self.mol.atom_select(fraga), tinker_key="%s.key" % self.name, tinkerpath=self.tinkerpath) self.B = TINKER(name="B", mol=self.mol.atom_select(fragb), tinker_key="%s.key" % self.name, tinkerpath=self.tinkerpath) # Interaction energy needs to be in kcal/mol. return (self.energy() - self.A.energy() - self.B.energy()) / 4.184 def molecular_dynamics(self, nsteps, timestep, temperature=None, pressure=None, nequil=0, nsave=1000, minimize=True, anisotropic=False, threads=1, verbose=False, **kwargs): """ Method for running a molecular dynamics simulation. Required arguments: nsteps = (int) Number of total time steps timestep = (float) Time step in FEMTOSECONDS temperature = (float) Temperature control (Kelvin) pressure = (float) Pressure control (atmospheres) nequil = (int) Number of additional time steps at the beginning for equilibration nsave = (int) Step interval for saving and printing data minimize = (bool) Perform an energy minimization prior to dynamics threads = (int) Specify how many OpenMP threads to use Returns simulation data: Rhos = (array) Density in kilogram m^-3 Potentials = (array) Potential energies Kinetics = (array) Kinetic energies Volumes = (array) Box volumes Dips = (3xN array) Dipole moments EComps = (dict) Energy components """ logger.error( 'Molecular dynamics not yet implemented in AMBER interface') raise NotImplementedError md_defs = OrderedDict() md_opts = OrderedDict() # Print out averages only at the end. md_opts["printout"] = nsave md_opts["openmp-threads"] = threads # Langevin dynamics for temperature control. if temperature != None: md_defs["integrator"] = "stochastic" else: md_defs["integrator"] = "beeman" md_opts["thermostat"] = None # Periodic boundary conditions. if self.pbc: md_opts["vdw-correction"] = '' if temperature != None and pressure != None: md_defs["integrator"] = "beeman" md_defs["thermostat"] = "bussi" md_defs["barostat"] = "montecarlo" if anisotropic: md_opts["aniso-pressure"] = '' elif pressure != None: warn_once( "Pressure is ignored because temperature is turned off.") else: if pressure != None: warn_once("Pressure is ignored because pbc is set to False.") # Use stochastic dynamics for the gas phase molecule. # If we use the regular integrators it may miss # six degrees of freedom in calculating the kinetic energy. md_opts["barostat"] = None eq_opts = deepcopy(md_opts) if self.pbc and temperature != None and pressure != None: eq_opts["integrator"] = "beeman" eq_opts["thermostat"] = "bussi" eq_opts["barostat"] = "berendsen" if minimize: if verbose: logger.info("Minimizing the energy...") self.optimize(method="bfgs", crit=1) os.system("mv %s.xyz_2 %s.xyz" % (self.name, self.name)) if verbose: logger.info("Done\n") # Run equilibration. if nequil > 0: write_key("%s-eq.key" % self.name, eq_opts, "%s.key" % self.name, md_defs) if verbose: printcool("Running equilibration dynamics", color=0) if self.pbc and pressure != None: self.calltinker( "dynamic %s -k %s-eq %i %f %f 4 %f %f" % (self.name, self.name, nequil, timestep, float(nsave * timestep) / 1000, temperature, pressure), print_to_screen=verbose) else: self.calltinker("dynamic %s -k %s-eq %i %f %f 2 %f" % (self.name, self.name, nequil, timestep, float(nsave * timestep) / 1000, temperature), print_to_screen=verbose) os.system("rm -f %s.arc" % (self.name)) # Run production. if verbose: printcool("Running production dynamics", color=0) write_key("%s-md.key" % self.name, md_opts, "%s.key" % self.name, md_defs) if self.pbc and pressure != None: odyn = self.calltinker( "dynamic %s -k %s-md %i %f %f 4 %f %f" % (self.name, self.name, nsteps, timestep, float(nsave * timestep / 1000), temperature, pressure), print_to_screen=verbose) else: odyn = self.calltinker( "dynamic %s -k %s-md %i %f %f 2 %f" % (self.name, self.name, nsteps, timestep, float(nsave * timestep / 1000), temperature), print_to_screen=verbose) # Gather information. os.system("mv %s.arc %s-md.arc" % (self.name, self.name)) self.md_trajectory = "%s-md.arc" % self.name edyn = [] kdyn = [] temps = [] for line in odyn: s = line.split() if 'Current Potential' in line: edyn.append(float(s[2])) if 'Current Kinetic' in line: kdyn.append(float(s[2])) if len(s) > 0 and s[0] == 'Temperature' and s[2] == 'Kelvin': temps.append(float(s[1])) # Potential and kinetic energies converted to kJ/mol. edyn = np.array(edyn) * 4.184 kdyn = np.array(kdyn) * 4.184 temps = np.array(temps) if verbose: logger.info("Post-processing to get the dipole moments\n") oanl = self.calltinker("analyze %s-md.arc" % self.name, stdin="G,E,M", print_to_screen=False) # Read potential energy and dipole from file. eanl = [] dip = [] mass = 0.0 ecomp = OrderedDict() havekeys = set() first_shot = True for ln, line in enumerate(oanl): strip = line.strip() s = line.split() if 'Total System Mass' in line: mass = float(s[-1]) if 'Total Potential Energy : ' in line: eanl.append(float(s[4])) if 'Dipole X,Y,Z-Components :' in line: dip.append([float(s[i]) for i in range(-3, 0)]) if first_shot: for key in eckeys: if strip.startswith(key): if key in ecomp: ecomp[key].append(float(s[-2]) * 4.184) else: ecomp[key] = [float(s[-2]) * 4.184] if key in havekeys: first_shot = False havekeys.add(key) else: for key in havekeys: if strip.startswith(key): if key in ecomp: ecomp[key].append(float(s[-2]) * 4.184) else: ecomp[key] = [float(s[-2]) * 4.184] for key in ecomp: ecomp[key] = np.array(ecomp[key]) ecomp["Potential Energy"] = edyn ecomp["Kinetic Energy"] = kdyn ecomp["Temperature"] = temps ecomp["Total Energy"] = edyn + kdyn # Energies in kilojoules per mole eanl = np.array(eanl) * 4.184 # Dipole moments in debye dip = np.array(dip) # Volume of simulation boxes in cubic nanometers # Conversion factor derived from the following: # In [22]: 1.0 * gram / mole / (1.0 * nanometer)**3 / AVOGADRO_CONSTANT_NA / (kilogram/meter**3) # Out[22]: 1.6605387831627252 conv = 1.6605387831627252 if self.pbc: vol = np.array([BuildLatticeFromLengthsAngles(*[float(j) for j in line.split()]).V \ for line in open("%s-md.arc" % self.name).readlines() \ if (len(line.split()) == 6 and isfloat(line.split()[1]) \ and all([isfloat(i) for i in line.split()[:6]]))]) / 1000 rho = conv * mass / vol else: vol = None rho = None prop_return = OrderedDict() prop_return.update({ 'Rhos': rho, 'Potentials': edyn, 'Kinetics': kdyn, 'Volumes': vol, 'Dips': dip, 'Ecomps': ecomp }) return prop_return