def fullsys_best_guess(comm=None): '''Get conventional E of full system by finding best initial guess. Iterates over fragments to produce Nfrag charge-local initial guesses. Takes best UHF energy from each of the corresponding initial densities, then does correlated calculation. ''' if comm is None: comm = MPI.comm geom.set_frag_auto() sub_fragments = geom.fragments[:] guess_vecs = [] esp_list = [] for i, frag_i in enumerate(sub_fragments): monomers = range(len(sub_fragments)) charges = [int(j == i) for j in monomers] esp, vecs = monomerSCF(monomers, charges, embedding=False, comm=comm) esp_list.append(esp) guess_vecs.append(vecs) geom.set_frag_full_system() my_calcs = [] my_guessvecs = MPI.scatter(comm, guess_vecs, master=0) for guess in my_guessvecs: res = backend.run('esp', [(0, 0, 0, 0)], 1, [], [], guess=guess) my_calcs.append(res) calcs = MPI.allgather(comm, my_calcs) ibest, best = min(enumerate(calcs), key=lambda x: x[1]['E_hf']) espfield = calcs[ibest]['esp_charges'] if params.options['correlation']: best = backend.run('energy', [(0, 0, 0, 0)], 1, [], [], guess=guess_vecs[ibest]) res = {} res['E1'] = best['E_tot'] res['E2'] = 0.0 res['monomers'] = [best] res['dimers'] = [] res['net_charges'] = [1] res['esp'] = espfield return res
def monomerSCF(comm=None): '''Cycle embedded monomer calculations until ESP charges converge. BIM version: include all monomers, take charges from input geometry, bq_lists from Globals.neighbor, and embedding option from input file. PBC is implicitly handled No need to do anything if embedding option is off. Args: comm: specify a sub-communicator for parallel execution. Default None: use the top-level communicator in Globals.MPI Returns: espcharges: a list of esp-fit atom-centered charges ''' if comm is None: comm = MPI.comm options = params.options RMSD_TOL = 0.001 MAXITER = 10 RMSD = RMSD_TOL + 1 itr = 0 espcharges = [0.0 for at in geom.geometry] if not options['embedding']: return espcharges natm = float(len(geom.geometry)) while RMSD > RMSD_TOL and itr < MAXITER: espcharges0 = espcharges[:] # copy myfrags = MPI.scatter(comm, range(len(geom.fragments)), master=0) mycharges = [] for m in myfrags: fragment = [(m,0,0,0)] net_chg = geom.charge(m) bqlist = neighbor.bq_lists[m] result = backend.run('esp', fragment, net_chg, bqlist, espcharges) mycharges.append(result['esp_charges']) espcharges = MPI.allgather(comm, mycharges) espcharges = [chg for m in espcharges for chg in m] residual = np.array(espcharges) - np.array(espcharges0) RMSD = np.linalg.norm(residual) / natm**0.5 itr += 1 if RMSD > RMSD_TOL: raise RuntimeError("Monomer SCF did not converge") else: return espcharges
def create_bim_fragment(specifier, espcharges): '''Create and dispatch a backend fragment calculation. Args: specifier: tuple specifying the monomer and cell indices for the requested calculation. espcharges: embedding field charges Returns: results: results dict from fragment calculation ''' assert len(specifier) in [1, 5, 6] #monomer if len(specifier) == 1: i, = specifier fragment = [(i,0,0,0)] net_chg = geom.charge(i) bqlist = neighbor.bq_lists[i] # dimer elif len(specifier) == 5: i,j,a,b,c = specifier fragment = [(i,0,0,0), (j,a,b,c)] net_chg = geom.charge(i) + geom.charge(j) # monomer in dimer field else: i,j,a,b,c,bqij = specifier # build dimer field if len(specifier) == 5 or len(specifier) == 6: bqi, bqj = neighbor.bq_lists[i], neighbor.bq_lists[j] bqj = [(bq[0], bq[1]+a, bq[2]+b, bq[3]+c) for bq in bqj] bqlist = list(set(bqi).union(set(bqj))) if len(specifier) == 5: bqlist.remove( (i,0,0,0) ) bqlist.remove( (j,a,b,c) ) if len(specifier) == 6: assert bqij in ['QMi_BQj', 'QMj_BQi'] if bqij == 'QMi_BQj': fragment = [(i,0,0,0)] net_chg = geom.charge(i) bqlist.remove( (i,0,0,0) ) else: fragment = [(j,a,b,c)] net_chg = geom.charge(j) bqlist.remove( (j,a,b,c) ) task, sum_fxn = get_task() result = backend.run(task, fragment, net_chg, bqlist, espcharges) return result
def coupl_chglocal(A, B): '''Charge-local dimer method for coupling Only works with NW backend and singly ionized molecular cluster cations. Args A, B: indices for off-diagonal H element calculation ''' esps_Aloc, vecs_Aloc = monomerSCF([A,B], [1,0], comm='serial') esps_Bloc, vecs_Bloc = monomerSCF([A,B], [0,1], comm='serial') bqs = [] esp = [] # Charge local frag = [(A,0,0,0), (B,0,0,0)] Aloc = backend.run('energy_hf', frag, 1, bqs, esp, noscf=True, guess=vecs_Aloc) Bloc = backend.run('energy_hf', frag, 1, bqs, esp, noscf=True, guess=vecs_Bloc) # Relaxed dimer Aloc_relax = backend.run('energy', frag, 1, bqs, esp, guess=vecs_Aloc) Bloc_relax = backend.run('energy', frag, 1, bqs, esp, guess=vecs_Bloc) relax = min(Aloc_relax, Bloc_relax, key=lambda x:x['E_tot']) E_relax = relax['E_tot'] E_Aloc = Aloc['E_tot'] E_Bloc = Bloc['E_tot'] if params.options['correlation']: monA_0 = backend.run('energy', [(A,0,0,0)], 0, [(B,0,0,0)], esps_Bloc, guess=vecs_Bloc[0]) monB_1 = backend.run('energy', [(B,0,0,0)], 1, [(A,0,0,0)], esps_Bloc, guess=vecs_Bloc[1]) monA_1 = backend.run('energy', [(A,0,0,0)], 1, [(B,0,0,0)], esps_Aloc, guess=vecs_Aloc[0]) monB_0 = backend.run('energy', [(B,0,0,0)], 0, [(A,0,0,0)], esps_Aloc, guess=vecs_Aloc[1]) E_Aloc += monA_1['E_corr'] + monB_0['E_corr'] E_Bloc += monA_0['E_corr'] + monB_1['E_corr'] assert E_relax <= E_Aloc and E_relax <= E_Bloc coupling = -1.0*((E_relax-E_Aloc)*(E_relax-E_Bloc))**0.5 results = dict(idx=(A,B),coupling=coupling, AB=E_relax, A=E_Aloc, B=E_Bloc) return results
def monomerSCF(monomers, net_charges, embedding=None, comm=None): '''Cycle embedded monomer calculations until the ESP charges converge. VBCT version: specify N monomers and their net charges explicitly. No support for cutoffs/periodicity: every monomer is embedded in the field of all other N-1 monomers. Able to override default embedding option. Monomer MO vectors are saved; thus one cycle runs even if embedding option is turned off. Args monomers: a list of monomer indices net_charges: net charge of each monomer embedding: Override True/False specified in input. Default None: use the value specified in input. comm: specify a sub-communicator for parallel execution. If string 'serial' is specified, bypass MPI communication. Default None: use the top-level communicator in Globals.MPI Returns espcharges: a list of esp-fit atom-centered charges movecs: a list of MO vectors for each monomer ''' if comm is None: comm = MPI.comm RMSD_TOL = 0.001 MAXITER = 10 RMSD = RMSD_TOL + 1 itr = 0 if embedding is None: embedding = params.options['embedding'] else: assert type(embedding) is bool espcharges = [0.0 for at in geom.geometry] while RMSD > RMSD_TOL and itr < MAXITER: espcharges0 = espcharges[:] # copy if comm is not 'serial': myfrags = MPI.scatter(comm, zip(monomers, net_charges), master=0) else: myfrags = zip(monomers, net_charges) mycharges = [] myvecs = [] for (m, net_chg) in myfrags: fragment = [(m, 0, 0, 0)] if embedding: bqlist = [(j, 0, 0, 0) for j in monomers if j != m] else: bqlist = [] result = backend.run('esp', fragment, net_chg, bqlist, espcharges, save=True) mycharges.append(result['esp_charges']) myvecs.append(result['movecs']) if comm is not 'serial': movecs = MPI.allgather(comm, myvecs) monomer_espcharges = MPI.allgather(comm, mycharges) else: movecs = myvecs monomer_espcharges = mycharges for (m, charges) in zip(monomers, monomer_espcharges): for (at, chg) in zip(geom.fragments[m], charges): espcharges[at] = chg residual = np.array(espcharges) - np.array(espcharges0) RMSD = np.linalg.norm(residual) itr += 1 if not embedding or len(monomers) == 1: return espcharges, movecs if RMSD > RMSD_TOL: raise RuntimeError("Monomer SCF did not converge") else: return espcharges, movecs
def diag_chglocal(charges, espfield, movecs, comm=None): '''Charge-local dimer method for diagonal element calculation. Only works with NW backend and singly ionized molecular cluster cations. Args charges: list of net charges on each fragment espfield: list of esp-fit atomic charges for entire system movecs: list of MO coeff files for each fragment comm: MPI communicator or subcommunicator ''' if comm is None: comm = MPI.comm embed_flag = params.options['embedding'] nfrag = len(geom.fragments) monomers = range(nfrag) dimers = list(combinations(monomers, 2)) my_mon_idxs = MPI.scatter(comm, monomers, master=0) my_dim_idxs = MPI.scatter(comm, dimers, master=0) my_mon_calcs = [] my_dim_calcs = [] for m in my_mon_idxs: bqlist = [] if embed_flag: bqlist = make_embed_list(m, monomers) frag = [(m,0,0,0)] res = backend.run('energy', frag, charges[m], bqlist, espfield, guess=movecs[m]) my_mon_calcs.append(res) for d in my_dim_idxs: i,j = d chg_i, chg_j = charges[i], charges[j] chg = charges[i]+charges[j] frag = [(d[0],0,0,0), (d[1],0,0,0)] guess = [movecs[i], movecs[j]] bqlist = [] if embed_flag: bqlist = make_embed_list(d, monomers) if chg_i != chg_j: assert chg == 1 charge_local = True calc = 'energy_hf' else: assert chg == 0 charge_local = False calc = 'energy' res = backend.run(calc, frag, chg, bqlist, espfield, guess=guess, noscf=charge_local) my_dim_calcs.append(res) mon_calcs = MPI.allgather(comm, my_mon_calcs) dim_calcs = MPI.allgather(comm, my_dim_calcs) results = {} E1 = sum([mon['E_tot'] for mon in mon_calcs]) results['E1'] = E1 E2 = 0.0 for (i,j), dim in zip(dimers, dim_calcs): Eij = dim['E_tot'] if 'E_corr' in dim: Ei = mon_calcs[i]['E_tot'] Ej = mon_calcs[j]['E_tot'] else: Ei = mon_calcs[i]['E_hf'] Ej = mon_calcs[j]['E_hf'] E2 += Eij - Ei - Ej results['E2'] = E2 results['monomers'] = mon_calcs results['dimers'] = dim_calcs results['net_charges'] = charges results['esp'] = espfield return results