def QChem_Dielectric_Energy(fnm,wq): QCIn = Molecule(fnm) for i in range(QCIn.na): # Q-Chem crashes if it doesn't recognize the chemical element if QCIn.Data['elem'][i] in ['M','L']: QCIn.Data['elem'][i] = 'He' CalcDir=os.path.splitext(fnm)[0]+".d" GoInto(CalcDir) digits = len(str(QCIn.ns)) for i in range(QCIn.ns): sdir = "%%0%ii" % digits % i GoInto(sdir) QCIn.write("qchem.in",select=i) queue_up(wq,"qchem40 qchem.in qchem.out",input_files=["qchem.in"],output_files=["qchem.out"],verbose=False) Leave(sdir) wq_wait(wq,verbose=False) PCM_Energies = [] for i in range(QCIn.ns): sdir = "%%0%ii" % digits % i GoInto(sdir) for line in open("qchem.out"): if "PCM electrostatic energy" in line: PCM_Energies.append(float(line.split()[-2])) Leave(sdir) Leave(CalcDir) return np.array(PCM_Energies) * 2625.5
def main(): M = Molecule(argv[1]) tempfnm = argv[3] if len(argv) >= 4 else None if tempfnm != None: M.add_quantum(tempfnm) print M.Data.keys() M.write(argv[2])
def write_fb_target_abinitio(self, records): """ Write a list of {'energy': xxx, 'molecule': xxx, 'name': xxx} records into a new target folder """ # prepare folder for writing target_name = 'abinitio_bond_angles' target_folder = os.path.join(self.out_folder, target_name) os.mkdir(target_folder) os.chdir(target_folder) # load data into a fb Molecule out_m = Molecule() out_m.elem = self.m.elem.copy() out_m.xyzs = [] out_m.qm_energies = [] out_m.comms = [] for record in records: qcmol = record['molecule'] energy = record['energy'] name = record.get('name', 'created by FBTargetBuilder') m = self.qc_molecule_to_fb_molecule(qcmol) assert m.elem == out_m.elem, 'Elements list of resulting qcmol is not consistent with self.m' # append geometry out_m.xyzs.append(m.xyzs[0]) # append energy out_m.qm_energies.append(energy) # append name out_m.comms.append(name) # write output print( f"Writing {len(records)} frames into targets/abinitio_bond_angles/traj.xyz" ) out_m.write('traj.xyz') print( f"Writing {len(records)} frames into targets/abinitio_bond_angles/qdata.txt" ) out_m.write('qdata.txt')
def write_torsion_target(procedure, target_name, removing_unreal=False): """ Fetch data from a QCArchive procedure and write as a ForceBalance Target folder Input: ------ procedure: qcfractal.interface.orm.torsiondrive_orm.TorsionDriveORM An object that represents a computation target_name: string Folder name to write the ForceBalance target removing_unreal: bool, default False If true, remove the atoms that are not "real" """ # check if the target folder name exists if os.path.exists(target_name): raise RuntimeError(f"Target folder {target_name} already exists") # form a Molecule object from the first torsion grid data grid_molecules = procedure.final_molecules() grid_energies = procedure.final_energies() data = next(iter(grid_molecules.values())) elem = data['symbols'] bonds = [(b[0], b[1]) for b in data['connectivity']] # remove the "unreal" atoms? removing_unreal = removing_unreal and (not all(data['real'])) if removing_unreal: elem = [e for e, f in zip(elem, data['real']) if f is True] bonds = [(b[0], b[1]) for b in bonds if data['real'][b[0]] and data['real'][b[1]]] # form the Molecule object m = Molecule() m.Data = { 'elem': elem, 'bonds': bonds, 'name': data['name'], 'xyzs': [], 'qm_energies': [], 'comms': [] } # load all frames for grid_id, data in grid_molecules.items(): geo = np.array(data['geometry']).reshape(-1, 3) * bohr2ang if removing_unreal: geo = geo[data['real']] m.Data['xyzs'].append(geo) energy = grid_energies[grid_id] m.Data['qm_energies'].append(energy) m.Data['comms'].append(f'{data["name"]} at torsion grid {grid_id}') # write the data os.mkdir(target_name) os.chdir(target_name) m.write('qdata.txt') m.write('geo.xyz') os.chdir('..')
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 main(): M = Molecule(opts.coords) if len(M.molecules) != 1: raise RuntimeError( 'Input coordinates must be a single contiguous molecule') if opts.phi1 == None: raise RuntimeError( 'phi1 (the first quartet of atoms) must be provided') xyzout = [] commout = [] xyzcsh = [] commcsh = [] if opts.phi2 != None: # Two dimensional scan for inc1 in range(0, 360, opts.scan): for inc2 in range(0, 360, opts.scan): xyzrot, clash = get_rotated_xyz(M, [opts.phi1, opts.phi2], [inc1, inc2]) print(inc1, inc2, "Clash" if clash else "Ok") comm = "Dihedrals %s, %s set to %i, %i" % (str( opts.phi1), str(opts.phi2), inc1, inc2) if clash: xyzcsh.append(xyzrot.copy()) commcsh.append(comm) else: xyzout.append(xyzrot.copy()) commout.append(comm) else: # One dimensional scan for inc1 in range(0, 360, opts.scan): xyzrot, clash = get_rotated_xyz(M, [opts.phi1], [inc1]) print(inc1, "Clash" if clash else "Ok") comm = "Dihedral %s set to %i" % (str(opts.phi1), inc1) if clash: xyzcsh.append(xyzrot.copy()) commcsh.append(comm) else: xyzout.append(xyzrot.copy()) commout.append(comm) if len(xyzout) > 0: M.xyzs = xyzout M.comms = commout M.write( os.path.splitext(opts.coords)[0] + "_out" + os.path.splitext(opts.coords)[1]) if len(xyzcsh) > 0: M.xyzs = xyzcsh M.comms = commcsh M.write( os.path.splitext(opts.coords)[0] + "_clash" + os.path.splitext(opts.coords)[1])
def calc_new(self, coords, dirname): if not os.path.exists(dirname): os.makedirs(dirname) Gro = Molecule("conf.gro") Gro.xyzs[0] = coords.reshape(-1, 3) * 0.529 cwd = os.getcwd() shutil.copy2("topol.top", dirname) shutil.copy2("shot.mdp", dirname) os.chdir(dirname) Gro.write("coords.gro") G = GMX(coords="coords.gro", gmx_top="topol.top", gmx_mdp="shot.mdp") EF = G.energy_force() Energy = EF[0, 0] / eqcgmx Gradient = EF[0, 1:] / fqcgmx os.chdir(cwd) return Energy, Gradient
def main(): # create molecule m = Molecule(opts.xyz_file) # write to pdb m.write(tf) # read into mdtraj p = mdtraj.load(tf) # get list of hydrogens to remove c_ndx = [i.index for i in p.topology.atoms if i.name in c_strip] tail_atms = [y for (x, y) in m.Data['bonds'] if x in c_ndx] tail_h = [i for i in tail_atms if i not in c_ndx] # throw out some hydrogens atoms_to_keep = [b.index for b in p.topology.atoms if b.index not in tail_h] p.restrict_atoms(atoms_to_keep) p.save(op) # hella circular x = Molecule(op) x.write(opts.out_gro)
def main(): M = Molecule(opts.coords) if len(M.molecules) != 1: raise RuntimeError('Input coordinates must be a single contiguous molecule') if opts.phi1 == None: raise RuntimeError('phi1 (the first quartet of atoms) must be provided') xyzout = [] commout = [] xyzcsh = [] commcsh = [] if opts.phi2 != None: # Two dimensional scan for inc1 in range(0, 360, opts.scan): for inc2 in range(0, 360, opts.scan): xyzrot, clash = get_rotated_xyz(M, [opts.phi1, opts.phi2], [inc1, inc2]) print(inc1, inc2, "Clash" if clash else "Ok") comm = "Dihedrals %s, %s set to %i, %i" % (str(opts.phi1), str(opts.phi2), inc1, inc2) if clash: xyzcsh.append(xyzrot.copy()) commcsh.append(comm) else: xyzout.append(xyzrot.copy()) commout.append(comm) else: # One dimensional scan for inc1 in range(0, 360, opts.scan): xyzrot, clash = get_rotated_xyz(M, [opts.phi1], [inc1]) print(inc1, "Clash" if clash else "Ok") comm = "Dihedral %s set to %i" % (str(opts.phi1), inc1) if clash: xyzcsh.append(xyzrot.copy()) commcsh.append(comm) else: xyzout.append(xyzrot.copy()) commout.append(comm) if len(xyzout) > 0: M.xyzs = xyzout M.comms = commout M.write(os.path.splitext(opts.coords)[0]+"_out"+os.path.splitext(opts.coords)[1]) if len(xyzcsh) > 0: M.xyzs = xyzcsh M.comms = commcsh M.write(os.path.splitext(opts.coords)[0]+"_clash"+os.path.splitext(opts.coords)[1])
def write_molecule_files(molecule, name, test_ff=None): qcjson_mol = molecule.dict(encoding='json') oemol = cmiles.utils.load_molecule(qcjson_mol) # write the mol2 file and xyz file for ext in ['xyz', 'mol2']: ofs.open(f'{name}.{ext}') oechem.OEWriteMolecule(ofs, oemol) ofs.close() success = True err_msg = "" # test if bonds changed bond_set = {(a, b) for a, b, v in molecule.connectivity} if not check_connectivity(bond_set, f'{name}.mol2'): success = False err_msg = "Bonds changed after rebuild" if not os.path.exists('../err_bonds_changed'): os.mkdir('../err_bonds_changed') shutil.move(f'{name}.mol2', f'../err_bonds_changed/{name}.mol2') os.remove(f'{name}.xyz') # test if can be created by the test_ff if success == True and test_ff != None: from openforcefield.topology import Molecule as Off_Molecule from openforcefield.topology import Topology as Off_Topology try: off_molecule = Off_Molecule.from_file(f'{name}.mol2') off_topology = Off_Topology.from_molecules(off_molecule) test_ff.create_openmm_system(off_topology) except Exception as e: success = False err_msg = str(e) if not os.path.exists('../error_mol2s'): os.mkdir('../error_mol2s') shutil.move(f'{name}.mol2', f'../error_mol2s/{name}.mol2') os.remove(f'{name}.xyz') if success == True: # use ForceBalance Molecule to generate pdb file # because the oechem.OEWriteMolecule will mess up with atom indices fbmol = Molecule(f'{name}.mol2') fbmol.write(f'{name}.pdb') return success, err_msg
def write_molecule_files(molecule_data_list): molecule, e0 = molecule_data_list[0] qcjson_mol = molecule.dict(encoding='json') oemol = cmiles.utils.load_molecule(qcjson_mol) # write the mol2 file using oechem ofs.open(f'input.mol2') oechem.OEWriteMolecule(ofs, oemol) ofs.close() # write the pdb file using ForceBalance Molecule fbmol = Molecule(f'input.mol2') fbmol.write(f'conf.pdb') # write xyz file using a new ForceBalance Molecule object m = Molecule() m.elem = [Elements[i] for i in molecule.atomic_numbers] m.xyzs = [] m.qm_energies = [] for mol, e in molecule_data_list: m.xyzs.append(mol.geometry * bohr2ang) m.qm_energies.append(e) m.write("coords.xyz") # write qdata.txt file with coords and energies m.write('qdata.txt')
import os, sys, re import numpy as np from forcebalance.molecule import Molecule from forcebalance.readfrq import read_frq_psi # Psi4 output file. psiout = sys.argv[1] # Mode number, starting from 1. modenum = int(sys.argv[2]) frqs, modes, elem, xyz = read_frq_psi(psiout) M = Molecule() M.elem = elem[:] M.xyzs = [] xmode = modes[modenum - 1] xmode /= (np.linalg.norm(xmode)/np.sqrt(M.na)) xmode *= 0.3 # Reasonable vibrational amplitude spac = np.linspace(0, 1, 101) disp = np.concatenate((spac, spac[::-1][1:], -1*spac[1:], -1*spac[::-1][1:-1])) for i in disp: M.xyzs.append(xyz+i*xmode) M.comms = ['Vibrational Mode %i Frequency %.3f Displacement %.3f' % (modenum, frqs[modenum-1], disp[i]*(np.linalg.norm(xmode)/np.sqrt(M.na))) for i in range(len(M))] M.write(os.path.splitext(psiout)[0]+'.mode%03i.xyz' % modenum)
def gen_tid_calculated_molecules_list(torsiondrive_data, forcefield, verbose=False): # gen dictionary with keys, including all tids in the input forcefield ff_torsion_param_list = forcefield.get_parameter_handler( 'ProperTorsions').parameters tid_calculated_molecules_list = {} molecules_list_dict_from_td = defaultdict = {} for torsion_param in ff_torsion_param_list: tid_calculated_molecules_list[torsion_param.id] = [] if os.path.exists('tmp'): shutil.rmtree('tmp') os.mkdir('tmp') os.chdir('tmp') for entry_index, td_data in torsiondrive_data.items(): # pick a single initial molecule qcmol = td_data['initial_molecules'][0] # write input.mol2 file qcjson_mol = qcmol.dict(encoding='json') oemol = cmiles.utils.load_molecule(qcjson_mol) ofs.open(f'input.mol2') oechem.OEWriteMolecule(ofs, oemol) ofs.close() # test mol2 file success, msg, molecule_labels = test_ff_mol2(forcefield, 'input.mol2') if not success: if verbose == True: print( 'Error occured while testing input.mol2. Excluded in tid_calculated_molecules_list. ' ) continue # check if the torsion scan contains one or more conformers forming strong internal H bonds if success: # write conf.pdb file fbmol = FBMolecule(f'input.mol2') # list of grid ids sorted sorted_grid_ids = sorted(td_data['final_molecules'].keys()) # write scan.xyz target_mol = FBMolecule() target_mol.elem = fbmol.elem target_mol.xyzs = [] target_mol.qm_energies = [] target_mol.qm_grads = [] for grid_id in sorted_grid_ids: grid_qc_mol = td_data['final_molecules'][grid_id] # convert geometry unit Bohr -> Angstrom geo = grid_qc_mol.geometry * 0.529177 target_mol.xyzs.append(geo) # add energy and gradient target_mol.qm_energies.append( td_data['final_energies'][grid_id]) target_mol.qm_grads.append(td_data['final_gradients'][grid_id]) target_mol.write('scan.xyz') no_hbonds = check_Hbond(scan_fnm='scan.xyz', top_fnm='input.mol2') if not no_hbonds: if verbose == True: print( 'Internal hydrogen bond detacted. Excluded in tid_calculated_molecules_list. ' ) success = False if success: mol_index = td_data['attributes']["canonical_isomeric_smiles"] indices = td_data['keywords']['dihedrals'][0] tid = molecule_labels['ProperTorsions'][tuple(indices)].id # qcschema_molecules = [qcmol.dict(encoding='json') for qcmol in td_data['initial_molecules']] tid_calculated_molecules_list[tid].append({ 'mol_index': mol_index, 'indices': indices }) qcschema_molecules = [] for qcmol in td_data['initial_molecules']: j_dict = qcmol.dict(encoding='json') qcschema_molecule = { 'symbols': j_dict['symbols'], 'geometry': j_dict['geometry'], 'connectivity': j_dict['connectivity'], 'molecular_charge': j_dict['molecular_charge'], 'molecular_multiplicity': j_dict['molecular_multiplicity'] } qcschema_molecules.append(qcschema_molecule) molecules_list_dict_from_td[mol_index] = qcschema_molecules print("\n## Available torsion scans from QCArchive ##\n" + '-' * 90) print(f"{'idx':<7} {'tid':7s} {'Number of torsion scans'}") for idx, (tid, molecules_list) in enumerate( tid_calculated_molecules_list.items()): if len(molecules_list) > 0: print(f'{idx:<7} {tid:7s} {len(molecules_list)}') print('-' * 90) os.chdir('..') shutil.rmtree('tmp') return tid_calculated_molecules_list, molecules_list_dict_from_td
def main(): topfnm = argv[3] if len(argv) >= 4 else None M = Molecule(argv[1], top=topfnm) M.write(argv[2])
def GenerateBox(pdbin, pdbout, box, nmol, tries): """ Call genbox. (Confirmed working with Gromacs version 4.6.7 and 5.1.4). Mainly checks whether genbox ran correctly. Parameters ---------- pdbin : str Name of input PDB file containing a single molecule. pdbout : str Name of output PDB file containing solvent box. box : float Solvent box size, should be determined previously. nmol : int Number of molecules to go into the solvent box tries : int Parameter for genbox to try inserting each molecule (tries) times Returns ------- None If successful, produces "pdbout" containing solvent box. """ if which('gmx'): gmxcmd = 'gmx insert-molecules' elif which('genbox'): gmxcmd = 'genbox' else: raise RuntimeError( 'gmx and/or genbox not in PATH. Please source Gromacs environment variables.' ) fout = open('genbox.out', 'w') ferr = open('genbox.err', 'w') subprocess.Popen( '%s -ci %s -o genbox.pdb -box %.3f %.3f %.3f -nmol %i -try %i' % (gmxcmd, pdbin, box, box, box, nmol, tries), shell=True, stdout=fout, stderr=ferr) fout.close() ferr.close() t0 = time.time() print("Running %s to create a solvent box..." % gmxcmd) print("Time elapsed: % .3f seconds" % (time.time() - t0)) nmol_out = 0 for line in open('genbox.err').readlines(): if 'Output configuration contains' in line: nmol_out = int(line.split()[-2]) if nmol_out == 0: raise RuntimeError('genbox failed to produce an output configuration') elif nmol_out != nmol: raise RuntimeError( 'genbox failed to create a box with %i molecules (actual %i); ' 'please retry with increased box size or number of tries' % (nmol, nmol_out)) else: # genbox throws away the CONECT records in the PDB, this operation adds them back. M1 = Molecule(pdbin, build_topology=False) M = Molecule('genbox.pdb', build_topology=False) solventbox_bonds = [] # Loop over the number of molecules in the solvent box for i in range(nmol): for j in M1.bonds: # Add the bonds for molecule number "i" in the solvent box solventbox_bonds.append((j[0] + i * M1.na, j[1] + i * M1.na)) M.bonds = solventbox_bonds M.write(pdbout) print( "-=# Output #=- Created %s containing solvent box with %i molecules and length %.3f" % (pdbout, nmol, box))
espval.append(float(s[3])) elif len(espxyz) > 0: # After reading in a block of ESPs, don't read any more. ESPMode = -1 if line.strip().startswith("Geometry (in Angstrom)"): XMode = 1 EMode = len(elem) == 0 if 'Electrostatic Potential' in line.strip() and ESPMode == 0: ESPMode = 1 if len(xyzs) == 0: raise Exception('%s has length zero' % psiout) return xyzs, elem, espxyz, espval xyzs, elem, espxyz, espval = read_psi_xyzesp(sys.argv[1]) M = Molecule() M.xyzs = xyzs M.elem = elem M.write('%s.xyz' % os.path.splitext(sys.argv[1])[0]) EM = Molecule() EM.xyzs = [np.array(espxyz) * 0.52917721092] EM.elem = ['H' for i in range(len(espxyz))] EM.write('%s.espx' % os.path.splitext(sys.argv[1])[0], ftype="xyz") M.qm_espxyzs = EM.xyzs M.qm_espvals = [np.array(espval)] M.write("qdata.txt") np.savetxt('%s.esp' % os.path.splitext(sys.argv[1])[0], espval)
from forcebalance.readfrq import read_frq_gen # Frequency output file. fout = sys.argv[1] # Mode number, starting from 1. modenum = int(sys.argv[2]) if modenum == 0: raise RuntimeError("Start mode number from one, please") frqs, modes, intens, elem, xyz = read_frq_gen(fout) M = Molecule() M.elem = elem[:] M.xyzs = [] xmode = modes[modenum - 1] xmode /= (np.linalg.norm(xmode)/np.sqrt(M.na)) xmode *= 0.3 # Reasonable vibrational amplitude spac = np.linspace(0, 1, 101) disp = np.concatenate((spac, spac[::-1][1:], -1*spac[1:], -1*spac[::-1][1:-1])) for i in disp: M.xyzs.append(xyz+i*xmode.reshape(-1,3)) M.comms = ['Vibrational Mode %i Frequency %.3f Displacement %.3f' % (modenum, frqs[modenum-1], disp[i]*(np.linalg.norm(xmode)/np.sqrt(M.na))) for i in range(len(M))] M.write(os.path.splitext(fout)[0]+'.mode%03i.xyz' % modenum)
from forcebalance.molecule import Molecule from forcebalance.nifty import _exec import os np="" if "CORES_PER_WORKER" in os.environ and int(os.environ["CORES_PER_WORKER"]) > 1: np=" -np %i" % int(os.environ["CORES_PER_WORKER"]) _exec("touch opt.xyz") _exec("touch energy.txt") _exec("rm -f qchem.out.prev") _exec("touch qchem.out.prev") qcin = Molecule("qchem.in", ftype="qcin") qcin.edit_qcrems({'geom_opt_max_cycles':'100'}) qcin.write("qchem.in") _exec("qchem42 %s qchem.in qchem.out &> qchem.err" % np) def special_criterion(): mk = 0 mx = 0 Cnvgd = {} for ln, line in enumerate(open("qchem.out").readlines()): if "Maximum optimization cycles reached" in line: mx = 1 if "Maximum Tolerance Cnvgd?" in line: mk = ln if mk > 0 and ln > mk and ln <= mk+3: s = line.split() try: Cnvgd[s[0]] = float(s[-3])
def make_fb_targets(): result_mol_folders = [ os.path.join(results_folder, f) for f in os.listdir(results_folder) if os.path.isdir(os.path.join(results_folder, f)) ] result_mol_folders.sort() print( f"\nLoading data from {len(result_mol_folders)} result folders under {results_folder}" ) # output folder if os.path.exists(out_folder): shutil.rmtree(out_folder) os.mkdir(out_folder) target_names = [] for mol_folder in result_mol_folders: mol_name = os.path.basename(mol_folder) # the name of the molecules should be consistent with the mol_folder mol_file = os.path.join(molecules_folder, mol_name + '.mol2') molecule = Molecule(mol_file) # find all torsion data finished_scans = [] for f in os.listdir(mol_folder): name, ext = os.path.splitext(f) if ext == '.xyz': finished_scans.append(name) if len(finished_scans) == 0: print(f'No finished scans found in {mol_folder}') continue # output target name target_name = 'td_' + mol_name target_names.append(target_name) # make target folder this_target_folder = os.path.join(out_folder, target_name) os.mkdir(this_target_folder) # read data from each finished scans target_mol = Molecule() target_mol.elem = molecule.elem target_mol.xyzs = [] target_mol.qm_energies = [] target_mol.qm_grads = [] for f in finished_scans: xyz_file = os.path.join(mol_folder, f + '.xyz') m = Molecule(xyz_file) target_mol.xyzs += m.xyzs # read energy from comment line energies = [float(comm.split()[-1]) for comm in m.comms] target_mol.qm_energies += energies # read gradient grad_file = os.path.join(mol_folder, f + '.gradxyz') grads = read_gradxyz(grad_file) target_mol.qm_grads += grads # write qdata.txt target_mol.write(os.path.join(this_target_folder, 'qdata.txt')) # write scan.xyz target_mol.write(os.path.join(this_target_folder, 'scan.xyz')) # write pdb molecule.write(os.path.join(this_target_folder, 'conf.pdb')) # copy mol2 file shutil.copyfile(mol_file, os.path.join(this_target_folder, 'input.mol2')) # write a note with open(os.path.join(this_target_folder, 'notes.txt'), 'w') as fnote: fnote.write( "Notes: This target is made by make_fb_targets.py, using data from\n" ) fnote.write(mol_file + '\n') for f in finished_scans: xyz_file = os.path.join(mol_folder, f + '.xyz') grad_file = os.path.join(mol_folder, f + '.gradxyz') fnote.write(xyz_file + '\n') fnote.write(grad_file + '\n') # write a target.in file for use in ForceBalance input with open(os.path.join(out_folder, 'targets.in'), 'w') as fout: for tname in target_names: fout.write(target_str.format(name=tname) + '\n') print(f"Targets generation finished!") print( f"You can copy contents in {os.path.join(out_folder, 'targets.in')} to your ForceBalance input file." )
class Lipid(Target): """ Subclass of Target for lipid property matching.""" def __init__(self,options,tgt_opts,forcefield): # Initialize base class super(Lipid,self).__init__(options,tgt_opts,forcefield) # Weight of the density self.set_option(tgt_opts,'w_rho',forceprint=True) # Weight of the thermal expansion coefficient self.set_option(tgt_opts,'w_alpha',forceprint=True) # Weight of the isothermal compressibility self.set_option(tgt_opts,'w_kappa',forceprint=True) # Weight of the isobaric heat capacity self.set_option(tgt_opts,'w_cp',forceprint=True) # Weight of the dielectric constant self.set_option(tgt_opts,'w_eps0',forceprint=True) # Weight of the area per lipid self.set_option(tgt_opts,'w_al',forceprint=True) # Weight of the bilayer isothermal compressibility self.set_option(tgt_opts,'w_lkappa',forceprint=True) # Weight of the deuterium order parameter self.set_option(tgt_opts,'w_scd',forceprint=True) # Normalize the property contributions to the objective function self.set_option(tgt_opts,'w_normalize',forceprint=True) # Optionally pause on the zeroth step self.set_option(tgt_opts,'manual') # Number of time steps in the lipid "equilibration" run self.set_option(tgt_opts,'lipid_eq_steps',forceprint=True) # Number of time steps in the lipid "production" run self.set_option(tgt_opts,'lipid_md_steps',forceprint=True) # Number of time steps in the gas "equilibration" run self.set_option(tgt_opts,'gas_eq_steps',forceprint=False) # Number of time steps in the gas "production" run self.set_option(tgt_opts,'gas_md_steps',forceprint=False) # Cutoff for nonbonded interactions in the liquid if tgt_opts['nonbonded_cutoff'] is not None: self.set_option(tgt_opts,'nonbonded_cutoff') # Cutoff for vdW interactions if different from other nonbonded interactions if tgt_opts['vdw_cutoff'] is not None: self.set_option(tgt_opts,'vdw_cutoff') # Time step length (in fs) for the lipid production run self.set_option(tgt_opts,'lipid_timestep',forceprint=True) # Time interval (in ps) for writing coordinates self.set_option(tgt_opts,'lipid_interval',forceprint=True) # Time step length (in fs) for the gas production run self.set_option(tgt_opts,'gas_timestep',forceprint=True) # Time interval (in ps) for writing coordinates self.set_option(tgt_opts,'gas_interval',forceprint=True) # Minimize the energy prior to running any dynamics self.set_option(tgt_opts,'minimize_energy',forceprint=True) # Isolated dipole (debye) for analytic self-polarization correction. self.set_option(tgt_opts,'self_pol_mu0',forceprint=True) # Molecular polarizability (ang**3) for analytic self-polarization correction. self.set_option(tgt_opts,'self_pol_alpha',forceprint=True) # Set up the simulation object for self-polarization correction. self.do_self_pol = (self.self_pol_mu0 > 0.0 and self.self_pol_alpha > 0.0) # Enable anisotropic periodic box self.set_option(tgt_opts,'anisotropic_box',forceprint=True) # Whether to save trajectories (0 = never, 1 = delete after good step, 2 = keep all) self.set_option(tgt_opts,'save_traj') #======================================# # Variables which are set here # #======================================# # List of trajectory files that may be deleted if self.save_traj == 1. self.last_traj = [] # Extra files to be copied back at the end of a run. self.extra_output = [] # Read the reference data self.read_data() # Read in lipid starting coordinates. if 'n_ic' in self.RefData: # Linked IC folder into the temp-directory. self.nptfiles += ["IC"] # Store IC frames in a dictionary. self.lipid_mols = OrderedDict() self.lipid_mols_new = OrderedDict() for pt in self.PhasePoints: pt_label = "IC/%sK-%s%s" % (pt[0], pt[1], pt[2]) if not os.path.exists(os.path.join(self.root, self.tgtdir, pt_label, self.lipid_coords)): raise RuntimeError("Initial condition files don't exist; please provide IC directory") # Create molecule object for each IC. all_ic = Molecule(os.path.join(self.root, self.tgtdir, pt_label, self.lipid_coords)) self.lipid_mols[pt] = [] n_uniq_ic = int(self.RefData['n_ic'][pt]) if n_uniq_ic > len(all_ic): raise RuntimeError("Number of frames in initial conditions .gro file is less than the number of parallel simulations requested in data.csv") # Index ICs by pressure and temperature in a dictionary. for ic in range(n_uniq_ic): self.lipid_mols[pt].append(all_ic[ic]) else: # Read in lipid starting coordinates. if not os.path.exists(os.path.join(self.root, self.tgtdir, self.lipid_coords)): logger.error("%s doesn't exist; please provide lipid_coords option\n" % self.lipid_coords) raise RuntimeError self.lipid_mol = Molecule(os.path.join(self.root, self.tgtdir, self.lipid_coords), toppbc=True) # Extra files to be linked into the temp-directory. self.nptfiles += [self.lipid_coords] # Scripts to be copied from the ForceBalance installation directory. self.scripts += ['npt_lipid.py'] # Prepare the temporary directory. self.prepare_temp_directory() # Build keyword dictionary to pass to engine. if self.do_self_pol: self.gas_engine_args.update(self.OptionDict) self.gas_engine_args.update(options) del self.gas_engine_args['name'] # Create engine object for gas molecule to do the polarization correction. self.gas_engine = self.engine_(target=self, mol=self.gas_mol, name="selfpol", **self.gas_engine_args) # Don't read indicate.log when calling meta_indicate() self.read_indicate = False self.write_indicate = False # Don't read objective.p when calling meta_get() self.read_objective = False #======================================# # UNDER DEVELOPMENT # #======================================# # Put stuff here that I'm not sure about. :) np.set_printoptions(precision=4, linewidth=100) np.seterr(under='ignore') ## Saved force field mvals for all iterations self.SavedMVal = {} ## Saved trajectories for all iterations and all temperatures self.SavedTraj = defaultdict(dict) ## Evaluated energies for all trajectories (i.e. all iterations and all temperatures), using all mvals self.MBarEnergy = defaultdict(lambda:defaultdict(dict)) def prepare_temp_directory(self): """ Prepare the temporary directory by copying in important files. """ abstempdir = os.path.join(self.root,self.tempdir) for f in self.nptfiles: LinkFile(os.path.join(self.root, self.tgtdir, f), os.path.join(abstempdir, f)) for f in self.scripts: LinkFile(os.path.join(os.path.split(__file__)[0],"data",f),os.path.join(abstempdir,f)) def read_data(self): # Read the 'data.csv' file. The file should contain guidelines. with open(os.path.join(self.tgtdir,'data.csv'),'rU') as f: R0 = list(csv.reader(f)) # All comments are erased. R1 = [[sub('#.*$','',word) for word in line] for line in R0 if len(line[0]) > 0 and line[0][0] != "#"] # All empty lines are deleted and words are converted to lowercase. R = [[wrd.lower() for wrd in line] for line in R1 if any([len(wrd) for wrd in line]) > 0] global_opts = OrderedDict() found_headings = False known_vars = ['mbar','rho','hvap','alpha','kappa','cp','eps0','cvib_intra', 'cvib_inter','cni','devib_intra','devib_inter', 'al', 'scd', 'n_ic', 'lkappa'] self.RefData = OrderedDict() for line in R: if line[0] == "global": # Global options are mainly denominators for the different observables. if isfloat(line[2]): global_opts[line[1]] = float(line[2]) elif line[2].lower() == 'false': global_opts[line[1]] = False elif line[2].lower() == 'true': global_opts[line[1]] = True elif not found_headings: found_headings = True headings = line if len(set(headings)) != len(headings): logger.error('Column headings in data.csv must be unique\n') raise RuntimeError if 'p' not in headings: logger.error('There must be a pressure column heading labeled by "p" in data.csv\n') raise RuntimeError if 't' not in headings: logger.error('There must be a temperature column heading labeled by "t" in data.csv\n') raise RuntimeError elif found_headings: try: # Temperatures are in kelvin. t = [float(val) for head, val in zip(headings,line) if head == 't'][0] # For convenience, users may input the pressure in atmosphere or bar. pval = [float(val.split()[0]) for head, val in zip(headings,line) if head == 'p'][0] punit = [val.split()[1] if len(val.split()) >= 1 else "atm" for head, val in zip(headings,line) if head == 'p'][0] unrec = set([punit]).difference(['atm','bar']) if len(unrec) > 0: logger.error('The pressure unit %s is not recognized, please use bar or atm\n' % unrec[0]) raise RuntimeError # This line actually reads the reference data and inserts it into the RefData dictionary of dictionaries. for head, val in zip(headings,line): if head == 't' or head == 'p' : continue if isfloat(val): self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = float(val.strip()) elif val.lower() == 'true': self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = True elif val.lower() == 'false': self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = False elif head == 'scd': self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = np.array(map(float, val.split())) except: logger.error(line + '\n') logger.error('Encountered an error reading this line!\n') raise RuntimeError else: logger.error(line + '\n') logger.error('I did not recognize this line!\n') raise RuntimeError # Check the reference data table for validity. default_denoms = defaultdict(int) PhasePoints = None for head in self.RefData: if head == 'n_ic': continue if head not in known_vars+[i+"_wt" for i in known_vars]: # Only hard-coded properties may be recognized. logger.error("The column heading %s is not recognized in data.csv\n" % head) raise RuntimeError if head in known_vars: if head+"_wt" not in self.RefData: # If the phase-point weights are not specified in the reference data file, initialize them all to one. self.RefData[head+"_wt"] = OrderedDict([(key, 1.0) for key in self.RefData[head]]) wts = np.array(self.RefData[head+"_wt"].values()) dat = np.array(self.RefData[head].values()) # S_cd specifies an array of averages (one for each tail node). Find avg over axis 0. avg = np.average(dat, weights=wts, axis=0) if len(wts) > 1: # If there is more than one data point, then the default denominator is the # standard deviation of the experimental values. if head == 'scd': default_denoms[head+"_denom"] = np.average(np.sqrt(np.dot(wts, (dat-avg)**2)/wts.sum())) else: default_denoms[head+"_denom"] = np.sqrt(np.dot(wts, (dat-avg)**2)/wts.sum()) else: # If there is only one data point, then the denominator is just the single # data point itself. if head == 'scd': default_denoms[head+"_denom"] = np.average(np.sqrt(np.abs(dat[0]))) else: default_denoms[head+"_denom"] = np.sqrt(np.abs(dat[0])) self.PhasePoints = self.RefData[head].keys() # This prints out all of the reference data. # printcool_dictionary(self.RefData[head],head) # Create labels for the directories. self.Labels = ["%.2fK-%.1f%s" % i for i in self.PhasePoints] logger.debug("global_opts:\n%s\n" % str(global_opts)) logger.debug("default_denoms:\n%s\n" % str(default_denoms)) for opt in global_opts: if "_denom" in opt: # Record entries from the global_opts dictionary so they can be retrieved from other methods. self.set_option(global_opts,opt,default=default_denoms[opt]) else: self.set_option(global_opts,opt) def check_files(self, there): there = os.path.abspath(there) havepts = 0 if all([i in os.listdir(there) for i in self.Labels]): for d in os.listdir(there): if d in self.Labels: if os.path.exists(os.path.join(there, d, 'npt_result.p')): havepts += 1 if (float(havepts)/len(self.Labels)) > 0.75: return 1 else: return 0 def npt_simulation(self, temperature, pressure, simnum): """ Submit a NPT simulation to the Work Queue. """ wq = getWorkQueue() if not os.path.exists('npt_result.p'): link_dir_contents(os.path.join(self.root,self.rundir),os.getcwd()) self.last_traj += [os.path.join(os.getcwd(), i) for i in self.extra_output] self.lipid_mol[simnum%len(self.lipid_mol)].write(self.lipid_coords, ftype='tinker' if self.engname == 'tinker' else None) cmdstr = '%s python npt_lipid.py %s %.3f %.3f' % (self.nptpfx, self.engname, temperature, pressure) if wq is None: logger.info("Running condensed phase simulation locally.\n") logger.info("You may tail -f %s/npt.out in another terminal window\n" % os.getcwd()) _exec(cmdstr, copy_stderr=True, outfnm='npt.out') else: queue_up(wq, command = cmdstr+' &> npt.out', input_files = self.nptfiles + self.scripts + ['forcebalance.p'], output_files = ['npt_result.p', 'npt.out'] + self.extra_output, tgt=self) def polarization_correction(self,mvals): d = self.gas_engine.get_multipole_moments(optimize=True)['dipole'] if not in_fd(): logger.info("The molecular dipole moment is % .3f debye\n" % np.linalg.norm(d)) # Taken from the original OpenMM interface code, this is how we calculate the conversion factor. # dd2 = ((np.linalg.norm(d)-self.self_pol_mu0)*debye)**2 # eps0 = 8.854187817620e-12 * coulomb**2 / newton / meter**2 # epol = 0.5*dd2/(self.self_pol_alpha*angstrom**3*4*np.pi*eps0)/(kilojoule_per_mole/AVOGADRO_CONSTANT_NA) # In [2]: eps0 = 8.854187817620e-12 * coulomb**2 / newton / meter**2 # In [7]: 1.0 * debye ** 2 / (1.0 * angstrom**3*4*np.pi*eps0) / (kilojoule_per_mole/AVOGADRO_CONSTANT_NA) # Out[7]: 60.240179789402056 convert = 60.240179789402056 dd2 = (np.linalg.norm(d)-self.self_pol_mu0)**2 epol = 0.5*convert*dd2/self.self_pol_alpha return epol def indicate(self): AGrad = hasattr(self, 'Gp') PrintDict = OrderedDict() def print_item(key, heading, physunit): if self.Xp[key] > 0: printcool_dictionary(self.Pp[key], title='%s %s%s\nTemperature Pressure Reference Calculated +- Stdev Delta Weight Term ' % (self.name, heading, " (%s) " % physunit if physunit else ""), bold=True, color=4, keywidth=15) bar = printcool("%s objective function: % .3f%s" % (heading, self.Xp[key], ", Derivative:" if AGrad else "")) if AGrad: self.FF.print_map(vals=self.Gp[key]) logger.info(bar) PrintDict[heading] = "% 10.5f % 8.3f % 14.5e" % (self.Xp[key], self.Wp[key], self.Xp[key]*self.Wp[key]) print_item("Rho", "Density", "kg m^-3") print_item("Alpha", "Thermal Expansion Coefficient", "10^-4 K^-1") print_item("Kappa", "Isothermal Compressibility", "10^-6 bar^-1") print_item("Cp", "Isobaric Heat Capacity", "cal mol^-1 K^-1") print_item("Eps0", "Dielectric Constant", None) print_item("Al", "Average Area per Lipid", "nm^2") print_item("Scd", "Deuterium Order Parameter", None) print_item("LKappa", "Bilayer Isothermal Compressibility", "mN/m") PrintDict['Total'] = "% 10s % 8s % 14.5e" % ("","",self.Objective) Title = "%s Condensed Phase Properties:\n %-20s %40s" % (self.name, "Property Name", "Residual x Weight = Contribution") printcool_dictionary(PrintDict,color=4,title=Title,keywidth=31) return def objective_term(self, points, expname, calc, err, grad, name="Quantity", SubAverage=False): if expname in self.RefData: exp = self.RefData[expname] Weights = self.RefData[expname+"_wt"] Denom = getattr(self,expname+"_denom") else: # If the reference data doesn't exist then return nothing. return 0.0, np.zeros(self.FF.np), np.zeros((self.FF.np,self.FF.np)), None Sum = sum(Weights.values()) for i in Weights: Weights[i] /= Sum logger.info("Weights have been renormalized to " + str(sum(Weights.values())) + "\n") # Use least-squares or hyperbolic (experimental) objective. LeastSquares = True logger.info("Physical quantity %s uses denominator = % .4f\n" % (name, Denom)) if not LeastSquares: # If using a hyperbolic functional form # we still want the contribution to the # objective function to be the same when # Delta = Denom. Denom /= 3 ** 0.5 Objective = 0.0 Gradient = np.zeros(self.FF.np) Hessian = np.zeros((self.FF.np,self.FF.np)) Objs = {} GradMap = [] avgCalc = 0.0 avgExp = 0.0 avgGrad = np.zeros(self.FF.np) for i, PT in enumerate(points): avgCalc += Weights[PT]*calc[PT] avgExp += Weights[PT]*exp[PT] avgGrad += Weights[PT]*grad[PT] for i, PT in enumerate(points): if SubAverage: G = grad[PT]-avgGrad Delta = calc[PT] - exp[PT] - avgCalc + avgExp else: G = grad[PT] Delta = calc[PT] - exp[PT] if hasattr(Delta, "__len__"): Delta = np.average(Delta) if LeastSquares: # Least-squares objective function. ThisObj = Weights[PT] * Delta ** 2 / Denom**2 Objs[PT] = ThisObj ThisGrad = 2.0 * Weights[PT] * Delta * G / Denom**2 GradMap.append(G) Objective += ThisObj Gradient += ThisGrad # Gauss-Newton approximation to the Hessian. Hessian += 2.0 * Weights[PT] * (np.outer(G, G)) / Denom**2 else: # L1-like objective function. D = Denom S = Delta**2 + D**2 ThisObj = Weights[PT] * (S**0.5-D) / Denom ThisGrad = Weights[PT] * (Delta/S**0.5) * G / Denom ThisHess = Weights[PT] * (1/S**0.5-Delta**2/S**1.5) * np.outer(G,G) / Denom Objs[PT] = ThisObj GradMap.append(G) Objective += ThisObj Gradient += ThisGrad Hessian += ThisHess GradMapPrint = [["#PhasePoint"] + self.FF.plist] for PT, g in zip(points,GradMap): GradMapPrint.append([' %8.2f %8.1f %3s' % PT] + ["% 9.3e" % i for i in g]) o = wopen('gradient_%s.dat' % name) for line in GradMapPrint: print >> o, ' '.join(line) o.close() Delta = np.array([calc[PT] - exp[PT] for PT in points]) delt = {PT : r for PT, r in zip(points,Delta)} if expname == 'scd': print_out = OrderedDict([(' %8.2f %8.1f %3s' % PT, '\n %s' % (' '.join('\t \t \t %9.6f %9.6f +- %-7.6f % 7.6f \n' % F for F in zip(exp[PT], calc[PT], flat(err[PT]), delt[PT])))) for PT in calc]) else: print_out = OrderedDict([(' %8.2f %8.1f %3s' % PT, "%9.3f %9.3f +- %-7.3f % 7.3f % 9.5f % 9.5f" % (exp[PT],calc[PT],err[PT],delt[PT],Weights[PT],Objs[PT])) for PT in calc]) return Objective, Gradient, Hessian, print_out def submit_jobs(self, mvals, AGrad=True, AHess=True): # This routine is called by Objective.stage() will run before "get". # It submits the jobs to the Work Queue and the stage() function will wait for jobs to complete. # # First dump the force field to a pickle file lp_dump((self.FF,mvals,self.OptionDict,AGrad),'forcebalance.p') # Give the user an opportunity to copy over data from a previous (perhaps failed) run. if (not self.evaluated) and self.manual: warn_press_key("Now's our chance to fill the temp directory up with data!\n(Considering using 'read' or 'continue' for better checkpointing)", timeout=7200) # If self.save_traj == 1, delete the trajectory files from a previous good optimization step. if self.evaluated and self.goodstep and self.save_traj < 2: for fn in self.last_traj: if os.path.exists(fn): os.remove(fn) self.last_traj = [] # Set up and run the NPT simulations. snum = 0 for label, pt in zip(self.Labels, self.PhasePoints): T = pt[0] P = pt[1] Punit = pt[2] if Punit == 'bar': P *= 1.0 / 1.01325 if not os.path.exists(label): os.makedirs(label) os.chdir(label) if 'n_ic' in self.RefData: n_uniq_ic = int(self.RefData['n_ic'][pt]) # Loop over parallel trajectories. for trj in range(n_uniq_ic): rel_trj = "trj_%i" % trj # Create directories for each parallel simulation. if not os.path.exists(rel_trj): os.makedirs(rel_trj) os.chdir(rel_trj) # Pull each simulation molecule from the lipid_mols dictionary. # lipid_mols is a dictionary of paths to either the initial # geometry files, or the geometries from the final frame of the # previous iteration. self.lipid_mol = self.lipid_mols[pt][trj] self.lipid_mol.write(self.lipid_coords) if not self.lipid_coords in self.nptfiles: self.nptfiles += [self.lipid_coords] self.npt_simulation(T,P,snum) os.chdir('..') else: self.npt_simulation(T,P,snum) os.chdir('..') snum += 1 def get(self, mvals, AGrad=True, AHess=True): """ Fitting of lipid bulk properties. This is the current major direction of development for ForceBalance. Basically, fitting the QM energies / forces alone does not always give us the best simulation behavior. In many cases it makes more sense to try and reproduce some experimentally known data as well. In order to reproduce experimentally known data, we need to run a simulation and compare the simulation result to experiment. The main challenge here is that the simulations are computationally intensive (i.e. they require energy and force evaluations), and furthermore the results are noisy. We need to run the simulations automatically and remotely (i.e. on clusters) and a good way to calculate the derivatives of the simulation results with respect to the parameter values. This function contains some experimentally known values of the density and enthalpy of vaporization (Hvap) of lipid water. It launches the density and Hvap calculations on the cluster, and gathers the results / derivatives. The actual calculation of results / derivatives is done in a separate file. After the results come back, they are gathered together to form an objective function. @param[in] mvals Mathematical parameter values @param[in] AGrad Switch to turn on analytic gradient @param[in] AHess Switch to turn on analytic Hessian @return Answer Contribution to the objective function """ mbar_verbose = False Answer = {} Results = {} Points = [] # These are the phase points for which data exists. BPoints = [] # These are the phase points for which we are doing MBAR for the condensed phase. tt = 0 for label, PT in zip(self.Labels, self.PhasePoints): if 'n_ic' in self.RefData: self.lipid_mols[PT] = [Molecule(last_frame) for last_frame in self.lipid_mols[PT]] n_uniq_ic = int(self.RefData['n_ic'][PT]) for ic in range(n_uniq_ic): if os.path.exists('./%s/trj_%s/npt_result.p' % (label, ic)): # Read in each each parallel simulation's data, and concatenate each property time series. ts = lp_load('./%s/trj_%s/npt_result.p' % (label, ic)) if ic == 0: ts_concat = list(ts) else: for d_arr in range(len(ts)): if isinstance(ts[d_arr], np.ndarray): # Gradients need a unique append format. if d_arr == 5: ts_concat[d_arr] = np.append(ts_concat[d_arr], ts[d_arr], axis = 1) else: ts_concat[d_arr] = np.append(ts_concat[d_arr], ts[d_arr], axis = 0) if isinstance(ts_concat[d_arr], list): ts_concat[d_arr] = [np.append(ts_concat[d_arr][i], ts[d_arr][i], axis = 1) for i in range(len(ts_concat[d_arr]))] # Write concatenated time series to a pickle file. if ic == (int(n_uniq_ic) - 1): lp_dump((ts_concat), './%s/npt_result.p' % label) if os.path.exists('./%s/npt_result.p' % label): logger.info('Reading information from ./%s/npt_result.p\n' % label) Points.append(PT) Results[tt] = lp_load('./%s/npt_result.p' % label) tt += 1 else: logger.warning('The file ./%s/npt_result.p does not exist so we cannot read it\n' % label) pass # for obs in self.RefData: # del self.RefData[obs][PT] if len(Points) == 0: logger.error('The lipid simulations have terminated with \x1b[1;91mno readable data\x1b[0m - this is a problem!\n') raise RuntimeError # Assign variable names to all the stuff in npt_result.p Rhos, Vols, Potentials, Energies, Dips, Grads, GDips, \ Rho_errs, Alpha_errs, Kappa_errs, Cp_errs, Eps0_errs, NMols, Als, Al_errs, Scds, Scd_errs, LKappa_errs = ([Results[t][i] for t in range(len(Points))] for i in range(18)) # Determine the number of molecules if len(set(NMols)) != 1: logger.error(str(NMols)) logger.error('The above list should only contain one number - the number of molecules\n') raise RuntimeError else: NMol = list(set(NMols))[0] R = np.array(list(itertools.chain(*list(Rhos)))) V = np.array(list(itertools.chain(*list(Vols)))) E = np.array(list(itertools.chain(*list(Energies)))) Dx = np.array(list(itertools.chain(*list(d[:,0] for d in Dips)))) Dy = np.array(list(itertools.chain(*list(d[:,1] for d in Dips)))) Dz = np.array(list(itertools.chain(*list(d[:,2] for d in Dips)))) G = np.hstack(tuple(Grads)) GDx = np.hstack(tuple(gd[0] for gd in GDips)) GDy = np.hstack(tuple(gd[1] for gd in GDips)) GDz = np.hstack(tuple(gd[2] for gd in GDips)) A = np.array(list(itertools.chain(*list(Als)))) S = np.array(list(itertools.chain(*list(Scds)))) Rho_calc = OrderedDict([]) Rho_grad = OrderedDict([]) Rho_std = OrderedDict([]) Alpha_calc = OrderedDict([]) Alpha_grad = OrderedDict([]) Alpha_std = OrderedDict([]) Kappa_calc = OrderedDict([]) Kappa_grad = OrderedDict([]) Kappa_std = OrderedDict([]) Cp_calc = OrderedDict([]) Cp_grad = OrderedDict([]) Cp_std = OrderedDict([]) Eps0_calc = OrderedDict([]) Eps0_grad = OrderedDict([]) Eps0_std = OrderedDict([]) Al_calc = OrderedDict([]) Al_grad = OrderedDict([]) Al_std = OrderedDict([]) LKappa_calc = OrderedDict([]) LKappa_grad = OrderedDict([]) LKappa_std = OrderedDict([]) Scd_calc = OrderedDict([]) Scd_grad = OrderedDict([]) Scd_std = OrderedDict([]) # The unit that converts atmospheres * nm**3 into kj/mol :) pvkj=0.061019351687175 # Run MBAR using the total energies. Required for estimates that use the kinetic energy. BSims = len(BPoints) Shots = len(Energies[0]) Shots_m = [len(i) for i in Energies] N_k = np.ones(BSims)*Shots # Use the value of the energy for snapshot t from simulation k at potential m U_kln = np.zeros([BSims,BSims,Shots]) for m, PT in enumerate(BPoints): T = PT[0] P = PT[1] / 1.01325 if PT[2] == 'bar' else PT[1] beta = 1. / (kb * T) for k in range(BSims): # The correct Boltzmann factors include PV. # Note that because the Boltzmann factors are computed from the conditions at simulation "m", # the pV terms must be rescaled to the pressure at simulation "m". kk = Points.index(BPoints[k]) U_kln[k, m, :] = Energies[kk] + P*Vols[kk]*pvkj U_kln[k, m, :] *= beta W1 = None if len(BPoints) > 1: logger.info("Running MBAR analysis on %i states...\n" % len(BPoints)) mbar = pymbar.MBAR(U_kln, N_k, verbose=mbar_verbose, relative_tolerance=5.0e-8) W1 = mbar.getWeights() logger.info("Done\n") elif len(BPoints) == 1: W1 = np.ones((BPoints*Shots,BPoints)) W1 /= BPoints*Shots def fill_weights(weights, phase_points, mbar_points, snapshots): """ Fill in the weight matrix with MBAR weights where MBAR was run, and equal weights otherwise. """ new_weights = np.zeros([len(phase_points)*snapshots,len(phase_points)]) for m, PT in enumerate(phase_points): if PT in mbar_points: mm = mbar_points.index(PT) for kk, PT1 in enumerate(mbar_points): k = phase_points.index(PT1) logger.debug("Will fill W2[%i:%i,%i] with W1[%i:%i,%i]\n" % (k*snapshots,k*snapshots+snapshots,m,kk*snapshots,kk*snapshots+snapshots,mm)) new_weights[k*snapshots:(k+1)*snapshots,m] = weights[kk*snapshots:(kk+1)*snapshots,mm] else: logger.debug("Will fill W2[%i:%i,%i] with equal weights\n" % (m*snapshots,(m+1)*snapshots,m)) new_weights[m*snapshots:(m+1)*snapshots,m] = 1.0/snapshots return new_weights W2 = fill_weights(W1, Points, BPoints, Shots) if self.do_self_pol: EPol = self.polarization_correction(mvals) GEPol = np.array([(f12d3p(fdwrap(self.polarization_correction, mvals, p), h = self.h, f0 = EPol)[0] if p in self.pgrad else 0.0) for p in range(self.FF.np)]) bar = printcool("Self-polarization correction to \nenthalpy of vaporization is % .3f kJ/mol%s" % (EPol, ", Derivative:" if AGrad else "")) if AGrad: self.FF.print_map(vals=GEPol) logger.info(bar) for i, PT in enumerate(Points): T = PT[0] P = PT[1] / 1.01325 if PT[2] == 'bar' else PT[1] PV = P*V*pvkj H = E + PV # The weights that we want are the last ones. W = flat(W2[:,i]) C = weight_info(W, PT, np.ones(len(Points))*Shots, verbose=mbar_verbose) Gbar = flat(np.mat(G)*col(W)) mBeta = -1/kb/T Beta = 1/kb/T kT = kb*T # Define some things to make the analytic derivatives easier. def avg(vec): return np.dot(W,vec) def covde(vec): return flat(np.mat(G)*col(W*vec)) - avg(vec)*Gbar def deprod(vec): return flat(np.mat(G)*col(W*vec)) ## Density. Rho_calc[PT] = np.dot(W,R) Rho_grad[PT] = mBeta*(flat(np.mat(G)*col(W*R)) - np.dot(W,R)*Gbar) ## Ignore enthalpy. ## Thermal expansion coefficient. Alpha_calc[PT] = 1e4 * (avg(H*V)-avg(H)*avg(V))/avg(V)/(kT*T) GAlpha1 = -1 * Beta * deprod(H*V) * avg(V) / avg(V)**2 GAlpha2 = +1 * Beta * avg(H*V) * deprod(V) / avg(V)**2 GAlpha3 = deprod(V)/avg(V) - Gbar GAlpha4 = Beta * covde(H) Alpha_grad[PT] = 1e4 * (GAlpha1 + GAlpha2 + GAlpha3 + GAlpha4)/(kT*T) ## Isothermal compressibility. bar_unit = 0.06022141793 * 1e6 Kappa_calc[PT] = bar_unit / kT * (avg(V**2)-avg(V)**2)/avg(V) GKappa1 = +1 * Beta**2 * avg(V**2) * deprod(V) / avg(V)**2 GKappa2 = -1 * Beta**2 * avg(V) * deprod(V**2) / avg(V)**2 GKappa3 = +1 * Beta**2 * covde(V) Kappa_grad[PT] = bar_unit*(GKappa1 + GKappa2 + GKappa3) ## Isobaric heat capacity. Cp_calc[PT] = 1000/(4.184*NMol*kT*T) * (avg(H**2) - avg(H)**2) if hasattr(self,'use_cvib_intra') and self.use_cvib_intra: logger.debug("Adding " + str(self.RefData['devib_intra'][PT]) + " to the heat capacity\n") Cp_calc[PT] += self.RefData['devib_intra'][PT] if hasattr(self,'use_cvib_inter') and self.use_cvib_inter: logger.debug("Adding " + str(self.RefData['devib_inter'][PT]) + " to the heat capacity\n") Cp_calc[PT] += self.RefData['devib_inter'][PT] GCp1 = 2*covde(H) * 1000 / 4.184 / (NMol*kT*T) GCp2 = mBeta*covde(H**2) * 1000 / 4.184 / (NMol*kT*T) GCp3 = 2*Beta*avg(H)*covde(H) * 1000 / 4.184 / (NMol*kT*T) Cp_grad[PT] = GCp1 + GCp2 + GCp3 ## Static dielectric constant. prefactor = 30.348705333964077 D2 = avg(Dx**2)+avg(Dy**2)+avg(Dz**2)-avg(Dx)**2-avg(Dy)**2-avg(Dz)**2 Eps0_calc[PT] = 1.0 + prefactor*(D2/avg(V))/T GD2 = 2*(flat(np.mat(GDx)*col(W*Dx)) - avg(Dx)*flat(np.mat(GDx)*col(W))) - Beta*(covde(Dx**2) - 2*avg(Dx)*covde(Dx)) GD2 += 2*(flat(np.mat(GDy)*col(W*Dy)) - avg(Dy)*flat(np.mat(GDy)*col(W))) - Beta*(covde(Dy**2) - 2*avg(Dy)*covde(Dy)) GD2 += 2*(flat(np.mat(GDz)*col(W*Dz)) - avg(Dz)*flat(np.mat(GDz)*col(W))) - Beta*(covde(Dz**2) - 2*avg(Dz)*covde(Dz)) Eps0_grad[PT] = prefactor*(GD2/avg(V) - mBeta*covde(V)*D2/avg(V)**2)/T ## Average area per lipid Al_calc[PT] = np.dot(W,A) Al_grad[PT] = mBeta*(flat(np.mat(G)*col(W*A)) - np.dot(W,A)*Gbar) ## Bilayer Isothermal compressibility. A_m2 = A * 1e-18 kbT = 1.3806488e-23 * T LKappa_calc[PT] = (1e3 * 2 * kbT / 128) * (avg(A_m2) / (avg(A_m2**2)-avg(A_m2)**2)) al_avg = avg(A_m2) al_sq_avg = avg(A_m2**2) al_avg_sq = al_avg**2 al_var = al_sq_avg - al_avg_sq GLKappa1 = covde(A_m2) / al_var GLKappa2 = (al_avg / al_var**2) * (covde(A_m2**2) - (2 * al_avg * covde(A))) LKappa_grad[PT] = (1e3 * 2 * kbT / 128) * (GLKappa1 - GLKappa2) ## Deuterium order parameter Scd_calc[PT] = np.dot(W,S) Scd_grad[PT] = mBeta * (flat(np.average(np.mat(G) * (S * W[:, np.newaxis]), axis = 1)) - np.average(np.average(S * W[:, np.newaxis], axis = 0), axis = 0) * Gbar) ## Estimation of errors. Rho_std[PT] = np.sqrt(sum(C**2 * np.array(Rho_errs)**2)) Alpha_std[PT] = np.sqrt(sum(C**2 * np.array(Alpha_errs)**2)) * 1e4 Kappa_std[PT] = np.sqrt(sum(C**2 * np.array(Kappa_errs)**2)) * 1e6 Cp_std[PT] = np.sqrt(sum(C**2 * np.array(Cp_errs)**2)) Eps0_std[PT] = np.sqrt(sum(C**2 * np.array(Eps0_errs)**2)) Al_std[PT] = np.sqrt(sum(C**2 * np.array(Al_errs)**2)) Scd_std[PT] = np.sqrt(sum(np.mat(C**2) * np.array(Scd_errs)**2)) LKappa_std[PT] = np.sqrt(sum(C**2 * np.array(LKappa_errs)**2)) * 1e6 # Get contributions to the objective function X_Rho, G_Rho, H_Rho, RhoPrint = self.objective_term(Points, 'rho', Rho_calc, Rho_std, Rho_grad, name="Density") X_Alpha, G_Alpha, H_Alpha, AlphaPrint = self.objective_term(Points, 'alpha', Alpha_calc, Alpha_std, Alpha_grad, name="Thermal Expansion") X_Kappa, G_Kappa, H_Kappa, KappaPrint = self.objective_term(Points, 'kappa', Kappa_calc, Kappa_std, Kappa_grad, name="Compressibility") X_Cp, G_Cp, H_Cp, CpPrint = self.objective_term(Points, 'cp', Cp_calc, Cp_std, Cp_grad, name="Heat Capacity") X_Eps0, G_Eps0, H_Eps0, Eps0Print = self.objective_term(Points, 'eps0', Eps0_calc, Eps0_std, Eps0_grad, name="Dielectric Constant") X_Al, G_Al, H_Al, AlPrint = self.objective_term(Points, 'al', Al_calc, Al_std, Al_grad, name="Avg Area per Lipid") X_Scd, G_Scd, H_Scd, ScdPrint = self.objective_term(Points, 'scd', Scd_calc, Scd_std, Scd_grad, name="Deuterium Order Parameter") X_LKappa, G_LKappa, H_LKappa, LKappaPrint = self.objective_term(Points, 'lkappa', LKappa_calc, LKappa_std, LKappa_grad, name="Bilayer Compressibility") Gradient = np.zeros(self.FF.np) Hessian = np.zeros((self.FF.np,self.FF.np)) if X_Rho == 0: self.w_rho = 0.0 if X_Alpha == 0: self.w_alpha = 0.0 if X_Kappa == 0: self.w_kappa = 0.0 if X_Cp == 0: self.w_cp = 0.0 if X_Eps0 == 0: self.w_eps0 = 0.0 if X_Al == 0: self.w_al = 0.0 if X_Scd == 0: self.w_scd = 0.0 if X_LKappa == 0: self.w_lkappa = 0.0 if self.w_normalize: w_tot = self.w_rho + self.w_alpha + self.w_kappa + self.w_cp + self.w_eps0 + self.w_al + self.w_scd + self.w_lkappa else: w_tot = 1.0 w_1 = self.w_rho / w_tot w_3 = self.w_alpha / w_tot w_4 = self.w_kappa / w_tot w_5 = self.w_cp / w_tot w_6 = self.w_eps0 / w_tot w_7 = self.w_al / w_tot w_8 = self.w_scd / w_tot w_9 = self.w_lkappa / w_tot Objective = w_1 * X_Rho + w_3 * X_Alpha + w_4 * X_Kappa + w_5 * X_Cp + w_6 * X_Eps0 + w_7 * X_Al + w_8 * X_Scd + w_9 * X_LKappa if AGrad: Gradient = w_1 * G_Rho + w_3 * G_Alpha + w_4 * G_Kappa + w_5 * G_Cp + w_6 * G_Eps0 + w_7 * G_Al + w_8 * G_Scd + w_9 * G_LKappa if AHess: Hessian = w_1 * H_Rho + w_3 * H_Alpha + w_4 * H_Kappa + w_5 * H_Cp + w_6 * H_Eps0 + w_7 * H_Al + w_8 * H_Scd + w_9 * H_LKappa if not in_fd(): self.Xp = {"Rho" : X_Rho, "Alpha" : X_Alpha, "Kappa" : X_Kappa, "Cp" : X_Cp, "Eps0" : X_Eps0, "Al" : X_Al, "Scd" : X_Scd, "LKappa" : X_LKappa} self.Wp = {"Rho" : w_1, "Alpha" : w_3, "Kappa" : w_4, "Cp" : w_5, "Eps0" : w_6, "Al" : w_7, "Scd" : w_8, "LKappa" : w_9} self.Pp = {"Rho" : RhoPrint, "Alpha" : AlphaPrint, "Kappa" : KappaPrint, "Cp" : CpPrint, "Eps0" : Eps0Print, "Al" : AlPrint, "Scd" : ScdPrint, "LKappa": LKappaPrint} if AGrad: self.Gp = {"Rho" : G_Rho, "Alpha" : G_Alpha, "Kappa" : G_Kappa, "Cp" : G_Cp, "Eps0" : G_Eps0, "Al" : G_Al, "Scd" : G_Scd, "LKappa" : G_LKappa} self.Objective = Objective Answer = {'X':Objective, 'G':Gradient, 'H':Hessian} return Answer
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
def make_torsiondrive_target(dataset_name, torsiondrive_data, test_ff=None): """ Make a folder of ForceBalance targets from the torsiondrive data """ target_name_prefix = 'td_' + dataset_name.replace(' ', '_') # create new targets folder if os.path.exists('targets'): shutil.rmtree('targets') os.mkdir('targets') os.chdir('targets') # write each entry as an individual target target_idx = 0 n_targets = len(torsiondrive_data) idx_fmt_string = get_int_fmt_string(n_targets) target_names = [] print(f"Generating {n_targets} targets") for entry_index, td_data in torsiondrive_data.items(): # pick a single initial molecule qcmol = td_data['initial_molecules'][0] # get mol_formula mol_formula = qcmol.get_molecular_formula() # create target folder target_idx_str = idx_fmt_string.format(target_idx) target_name = f"{target_name_prefix}_{target_idx_str}_{mol_formula}" print(f"{target_idx}: {target_name}") os.mkdir(target_name) os.chdir(target_name) # write a note with open('note.txt', 'w') as notefile: notefile.write( f'Target generated from dataset {dataset_name}, entry {entry_index}' ) # write input.mol2 file qcjson_mol = qcmol.dict(encoding='json') oemol = cmiles.utils.load_molecule(qcjson_mol) ofs.open(f'input.mol2') oechem.OEWriteMolecule(ofs, oemol) ofs.close() # test mol2 file success = True if test_ff != None: success, msg, molecule_labels = test_ff_mol2(test_ff, 'input.mol2') if not success: if not os.path.exists('../error_mol2s'): os.mkdir('../error_mol2s') shutil.move(f'input.mol2', f'../error_mol2s/{target_name}.mol2') with open(f'../error_mol2s/{target_name}_error.txt', 'w') as notefile: notefile.write(f'{dataset_name}\ntarget_name {target_name}\n') notefile.write( f'entry {entry_index}\ntd_keywords {td_data["keywords"]}\n' ) notefile.write(f'error message:\n{msg}') # remove this folder os.chdir('..') shutil.rmtree(target_name) else: # write conf.pdb file fbmol = Molecule(f'input.mol2') fbmol.write(f'conf.pdb') # list of grid ids sorted sorted_grid_ids = sorted(td_data['final_molecules'].keys()) # write scan.xyz and qdata.txt files target_mol = Molecule() target_mol.elem = fbmol.elem target_mol.xyzs = [] target_mol.qm_energies = [] target_mol.qm_grads = [] for grid_id in sorted_grid_ids: grid_qc_mol = td_data['final_molecules'][grid_id] # convert geometry unit Bohr -> Angstrom geo = grid_qc_mol.geometry * 0.529177 target_mol.xyzs.append(geo) # add energy and gradient target_mol.qm_energies.append( td_data['final_energies'][grid_id]) target_mol.qm_grads.append(td_data['final_gradients'][grid_id]) target_mol.write('scan.xyz') target_mol.write('qdata.txt') # check if the torsion scan contains one or more conformers forming strong internal H bonds no_hbonds, hbonds = screening_Hbond(mol2_fnm='input.mol2', scan_fnm='scan.xyz') if no_hbonds != True: msg = 'One or more internal H bonds exist.' if not os.path.exists('../error_mol2s'): os.mkdir('../error_mol2s') shutil.move(f'input.mol2', f'../error_mol2s/{target_name}.mol2') with open(f'../error_mol2s/{target_name}_error.txt', 'w') as notefile: notefile.write( f'{dataset_name}\ntarget_name {target_name}\n') notefile.write( f'entry {entry_index}\ntd_keywords {td_data["keywords"]}\n' ) notefile.write(f'error message:\n{msg}') # remove this folder os.chdir('..') shutil.rmtree(target_name) else: # pick metadata to write into the metadata.json file metadata = copy.deepcopy(td_data['keywords']) metadata['dataset_name'] = dataset_name metadata['entry_label'] = entry_index metadata['canonical_smiles'] = td_data['attributes'].get( 'canonical_smiles', 'unknown') metadata['torsion_grid_ids'] = sorted_grid_ids # find SMIRKs for torsion being scaned if test_ff is provided if test_ff: metadata['smirks'] = [] metadata['smirks_ids'] = [] for torsion_indices in td_data['keywords']['dihedrals']: param = molecule_labels['ProperTorsions'][tuple( torsion_indices)] metadata['smirks'].append(param.smirks) metadata['smirks_ids'].append(param.id) with open('metadata.json', 'w') as jsonfile: json.dump(metadata, jsonfile, indent=2) # finish this target target_names.append(target_name) os.chdir('..') target_idx += 1 # write targets.{dataset_name}.in file target_in_fnm = f"targets.{dataset_name.replace(' ', '_')}.in" with open(target_in_fnm, 'w') as outfile: for target_name in target_names: outfile.write(target_in_str.format(name=target_name)) print(f"Successfull generated {len(target_names)} targets.") print( f"You can copy contents in {target_in_fnm} to your ForceBalance input file." ) os.chdir('..')
# Psi4 output file. psiout = sys.argv[1] # Mode number, starting from 1. modenum = int(sys.argv[2]) frqs, modes, elem, xyz = read_frq_psi(psiout) M = Molecule() M.elem = elem[:] M.xyzs = [] xmode = modes[modenum - 1] xmode /= np.linalg.norm(xmode) / np.sqrt(M.na) xmode *= 0.3 # Reasonable vibrational amplitude spac = np.linspace(0, 1, 101) disp = np.concatenate((spac, spac[::-1][1:], -1 * spac[1:], -1 * spac[::-1][1:-1])) for i in disp: M.xyzs.append(xyz + i * xmode) M.comms = [ "Vibrational Mode %i Frequency %.3f Displacement %.3f" % (modenum, frqs[modenum - 1], disp[i] * (np.linalg.norm(xmode) / np.sqrt(M.na))) for i in range(len(M)) ] M.write(os.path.splitext(psiout)[0] + ".mode%03i.xyz" % modenum)
#!/usr/bin/env python from builtins import range from forcebalance.molecule import Molecule import os, sys # Load in the Gromacs .gro file to be converted to TINKER format. M = Molecule(sys.argv[1]) # Build the line suffix for the TINKER format. tinkersuf = [] for i in range(M.na): if i%3==0: tinkersuf.append("%5i %5i %5i" % (1, i+2, i+3)) else: tinkersuf.append("%5i %5i" % (2, i-i%3+1)) M.tinkersuf = tinkersuf # Delete the periodic box. del M.Data['boxes'] # Write the TINKER output format. M.write(os.path.splitext(sys.argv[1])[0]+'.xyz', ftype='tinker')
def make_vib_freq_target(dataset_name, hessian_data, test_ff=None): """ Make a folder of ForceBalance targets from the torsiondrive data """ target_name_prefix = 'vibfreq_' + dataset_name.replace(' ', '_') # create new targets folder if os.path.exists('targets'): shutil.rmtree('targets') os.mkdir('targets') os.chdir('targets') # write each entry as an individual target target_idx = 0 n_targets = len(hessian_data) idx_fmt_string = get_int_fmt_string(n_targets) target_names = [] print(f"Generating {n_targets} targets") for entry_index, data in hessian_data.items(): # get formula for the molecule qcmol = data['molecule'] # get mol_formula mol_formula = qcmol.get_molecular_formula() # create target folder target_idx_str = idx_fmt_string.format(target_idx) target_name = f"{target_name_prefix}_{target_idx_str}_{mol_formula}" print(f"{target_idx_str}: {target_name:70s} - ", end='') os.mkdir(target_name) os.chdir(target_name) # write a note with open('note.txt', 'w') as notefile: notefile.write( f'Target generated from dataset {dataset_name}, entry {entry_index}' ) # write input.mol2 file qcjson_mol = qcmol.dict(encoding='json') oemol = cmiles.utils.load_molecule(qcjson_mol) ofs.open('input.mol2') oechem.OEWriteMolecule(ofs, oemol) ofs.close() # test mol2 file success = True # test if bonds changed if not check_connectivity('input.mol2'): success = False msg = "Bonds changed after rebuild" if success and test_ff != None: success, msg, molecule_labels = test_ff_mol2(test_ff, 'input.mol2') if not success: if not os.path.exists('../error_mol2s'): os.mkdir('../error_mol2s') shutil.move(f'input.mol2', f'../error_mol2s/{target_name}.mol2') with open(f'../error_mol2s/{target_name}_error.txt', 'w') as notefile: notefile.write(f'{dataset_name}\ntarget_name {target_name}\n') notefile.write(f'entry {entry_index}\n') notefile.write(f'error message:\n{msg}') # remove this folder os.chdir('..') shutil.rmtree(target_name) print("Error: " + msg.replace('\n', ';')) else: # write conf.pdb file fbmol = Molecule(f'input.mol2') fbmol.write(f'conf.pdb') # write vdata.txt for this target create_vdata_txt('conf.pdb', data) print(f'Success') # finish this target target_names.append(target_name) os.chdir('..') target_idx += 1 # write targets.{dataset_name}.in file target_in_fnm = f"targets.{dataset_name.replace(' ', '_')}.in" with open(target_in_fnm, 'w') as outfile: for target_name in target_names: outfile.write(target_in_str.format(name=target_name)) print(f"Successfull generated {len(target_names)} targets.") print( f"You can copy contents in {target_in_fnm} to your ForceBalance input file." ) os.chdir('..')
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 Lipid(Target): """ Subclass of Target for lipid property matching.""" def __init__(self,options,tgt_opts,forcefield): # Initialize base class super(Lipid,self).__init__(options,tgt_opts,forcefield) # Weight of the density self.set_option(tgt_opts,'w_rho',forceprint=True) # Weight of the thermal expansion coefficient self.set_option(tgt_opts,'w_alpha',forceprint=True) # Weight of the isothermal compressibility self.set_option(tgt_opts,'w_kappa',forceprint=True) # Weight of the isobaric heat capacity self.set_option(tgt_opts,'w_cp',forceprint=True) # Weight of the dielectric constant self.set_option(tgt_opts,'w_eps0',forceprint=True) # Weight of the area per lipid self.set_option(tgt_opts,'w_al',forceprint=True) # Weight of the bilayer isothermal compressibility self.set_option(tgt_opts,'w_lkappa',forceprint=True) # Weight of the deuterium order parameter self.set_option(tgt_opts,'w_scd',forceprint=True) # Normalize the property contributions to the objective function self.set_option(tgt_opts,'w_normalize',forceprint=True) # Optionally pause on the zeroth step self.set_option(tgt_opts,'manual') # Number of time steps in the lipid "equilibration" run self.set_option(tgt_opts,'lipid_eq_steps',forceprint=True) # Number of time steps in the lipid "production" run self.set_option(tgt_opts,'lipid_md_steps',forceprint=True) # Number of time steps in the gas "equilibration" run self.set_option(tgt_opts,'gas_eq_steps',forceprint=False) # Number of time steps in the gas "production" run self.set_option(tgt_opts,'gas_md_steps',forceprint=False) # Cutoff for nonbonded interactions in the liquid if tgt_opts['nonbonded_cutoff'] is not None: self.set_option(tgt_opts,'nonbonded_cutoff') # Cutoff for vdW interactions if different from other nonbonded interactions if tgt_opts['vdw_cutoff'] is not None: self.set_option(tgt_opts,'vdw_cutoff') # Time step length (in fs) for the lipid production run self.set_option(tgt_opts,'lipid_timestep',forceprint=True) # Time interval (in ps) for writing coordinates self.set_option(tgt_opts,'lipid_interval',forceprint=True) # Time step length (in fs) for the gas production run self.set_option(tgt_opts,'gas_timestep',forceprint=True) # Time interval (in ps) for writing coordinates self.set_option(tgt_opts,'gas_interval',forceprint=True) # Minimize the energy prior to running any dynamics self.set_option(tgt_opts,'minimize_energy',forceprint=True) # Isolated dipole (debye) for analytic self-polarization correction. self.set_option(tgt_opts,'self_pol_mu0',forceprint=True) # Molecular polarizability (ang**3) for analytic self-polarization correction. self.set_option(tgt_opts,'self_pol_alpha',forceprint=True) # Set up the simulation object for self-polarization correction. self.do_self_pol = (self.self_pol_mu0 > 0.0 and self.self_pol_alpha > 0.0) # Enable anisotropic periodic box self.set_option(tgt_opts,'anisotropic_box',forceprint=True) # Whether to save trajectories (0 = never, 1 = delete after good step, 2 = keep all) self.set_option(tgt_opts,'save_traj') #======================================# # Variables which are set here # #======================================# ## 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 # List of trajectory files that may be deleted if self.save_traj == 1. self.last_traj = [] # Extra files to be copied back at the end of a run. self.extra_output = [] # Read the reference data self.read_data() # Read in lipid starting coordinates. if 'n_ic' in self.RefData: # Linked IC folder into the temp-directory. self.nptfiles += ["IC"] # Store IC frames in a dictionary. self.lipid_mols = OrderedDict() self.lipid_mols_new = OrderedDict() for pt in self.PhasePoints: pt_label = "IC/%sK-%s%s" % (pt[0], pt[1], pt[2]) if not os.path.exists(os.path.join(self.root, self.tgtdir, pt_label, self.lipid_coords)): raise RuntimeError("Initial condition files don't exist; please provide IC directory") # Create molecule object for each IC. all_ic = Molecule(os.path.join(self.root, self.tgtdir, pt_label, self.lipid_coords)) self.lipid_mols[pt] = [] n_uniq_ic = int(self.RefData['n_ic'][pt]) if n_uniq_ic > len(all_ic): raise RuntimeError("Number of frames in initial conditions .gro file is less than the number of parallel simulations requested in data.csv") # Index ICs by pressure and temperature in a dictionary. for ic in range(n_uniq_ic): self.lipid_mols[pt].append(all_ic[ic]) else: # Read in lipid starting coordinates. if not os.path.exists(os.path.join(self.root, self.tgtdir, self.lipid_coords)): logger.error("%s doesn't exist; please provide lipid_coords option\n" % self.lipid_coords) raise RuntimeError self.lipid_mol = Molecule(os.path.join(self.root, self.tgtdir, self.lipid_coords), toppbc=True) # Extra files to be linked into the temp-directory. self.nptfiles += [self.lipid_coords] # Scripts to be copied from the ForceBalance installation directory. self.scripts += ['npt_lipid.py'] # Prepare the temporary directory. self.prepare_temp_directory() # Build keyword dictionary to pass to engine. if self.do_self_pol: self.gas_engine_args.update(self.OptionDict) self.gas_engine_args.update(options) del self.gas_engine_args['name'] # Create engine object for gas molecule to do the polarization correction. self.gas_engine = self.engine_(target=self, mol=self.gas_mol, name="selfpol", **self.gas_engine_args) # Don't read indicate.log when calling meta_indicate() self.read_indicate = False self.write_indicate = False # Don't read objective.p when calling meta_get() self.read_objective = False #======================================# # UNDER DEVELOPMENT # #======================================# # Put stuff here that I'm not sure about. :) np.set_printoptions(precision=4, linewidth=100) np.seterr(under='ignore') ## Saved force field mvals for all iterations self.SavedMVal = {} ## Saved trajectories for all iterations and all temperatures self.SavedTraj = defaultdict(dict) ## Evaluated energies for all trajectories (i.e. all iterations and all temperatures), using all mvals self.MBarEnergy = defaultdict(lambda:defaultdict(dict)) def prepare_temp_directory(self): """ Prepare the temporary directory by copying in important files. """ abstempdir = os.path.join(self.root,self.tempdir) for f in self.nptfiles: LinkFile(os.path.join(self.root, self.tgtdir, f), os.path.join(abstempdir, f)) for f in self.scripts: LinkFile(os.path.join(os.path.split(__file__)[0],"data",f),os.path.join(abstempdir,f)) def read_data(self): # Read the 'data.csv' file. The file should contain guidelines. with open(os.path.join(self.tgtdir,'data.csv'),'rU') as f: R0 = list(csv.reader(f)) # All comments are erased. R1 = [[sub('#.*$','',word) for word in line] for line in R0 if len(line[0]) > 0 and line[0][0] != "#"] # All empty lines are deleted and words are converted to lowercase. R = [[wrd.lower() for wrd in line] for line in R1 if any([len(wrd) for wrd in line]) > 0] global_opts = OrderedDict() found_headings = False known_vars = ['mbar','rho','hvap','alpha','kappa','cp','eps0','cvib_intra', 'cvib_inter','cni','devib_intra','devib_inter', 'al', 'scd', 'n_ic', 'lkappa'] self.RefData = OrderedDict() for line in R: if line[0] == "global": # Global options are mainly denominators for the different observables. if isfloat(line[2]): global_opts[line[1]] = float(line[2]) elif line[2].lower() == 'false': global_opts[line[1]] = False elif line[2].lower() == 'true': global_opts[line[1]] = True elif not found_headings: found_headings = True headings = line if len(set(headings)) != len(headings): logger.error('Column headings in data.csv must be unique\n') raise RuntimeError if 'p' not in headings: logger.error('There must be a pressure column heading labeled by "p" in data.csv\n') raise RuntimeError if 't' not in headings: logger.error('There must be a temperature column heading labeled by "t" in data.csv\n') raise RuntimeError elif found_headings: try: # Temperatures are in kelvin. t = [float(val) for head, val in zip(headings,line) if head == 't'][0] # For convenience, users may input the pressure in atmosphere or bar. pval = [float(val.split()[0]) for head, val in zip(headings,line) if head == 'p'][0] punit = [val.split()[1] if len(val.split()) >= 1 else "atm" for head, val in zip(headings,line) if head == 'p'][0] unrec = set([punit]).difference(['atm','bar']) if len(unrec) > 0: logger.error('The pressure unit %s is not recognized, please use bar or atm\n' % unrec[0]) raise RuntimeError # This line actually reads the reference data and inserts it into the RefData dictionary of dictionaries. for head, val in zip(headings,line): if head == 't' or head == 'p' : continue if isfloat(val): self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = float(val.strip()) elif val.lower() == 'true': self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = True elif val.lower() == 'false': self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = False elif head == 'scd': self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = np.array(list(map(float, val.split()))) except: logger.error(line + '\n') logger.error('Encountered an error reading this line!\n') raise RuntimeError else: logger.error(line + '\n') logger.error('I did not recognize this line!\n') raise RuntimeError # Check the reference data table for validity. default_denoms = defaultdict(int) PhasePoints = None RefData_copy = copy.deepcopy(self.RefData) for head in self.RefData: if head == 'n_ic': continue if head not in known_vars+[i+"_wt" for i in known_vars]: # Only hard-coded properties may be recognized. logger.error("The column heading %s is not recognized in data.csv\n" % head) raise RuntimeError if head in known_vars: if head+"_wt" not in self.RefData: # If the phase-point weights are not specified in the reference data file, initialize them all to one. RefData_copy[head+"_wt"] = OrderedDict([(key, 1.0) for key in self.RefData[head]]) wts = np.array(list(RefData_copy[head+"_wt"].values())) dat = np.array(list(self.RefData[head].values())) # S_cd specifies an array of averages (one for each tail node). Find avg over axis 0. avg = np.average(dat, weights=wts, axis=0) if len(wts) > 1: # If there is more than one data point, then the default denominator is the # standard deviation of the experimental values. if head == 'scd': default_denoms[head+"_denom"] = np.average(np.sqrt(np.dot(wts, (dat-avg)**2)/wts.sum())) else: default_denoms[head+"_denom"] = np.sqrt(np.dot(wts, (dat-avg)**2)/wts.sum()) else: # If there is only one data point, then the denominator is just the single # data point itself. if head == 'scd': default_denoms[head+"_denom"] = np.average(np.sqrt(np.abs(dat[0]))) else: default_denoms[head+"_denom"] = np.sqrt(np.abs(dat[0])) self.PhasePoints = list(self.RefData[head].keys()) # This prints out all of the reference data. # printcool_dictionary(self.RefData[head],head) self.RefData = RefData_copy # Create labels for the directories. self.Labels = ["%.2fK-%.1f%s" % i for i in self.PhasePoints] logger.debug("global_opts:\n%s\n" % str(global_opts)) logger.debug("default_denoms:\n%s\n" % str(default_denoms)) for opt in global_opts: if "_denom" in opt: # Record entries from the global_opts dictionary so they can be retrieved from other methods. self.set_option(global_opts,opt,default=default_denoms[opt]) else: self.set_option(global_opts,opt) def check_files(self, there): there = os.path.abspath(there) havepts = 0 if all([i in os.listdir(there) for i in self.Labels]): for d in os.listdir(there): if d in self.Labels: if os.path.exists(os.path.join(there, d, 'npt_result.p')): havepts += 1 if (float(havepts)/len(self.Labels)) > 0.75: return 1 else: return 0 def npt_simulation(self, temperature, pressure, simnum): """ Submit a NPT simulation to the Work Queue. """ wq = getWorkQueue() if not os.path.exists('npt_result.p'): link_dir_contents(os.path.join(self.root,self.rundir),os.getcwd()) self.last_traj += [os.path.join(os.getcwd(), i) for i in self.extra_output] self.lipid_mol[simnum%len(self.lipid_mol)].write(self.lipid_coords, ftype='tinker' if self.engname == 'tinker' else None) cmdstr = '%s python npt_lipid.py %s %.3f %.3f' % (self.nptpfx, self.engname, temperature, pressure) if wq is None: logger.info("Running condensed phase simulation locally.\n") logger.info("You may tail -f %s/npt.out in another terminal window\n" % os.getcwd()) _exec(cmdstr, copy_stderr=True, outfnm='npt.out') else: queue_up(wq, command = cmdstr+' &> npt.out', input_files = self.nptfiles + self.scripts + ['forcebalance.p'], output_files = ['npt_result.p', 'npt.out'] + self.extra_output, tgt=self) def polarization_correction(self,mvals): d = self.gas_engine.get_multipole_moments(optimize=True)['dipole'] if not in_fd(): logger.info("The molecular dipole moment is % .3f debye\n" % np.linalg.norm(d)) # Taken from the original OpenMM interface code, this is how we calculate the conversion factor. # dd2 = ((np.linalg.norm(d)-self.self_pol_mu0)*debye)**2 # eps0 = 8.854187817620e-12 * coulomb**2 / newton / meter**2 # epol = 0.5*dd2/(self.self_pol_alpha*angstrom**3*4*np.pi*eps0)/(kilojoule_per_mole/AVOGADRO_CONSTANT_NA) # In [2]: eps0 = 8.854187817620e-12 * coulomb**2 / newton / meter**2 # In [7]: 1.0 * debye ** 2 / (1.0 * angstrom**3*4*np.pi*eps0) / (kilojoule_per_mole/AVOGADRO_CONSTANT_NA) # Out[7]: 60.240179789402056 convert = 60.240179789402056 dd2 = (np.linalg.norm(d)-self.self_pol_mu0)**2 epol = 0.5*convert*dd2/self.self_pol_alpha return epol def indicate(self): AGrad = hasattr(self, 'Gp') PrintDict = OrderedDict() def print_item(key, heading, physunit): if self.Xp[key] > 0: printcool_dictionary(self.Pp[key], title='%s %s%s\nTemperature Pressure Reference Calculated +- Stdev Delta Weight Term ' % (self.name, heading, " (%s) " % physunit if physunit else ""), bold=True, color=4, keywidth=15) bar = printcool("%s objective function: % .3f%s" % (heading, self.Xp[key], ", Derivative:" if AGrad else "")) if AGrad: self.FF.print_map(vals=self.Gp[key]) logger.info(bar) PrintDict[heading] = "% 10.5f % 8.3f % 14.5e" % (self.Xp[key], self.Wp[key], self.Xp[key]*self.Wp[key]) print_item("Rho", "Density", "kg m^-3") print_item("Alpha", "Thermal Expansion Coefficient", "10^-4 K^-1") print_item("Kappa", "Isothermal Compressibility", "10^-6 bar^-1") print_item("Cp", "Isobaric Heat Capacity", "cal mol^-1 K^-1") print_item("Eps0", "Dielectric Constant", None) print_item("Al", "Average Area per Lipid", "nm^2") print_item("Scd", "Deuterium Order Parameter", None) print_item("LKappa", "Bilayer Isothermal Compressibility", "mN/m") PrintDict['Total'] = "% 10s % 8s % 14.5e" % ("","",self.Objective) Title = "%s Condensed Phase Properties:\n %-20s %40s" % (self.name, "Property Name", "Residual x Weight = Contribution") printcool_dictionary(PrintDict,color=4,title=Title,keywidth=31) return def objective_term(self, points, expname, calc, err, grad, name="Quantity", SubAverage=False): if expname in self.RefData: exp = self.RefData[expname] Weights = self.RefData[expname+"_wt"] Denom = getattr(self,expname+"_denom") else: # If the reference data doesn't exist then return nothing. return 0.0, np.zeros(self.FF.np), np.zeros((self.FF.np,self.FF.np)), None Sum = sum(Weights.values()) for i in Weights: Weights[i] /= Sum logger.info("Weights have been renormalized to " + str(sum(Weights.values())) + "\n") # Use least-squares or hyperbolic (experimental) objective. LeastSquares = True logger.info("Physical quantity %s uses denominator = % .4f\n" % (name, Denom)) if not LeastSquares: # If using a hyperbolic functional form # we still want the contribution to the # objective function to be the same when # Delta = Denom. Denom /= 3 ** 0.5 Objective = 0.0 Gradient = np.zeros(self.FF.np) Hessian = np.zeros((self.FF.np,self.FF.np)) Objs = {} GradMap = [] avgCalc = 0.0 avgExp = 0.0 avgGrad = np.zeros(self.FF.np) for i, PT in enumerate(points): avgCalc += Weights[PT]*calc[PT] avgExp += Weights[PT]*exp[PT] avgGrad += Weights[PT]*grad[PT] for i, PT in enumerate(points): if SubAverage: G = grad[PT]-avgGrad Delta = calc[PT] - exp[PT] - avgCalc + avgExp else: G = grad[PT] Delta = calc[PT] - exp[PT] if hasattr(Delta, "__len__"): Delta = np.average(Delta) if LeastSquares: # Least-squares objective function. ThisObj = Weights[PT] * Delta ** 2 / Denom**2 Objs[PT] = ThisObj ThisGrad = 2.0 * Weights[PT] * Delta * G / Denom**2 GradMap.append(G) Objective += ThisObj Gradient += ThisGrad # Gauss-Newton approximation to the Hessian. Hessian += 2.0 * Weights[PT] * (np.outer(G, G)) / Denom**2 else: # L1-like objective function. D = Denom S = Delta**2 + D**2 ThisObj = Weights[PT] * (S**0.5-D) / Denom ThisGrad = Weights[PT] * (Delta/S**0.5) * G / Denom ThisHess = Weights[PT] * (1/S**0.5-Delta**2/S**1.5) * np.outer(G,G) / Denom Objs[PT] = ThisObj GradMap.append(G) Objective += ThisObj Gradient += ThisGrad Hessian += ThisHess GradMapPrint = [["#PhasePoint"] + self.FF.plist] for PT, g in zip(points,GradMap): GradMapPrint.append([' %8.2f %8.1f %3s' % PT] + ["% 9.3e" % i for i in g]) o = wopen('gradient_%s.dat' % name) for line in GradMapPrint: print(' '.join(line), file=o) o.close() Delta = np.array([calc[PT] - exp[PT] for PT in points]) delt = {PT : r for PT, r in zip(points,Delta)} if expname == 'scd': print_out = OrderedDict([(' %8.2f %8.1f %3s' % PT, '\n %s' % (' '.join('\t \t \t %9.6f %9.6f +- %-7.6f % 7.6f \n' % F for F in zip(exp[PT], calc[PT], flat(err[PT]), delt[PT])))) for PT in calc]) else: print_out = OrderedDict([(' %8.2f %8.1f %3s' % PT, "%9.3f %9.3f +- %-7.3f % 7.3f % 9.5f % 9.5f" % (exp[PT],calc[PT],err[PT],delt[PT],Weights[PT],Objs[PT])) for PT in calc]) return Objective, Gradient, Hessian, print_out def submit_jobs(self, mvals, AGrad=True, AHess=True): # This routine is called by Objective.stage() will run before "get". # It submits the jobs to the Work Queue and the stage() function will wait for jobs to complete. # # First dump the force field to a pickle file lp_dump((self.FF,mvals,self.OptionDict,AGrad),'forcebalance.p') # Give the user an opportunity to copy over data from a previous (perhaps failed) run. if (not self.evaluated) and self.manual: warn_press_key("Now's our chance to fill the temp directory up with data!\n(Considering using 'read' or 'continue' for better checkpointing)", timeout=7200) # If self.save_traj == 1, delete the trajectory files from a previous good optimization step. if self.evaluated and self.goodstep and self.save_traj < 2: for fn in self.last_traj: if os.path.exists(fn): os.remove(fn) self.last_traj = [] # Set up and run the NPT simulations. snum = 0 for label, pt in zip(self.Labels, self.PhasePoints): T = pt[0] P = pt[1] Punit = pt[2] if Punit == 'bar': P *= 1.0 / 1.01325 if not os.path.exists(label): os.makedirs(label) os.chdir(label) if 'n_ic' in self.RefData: n_uniq_ic = int(self.RefData['n_ic'][pt]) # Loop over parallel trajectories. for trj in range(n_uniq_ic): rel_trj = "trj_%i" % trj # Create directories for each parallel simulation. if not os.path.exists(rel_trj): os.makedirs(rel_trj) os.chdir(rel_trj) # Pull each simulation molecule from the lipid_mols dictionary. # lipid_mols is a dictionary of paths to either the initial # geometry files, or the geometries from the final frame of the # previous iteration. self.lipid_mol = self.lipid_mols[pt][trj] self.lipid_mol.write(self.lipid_coords) if not self.lipid_coords in self.nptfiles: self.nptfiles += [self.lipid_coords] self.npt_simulation(T,P,snum) os.chdir('..') else: self.npt_simulation(T,P,snum) os.chdir('..') snum += 1 def get(self, mvals, AGrad=True, AHess=True): """ Fitting of lipid bulk properties. This is the current major direction of development for ForceBalance. Basically, fitting the QM energies / forces alone does not always give us the best simulation behavior. In many cases it makes more sense to try and reproduce some experimentally known data as well. In order to reproduce experimentally known data, we need to run a simulation and compare the simulation result to experiment. The main challenge here is that the simulations are computationally intensive (i.e. they require energy and force evaluations), and furthermore the results are noisy. We need to run the simulations automatically and remotely (i.e. on clusters) and a good way to calculate the derivatives of the simulation results with respect to the parameter values. This function contains some experimentally known values of the density and enthalpy of vaporization (Hvap) of lipid water. It launches the density and Hvap calculations on the cluster, and gathers the results / derivatives. The actual calculation of results / derivatives is done in a separate file. After the results come back, they are gathered together to form an objective function. @param[in] mvals Mathematical parameter values @param[in] AGrad Switch to turn on analytic gradient @param[in] AHess Switch to turn on analytic Hessian @return Answer Contribution to the objective function """ mbar_verbose = False Answer = {} Results = {} Points = [] # These are the phase points for which data exists. BPoints = [] # These are the phase points for which we are doing MBAR for the condensed phase. tt = 0 for label, PT in zip(self.Labels, self.PhasePoints): if 'n_ic' in self.RefData: self.lipid_mols[PT] = [Molecule(last_frame) for last_frame in self.lipid_mols[PT]] n_uniq_ic = int(self.RefData['n_ic'][PT]) for ic in range(n_uniq_ic): if os.path.exists('./%s/trj_%s/npt_result.p' % (label, ic)): # Read in each each parallel simulation's data, and concatenate each property time series. ts = lp_load('./%s/trj_%s/npt_result.p' % (label, ic)) if ic == 0: ts_concat = list(ts) else: for d_arr in range(len(ts)): if isinstance(ts[d_arr], np.ndarray): # Gradients need a unique append format. if d_arr == 5: ts_concat[d_arr] = np.append(ts_concat[d_arr], ts[d_arr], axis = 1) else: ts_concat[d_arr] = np.append(ts_concat[d_arr], ts[d_arr], axis = 0) if isinstance(ts_concat[d_arr], list): ts_concat[d_arr] = [np.append(ts_concat[d_arr][i], ts[d_arr][i], axis = 1) for i in range(len(ts_concat[d_arr]))] # Write concatenated time series to a pickle file. if ic == (int(n_uniq_ic) - 1): lp_dump((ts_concat), './%s/npt_result.p' % label) if os.path.exists('./%s/npt_result.p' % label): logger.info('Reading information from ./%s/npt_result.p\n' % label) Points.append(PT) Results[tt] = lp_load('./%s/npt_result.p' % label) tt += 1 else: logger.warning('The file ./%s/npt_result.p does not exist so we cannot read it\n' % label) pass # for obs in self.RefData: # del self.RefData[obs][PT] if len(Points) == 0: logger.error('The lipid simulations have terminated with \x1b[1;91mno readable data\x1b[0m - this is a problem!\n') raise RuntimeError # Assign variable names to all the stuff in npt_result.p Rhos, Vols, Potentials, Energies, Dips, Grads, GDips, \ Rho_errs, Alpha_errs, Kappa_errs, Cp_errs, Eps0_errs, NMols, Als, Al_errs, Scds, Scd_errs, LKappa_errs = ([Results[t][i] for t in range(len(Points))] for i in range(18)) # Determine the number of molecules if len(set(NMols)) != 1: logger.error(str(NMols)) logger.error('The above list should only contain one number - the number of molecules\n') raise RuntimeError else: NMol = list(set(NMols))[0] R = np.array(list(itertools.chain(*list(Rhos)))) V = np.array(list(itertools.chain(*list(Vols)))) E = np.array(list(itertools.chain(*list(Energies)))) Dx = np.array(list(itertools.chain(*list(d[:,0] for d in Dips)))) Dy = np.array(list(itertools.chain(*list(d[:,1] for d in Dips)))) Dz = np.array(list(itertools.chain(*list(d[:,2] for d in Dips)))) G = np.hstack(tuple(Grads)) GDx = np.hstack(tuple(gd[0] for gd in GDips)) GDy = np.hstack(tuple(gd[1] for gd in GDips)) GDz = np.hstack(tuple(gd[2] for gd in GDips)) A = np.array(list(itertools.chain(*list(Als)))) S = np.array(list(itertools.chain(*list(Scds)))) Rho_calc = OrderedDict([]) Rho_grad = OrderedDict([]) Rho_std = OrderedDict([]) Alpha_calc = OrderedDict([]) Alpha_grad = OrderedDict([]) Alpha_std = OrderedDict([]) Kappa_calc = OrderedDict([]) Kappa_grad = OrderedDict([]) Kappa_std = OrderedDict([]) Cp_calc = OrderedDict([]) Cp_grad = OrderedDict([]) Cp_std = OrderedDict([]) Eps0_calc = OrderedDict([]) Eps0_grad = OrderedDict([]) Eps0_std = OrderedDict([]) Al_calc = OrderedDict([]) Al_grad = OrderedDict([]) Al_std = OrderedDict([]) LKappa_calc = OrderedDict([]) LKappa_grad = OrderedDict([]) LKappa_std = OrderedDict([]) Scd_calc = OrderedDict([]) Scd_grad = OrderedDict([]) Scd_std = OrderedDict([]) # The unit that converts atmospheres * nm**3 into kj/mol :) pvkj=0.061019351687175 # Run MBAR using the total energies. Required for estimates that use the kinetic energy. BSims = len(BPoints) Shots = len(Energies[0]) Shots_m = [len(i) for i in Energies] N_k = np.ones(BSims)*Shots # Use the value of the energy for snapshot t from simulation k at potential m U_kln = np.zeros([BSims,BSims,Shots]) for m, PT in enumerate(BPoints): T = PT[0] P = PT[1] / 1.01325 if PT[2] == 'bar' else PT[1] beta = 1. / (kb * T) for k in range(BSims): # The correct Boltzmann factors include PV. # Note that because the Boltzmann factors are computed from the conditions at simulation "m", # the pV terms must be rescaled to the pressure at simulation "m". kk = Points.index(BPoints[k]) U_kln[k, m, :] = Energies[kk] + P*Vols[kk]*pvkj U_kln[k, m, :] *= beta W1 = None if len(BPoints) > 1: logger.info("Running MBAR analysis on %i states...\n" % len(BPoints)) mbar = pymbar.MBAR(U_kln, N_k, verbose=mbar_verbose, relative_tolerance=5.0e-8) W1 = mbar.getWeights() logger.info("Done\n") elif len(BPoints) == 1: W1 = np.ones((BPoints*Shots,BPoints)) W1 /= BPoints*Shots def fill_weights(weights, phase_points, mbar_points, snapshots): """ Fill in the weight matrix with MBAR weights where MBAR was run, and equal weights otherwise. """ new_weights = np.zeros([len(phase_points)*snapshots,len(phase_points)]) for m, PT in enumerate(phase_points): if PT in mbar_points: mm = mbar_points.index(PT) for kk, PT1 in enumerate(mbar_points): k = phase_points.index(PT1) logger.debug("Will fill W2[%i:%i,%i] with W1[%i:%i,%i]\n" % (k*snapshots,k*snapshots+snapshots,m,kk*snapshots,kk*snapshots+snapshots,mm)) new_weights[k*snapshots:(k+1)*snapshots,m] = weights[kk*snapshots:(kk+1)*snapshots,mm] else: logger.debug("Will fill W2[%i:%i,%i] with equal weights\n" % (m*snapshots,(m+1)*snapshots,m)) new_weights[m*snapshots:(m+1)*snapshots,m] = 1.0/snapshots return new_weights W2 = fill_weights(W1, Points, BPoints, Shots) if self.do_self_pol: EPol = self.polarization_correction(mvals) GEPol = np.array([(f12d3p(fdwrap(self.polarization_correction, mvals, p), h = self.h, f0 = EPol)[0] if p in self.pgrad else 0.0) for p in range(self.FF.np)]) bar = printcool("Self-polarization correction to \nenthalpy of vaporization is % .3f kJ/mol%s" % (EPol, ", Derivative:" if AGrad else "")) if AGrad: self.FF.print_map(vals=GEPol) logger.info(bar) for i, PT in enumerate(Points): T = PT[0] P = PT[1] / 1.01325 if PT[2] == 'bar' else PT[1] PV = P*V*pvkj H = E + PV # The weights that we want are the last ones. W = flat(W2[:,i]) C = weight_info(W, PT, np.ones(len(Points), dtype=int)*Shots, verbose=mbar_verbose) Gbar = flat(np.dot(G,col(W))) mBeta = -1/kb/T Beta = 1/kb/T kT = kb*T # Define some things to make the analytic derivatives easier. def avg(vec): return np.dot(W,vec) def covde(vec): return flat(np.dot(G,col(W*vec))) - avg(vec)*Gbar def deprod(vec): return flat(np.dot(G,col(W*vec))) ## Density. Rho_calc[PT] = np.dot(W,R) Rho_grad[PT] = mBeta*(flat(np.dot(G,col(W*R))) - np.dot(W,R)*Gbar) ## Ignore enthalpy. ## Thermal expansion coefficient. Alpha_calc[PT] = 1e4 * (avg(H*V)-avg(H)*avg(V))/avg(V)/(kT*T) GAlpha1 = -1 * Beta * deprod(H*V) * avg(V) / avg(V)**2 GAlpha2 = +1 * Beta * avg(H*V) * deprod(V) / avg(V)**2 GAlpha3 = deprod(V)/avg(V) - Gbar GAlpha4 = Beta * covde(H) Alpha_grad[PT] = 1e4 * (GAlpha1 + GAlpha2 + GAlpha3 + GAlpha4)/(kT*T) ## Isothermal compressibility. bar_unit = 0.06022141793 * 1e6 Kappa_calc[PT] = bar_unit / kT * (avg(V**2)-avg(V)**2)/avg(V) GKappa1 = +1 * Beta**2 * avg(V**2) * deprod(V) / avg(V)**2 GKappa2 = -1 * Beta**2 * avg(V) * deprod(V**2) / avg(V)**2 GKappa3 = +1 * Beta**2 * covde(V) Kappa_grad[PT] = bar_unit*(GKappa1 + GKappa2 + GKappa3) ## Isobaric heat capacity. Cp_calc[PT] = 1000/(4.184*NMol*kT*T) * (avg(H**2) - avg(H)**2) if hasattr(self,'use_cvib_intra') and self.use_cvib_intra: logger.debug("Adding " + str(self.RefData['devib_intra'][PT]) + " to the heat capacity\n") Cp_calc[PT] += self.RefData['devib_intra'][PT] if hasattr(self,'use_cvib_inter') and self.use_cvib_inter: logger.debug("Adding " + str(self.RefData['devib_inter'][PT]) + " to the heat capacity\n") Cp_calc[PT] += self.RefData['devib_inter'][PT] GCp1 = 2*covde(H) * 1000 / 4.184 / (NMol*kT*T) GCp2 = mBeta*covde(H**2) * 1000 / 4.184 / (NMol*kT*T) GCp3 = 2*Beta*avg(H)*covde(H) * 1000 / 4.184 / (NMol*kT*T) Cp_grad[PT] = GCp1 + GCp2 + GCp3 ## Static dielectric constant. prefactor = 30.348705333964077 D2 = avg(Dx**2)+avg(Dy**2)+avg(Dz**2)-avg(Dx)**2-avg(Dy)**2-avg(Dz)**2 Eps0_calc[PT] = 1.0 + prefactor*(D2/avg(V))/T GD2 = 2*(flat(np.dot(GDx,col(W*Dx))) - avg(Dx)*flat(np.dot(GDx,col(W)))) - Beta*(covde(Dx**2) - 2*avg(Dx)*covde(Dx)) GD2 += 2*(flat(np.dot(GDy,col(W*Dy))) - avg(Dy)*flat(np.dot(GDy,col(W)))) - Beta*(covde(Dy**2) - 2*avg(Dy)*covde(Dy)) GD2 += 2*(flat(np.dot(GDz,col(W*Dz))) - avg(Dz)*flat(np.dot(GDz,col(W)))) - Beta*(covde(Dz**2) - 2*avg(Dz)*covde(Dz)) Eps0_grad[PT] = prefactor*(GD2/avg(V) - mBeta*covde(V)*D2/avg(V)**2)/T ## Average area per lipid Al_calc[PT] = np.dot(W,A) Al_grad[PT] = mBeta*(flat(np.dot(G,col(W*A))) - np.dot(W,A)*Gbar) ## Bilayer Isothermal compressibility. A_m2 = A * 1e-18 kbT = 1.3806488e-23 * T LKappa_calc[PT] = (1e3 * 2 * kbT / 128) * (avg(A_m2) / (avg(A_m2**2)-avg(A_m2)**2)) al_avg = avg(A_m2) al_sq_avg = avg(A_m2**2) al_avg_sq = al_avg**2 al_var = al_sq_avg - al_avg_sq GLKappa1 = covde(A_m2) / al_var GLKappa2 = (al_avg / al_var**2) * (covde(A_m2**2) - (2 * al_avg * covde(A))) LKappa_grad[PT] = (1e3 * 2 * kbT / 128) * (GLKappa1 - GLKappa2) ## Deuterium order parameter Scd_calc[PT] = np.dot(W,S) # LPW: In case I did not do the conversion correctly, the line of code previously here was: # Scd_grad[PT] = mBeta * (flat(np.average(np.mat(G) * (S * W[:, np.newaxis]), axis = 1)) - np.average(np.average(S * W[:, np.newaxis], axis = 0), axis = 0) * Gbar) Scd_grad[PT] = mBeta * (flat(np.average(np.dot(G, (S * W[:, np.newaxis])), axis = 1)) - np.average(np.average(S * W[:, np.newaxis], axis = 0), axis = 0) * Gbar) ## Estimation of errors. Rho_std[PT] = np.sqrt(sum(C**2 * np.array(Rho_errs)**2)) Alpha_std[PT] = np.sqrt(sum(C**2 * np.array(Alpha_errs)**2)) * 1e4 Kappa_std[PT] = np.sqrt(sum(C**2 * np.array(Kappa_errs)**2)) * 1e6 Cp_std[PT] = np.sqrt(sum(C**2 * np.array(Cp_errs)**2)) Eps0_std[PT] = np.sqrt(sum(C**2 * np.array(Eps0_errs)**2)) Al_std[PT] = np.sqrt(sum(C**2 * np.array(Al_errs)**2)) # LPW: In case I did not do the conversion correctly, the line of code previously here was: # Scd_std[PT] = np.sqrt(sum(np.mat(C**2) * np.array(Scd_errs)**2)) Scd_std[PT] = np.sqrt(sum(np.dot(row(C**2), np.array(Scd_errs)**2))) LKappa_std[PT] = np.sqrt(sum(C**2 * np.array(LKappa_errs)**2)) * 1e6 # Get contributions to the objective function X_Rho, G_Rho, H_Rho, RhoPrint = self.objective_term(Points, 'rho', Rho_calc, Rho_std, Rho_grad, name="Density") X_Alpha, G_Alpha, H_Alpha, AlphaPrint = self.objective_term(Points, 'alpha', Alpha_calc, Alpha_std, Alpha_grad, name="Thermal Expansion") X_Kappa, G_Kappa, H_Kappa, KappaPrint = self.objective_term(Points, 'kappa', Kappa_calc, Kappa_std, Kappa_grad, name="Compressibility") X_Cp, G_Cp, H_Cp, CpPrint = self.objective_term(Points, 'cp', Cp_calc, Cp_std, Cp_grad, name="Heat Capacity") X_Eps0, G_Eps0, H_Eps0, Eps0Print = self.objective_term(Points, 'eps0', Eps0_calc, Eps0_std, Eps0_grad, name="Dielectric Constant") X_Al, G_Al, H_Al, AlPrint = self.objective_term(Points, 'al', Al_calc, Al_std, Al_grad, name="Avg Area per Lipid") X_Scd, G_Scd, H_Scd, ScdPrint = self.objective_term(Points, 'scd', Scd_calc, Scd_std, Scd_grad, name="Deuterium Order Parameter") X_LKappa, G_LKappa, H_LKappa, LKappaPrint = self.objective_term(Points, 'lkappa', LKappa_calc, LKappa_std, LKappa_grad, name="Bilayer Compressibility") Gradient = np.zeros(self.FF.np) Hessian = np.zeros((self.FF.np,self.FF.np)) if X_Rho == 0: self.w_rho = 0.0 if X_Alpha == 0: self.w_alpha = 0.0 if X_Kappa == 0: self.w_kappa = 0.0 if X_Cp == 0: self.w_cp = 0.0 if X_Eps0 == 0: self.w_eps0 = 0.0 if X_Al == 0: self.w_al = 0.0 if X_Scd == 0: self.w_scd = 0.0 if X_LKappa == 0: self.w_lkappa = 0.0 if self.w_normalize: w_tot = self.w_rho + self.w_alpha + self.w_kappa + self.w_cp + self.w_eps0 + self.w_al + self.w_scd + self.w_lkappa else: w_tot = 1.0 w_1 = self.w_rho / w_tot w_3 = self.w_alpha / w_tot w_4 = self.w_kappa / w_tot w_5 = self.w_cp / w_tot w_6 = self.w_eps0 / w_tot w_7 = self.w_al / w_tot w_8 = self.w_scd / w_tot w_9 = self.w_lkappa / w_tot Objective = w_1 * X_Rho + w_3 * X_Alpha + w_4 * X_Kappa + w_5 * X_Cp + w_6 * X_Eps0 + w_7 * X_Al + w_8 * X_Scd + w_9 * X_LKappa if AGrad: Gradient = w_1 * G_Rho + w_3 * G_Alpha + w_4 * G_Kappa + w_5 * G_Cp + w_6 * G_Eps0 + w_7 * G_Al + w_8 * G_Scd + w_9 * G_LKappa if AHess: Hessian = w_1 * H_Rho + w_3 * H_Alpha + w_4 * H_Kappa + w_5 * H_Cp + w_6 * H_Eps0 + w_7 * H_Al + w_8 * H_Scd + w_9 * H_LKappa if not in_fd(): self.Xp = {"Rho" : X_Rho, "Alpha" : X_Alpha, "Kappa" : X_Kappa, "Cp" : X_Cp, "Eps0" : X_Eps0, "Al" : X_Al, "Scd" : X_Scd, "LKappa" : X_LKappa} self.Wp = {"Rho" : w_1, "Alpha" : w_3, "Kappa" : w_4, "Cp" : w_5, "Eps0" : w_6, "Al" : w_7, "Scd" : w_8, "LKappa" : w_9} self.Pp = {"Rho" : RhoPrint, "Alpha" : AlphaPrint, "Kappa" : KappaPrint, "Cp" : CpPrint, "Eps0" : Eps0Print, "Al" : AlPrint, "Scd" : ScdPrint, "LKappa": LKappaPrint} if AGrad: self.Gp = {"Rho" : G_Rho, "Alpha" : G_Alpha, "Kappa" : G_Kappa, "Cp" : G_Cp, "Eps0" : G_Eps0, "Al" : G_Al, "Scd" : G_Scd, "LKappa" : G_LKappa} self.Objective = Objective Answer = {'X':Objective, 'G':Gradient, 'H':Hessian} return Answer
from forcebalance.molecule import Molecule from forcebalance.nifty import _exec import os np = "" if "CORES_PER_WORKER" in os.environ and int( os.environ["CORES_PER_WORKER"]) > 1: np = " -np %i" % int(os.environ["CORES_PER_WORKER"]) _exec("touch opt.xyz") _exec("touch energy.txt") _exec("rm -f qchem.out.prev") _exec("touch qchem.out.prev") qcin = Molecule("qchem.in", ftype="qcin") qcin.edit_qcrems({'geom_opt_max_cycles': '100'}) qcin.write("qchem.in") _exec("qchem42 %s qchem.in qchem.out &> qchem.err" % np) def special_criterion(): mk = 0 mx = 0 Cnvgd = {} for ln, line in enumerate(open("qchem.out").readlines()): if "Maximum optimization cycles reached" in line: mx = 1 if "Maximum Tolerance Cnvgd?" in line: mk = ln if mk > 0 and ln > mk and ln <= mk + 3: s = line.split() try:
elif len(espxyz) > 0: # After reading in a block of ESPs, don't read any more. ESPMode = -1 if line.strip().startswith("Geometry (in Angstrom)"): XMode = 1 EMode = len(elem) == 0 if 'Electrostatic Potential' in line.strip() and ESPMode == 0: ESPMode = 1 if len(xyzs) == 0: raise Exception('%s has length zero' % psiout) return xyzs, elem, espxyz, espval xyzs, elem, espxyz, espval = read_psi_xyzesp(sys.argv[1]) M = Molecule() M.xyzs = xyzs M.elem = elem M.write('%s.xyz' % os.path.splitext(sys.argv[1])[0]) EM = Molecule() EM.xyzs = [np.array(espxyz) * 0.52917721092] EM.elem = ['H' for i in range(len(espxyz))] EM.write('%s.espx' % os.path.splitext(sys.argv[1])[0], ftype="xyz") M.qm_espxyzs = EM.xyzs M.qm_espvals = [np.array(espval)] M.write("qdata.txt") np.savetxt('%s.esp' % os.path.splitext(sys.argv[1])[0], espval)