def write(self, out=sys.stdout): Timer.write(self, out) self.hpm_stop(self.top_level)
class ExcitedState(GPAW): def __init__(self, lrtddft=None, index=0, d=0.001, txt=None, parallel=0, communicator=None, name=None, restart=None): """ExcitedState object. parallel: Can be used to parallelize the numerical force calculation over images. """ self.timer = Timer() self.atoms = None if isinstance(index, int): self.index = UnconstraintIndex(index) else: self.index = index self.results = {} self.results['forces'] = None self.results['energy'] = None if communicator is None: try: communicator = lrtddft.calculator.wfs.world except: communicator = mpi.world self.world = communicator if restart is not None: self.read(restart) if txt is None: self.txt = self.lrtddft.txt else: self.txt = convert_string_to_fd(txt, self.world) if lrtddft is not None: self.lrtddft = lrtddft self.calculator = self.lrtddft.calculator self.atoms = self.calculator.atoms self.parameters = self.calculator.parameters if txt is None: self.txt = self.lrtddft.txt else: self.txt = convert_string_to_fd(txt, self.world) self.d = d self.parallel = parallel self.name = name self.log = GPAWLogger(self.world) self.log.fd = self.txt self.reader = None self.calculator.log.fd = self.txt self.log('#', self.__class__.__name__, __version__) self.log('#', self.index) if name: self.log('name=' + name) self.log('# Force displacement:', self.d) self.log def __del__(self): self.timer.write(self.log.fd) def set(self, **kwargs): self.calculator.set(**kwargs) def set_positions(self, atoms): """Update the positions of the atoms.""" self.atoms = atoms.copy() self.results['forces'] = None self.results['energy'] = None def write(self, filename, mode=''): try: os.makedirs(filename) except OSError as exception: if exception.errno != errno.EEXIST: raise self.calculator.write(filename=filename + '/' + filename, mode=mode) self.lrtddft.write(filename=filename + '/' + filename + '.lr.dat.gz', fh=None) f = open(filename + '/' + filename + '.exst', 'w') f.write('# ' + self.__class__.__name__ + __version__ + '\n') f.write('Displacement: {0}'.format(self.d) + '\n') f.write('Index: ' + self.index.__class__.__name__ + '\n') for k, v in self.index.__dict__.items(): f.write('{0}, {1}'.format(k, v) + '\n') f.close() mpi.world.barrier() def read(self, filename): self.lrtddft = LrTDDFT(filename + '/' + filename + '.lr.dat.gz') self.atoms, self.calculator = restart( filename + '/' + filename, communicator=self.world) E0 = self.calculator.get_potential_energy() f = open(filename + '/' + filename + '.exst', 'r') f.readline() self.d = f.readline().replace('\n', '').split()[1] indextype = f.readline().replace('\n', '').split()[1] if indextype == 'UnconstraintIndex': iex = int(f.readline().replace('\n', '').split()[1]) self.index = UnconstraintIndex(iex) else: direction = f.readline().replace('\n', '').split()[1] if direction in [str(0), str(1), str(2)]: direction = int(direction) else: direction = None val = f.readline().replace('\n', '').split() if indextype == 'MinimalOSIndex': self.index = MinimalOSIndex(float(val[1]), direction) else: emin = float(val[2]) emax = float(val[3].replace(']', '')) self.index = MaximalOSIndex([emin, emax], direction) index = self.index.apply(self.lrtddft) self.results['energy'] = E0 + self.lrtddft[index].energy * Hartree self.lrtddft.set_calculator(self.calculator) def calculation_required(self, atoms, quantities): if len(quantities) == 0: return False if self.atoms is None: return True elif (len(atoms) != len(self.atoms) or (atoms.get_atomic_numbers() != self.atoms.get_atomic_numbers()).any() or (atoms.get_initial_magnetic_moments() != self.atoms.get_initial_magnetic_moments()).any() or (atoms.get_cell() != self.atoms.get_cell()).any() or (atoms.get_pbc() != self.atoms.get_pbc()).any()): return True elif (atoms.get_positions() != self.atoms.get_positions()).any(): return True for quantity in ['energy', 'forces']: if quantity in quantities: quantities.remove(quantity) if self.results[quantity] is None: return True return len(quantities) > 0 def check_state(self, atoms, tol=1e-15): system_changes = GPAW.check_state(self.calculator, atoms, tol) return system_changes def get_potential_energy(self, atoms=None, force_consistent=None): """Evaluate potential energy for the given excitation.""" if atoms is None: atoms = self.atoms if self.calculation_required(atoms, ['energy']): self.results['energy'] = self.calculate(atoms) return self.results['energy'] def calculate(self, atoms): """Evaluate your energy if needed.""" self.set_positions(atoms) self.calculator.calculate(atoms) E0 = self.calculator.get_potential_energy() atoms.set_calculator(self) if hasattr(self, 'density'): del(self.density) self.lrtddft.forced_update() self.lrtddft.diagonalize() index = self.index.apply(self.lrtddft) energy = E0 + self.lrtddft[index].energy * Hartree self.log('--------------------------') self.log('Excited state') self.log(self.index) self.log('Energy: {0}'.format(energy)) self.log() return energy def get_forces(self, atoms=None, save=False): """Get finite-difference forces If save = True, restartfiles for every displacement are given """ if atoms is None: atoms = self.atoms if self.calculation_required(atoms, ['forces']): atoms.set_calculator(self) # do the ground state calculation to set all # ranks to the same density to start with E0 = self.calculate(atoms) finite = FiniteDifference( atoms=atoms, propertyfunction=atoms.get_potential_energy, save=save, name="excited_state", ending='.gpw', d=self.d, parallel=self.parallel) F_av = finite.run() self.set_positions(atoms) self.results['energy'] = E0 self.results['forces'] = F_av if self.txt: self.log('Excited state forces in eV/Ang:') symbols = self.atoms.get_chemical_symbols() for a, symbol in enumerate(symbols): self.log(('%3d %-2s %10.5f %10.5f %10.5f' % ((a, symbol) + tuple(self.results['forces'][a])))) return self.results['forces'] def forces_indexn(self, index): """ If restartfiles are created from the force calculation, this function allows the calculation of forces for every excited state index. """ atoms = self.atoms def reforce(self, name): excalc = ExcitedState(index=index, restart=name) return excalc.get_potential_energy() fd = FiniteDifference( atoms=atoms, save=True, propertyfunction=self.atoms.get_potential_energy, name="excited_state", ending='.gpw', d=self.d, parallel=0) atoms.set_calculator(self) return fd.restart(reforce) def get_stress(self, atoms): """Return the stress for the current state of the Atoms.""" raise NotImplementedError def initialize_density(self, method='dipole'): if hasattr(self, 'density') and self.density.method == method: return gsdensity = self.calculator.density lr = self.lrtddft self.density = ExcitedStateDensity( gsdensity.gd, gsdensity.finegd, lr.kss.npspins, gsdensity.charge, method=method, redistributor=gsdensity.redistributor) index = self.index.apply(self.lrtddft) self.density.initialize(self.lrtddft, index) self.density.update(self.calculator.wfs) def get_pseudo_density(self, **kwargs): """Return pseudo-density array.""" method = kwargs.pop('method', 'dipole') self.initialize_density(method) return GPAW.get_pseudo_density(self, **kwargs) def get_all_electron_density(self, **kwargs): """Return all electron density array.""" method = kwargs.pop('method', 'dipole') self.initialize_density(method) return GPAW.get_all_electron_density(self, **kwargs)
def write(self, out=sys.stdout): Timer.write(self, out) if self.merge: self.pytau.dbMergeDump() else: self.pytau.stop(self.tau_timers[self.top_level])
class RPACorrelation: def __init__(self, calc, xc='RPA', filename=None, skip_gamma=False, qsym=True, nlambda=None, nfrequencies=16, frequency_max=800.0, frequency_scale=2.0, frequencies=None, weights=None, truncation=None, world=mpi.world, nblocks=1, txt=sys.stdout): """Creates the RPACorrelation object calc: str or calculator object The string should refer to the .gpw file contaning KS orbitals xc: str Exchange-correlation kernel. This is only different from RPA when this object is constructed from a different module - e.g. fxc.py filename: str txt output skip_gamme: bool If True, skip q = [0,0,0] from the calculation qsym: bool Use symmetry to reduce q-points nlambda: int Number of lambda points. Only used for numerical coupling constant integration involved when called from fxc.py nfrequencies: int Number of frequency points used in the Gauss-Legendre integration frequency_max: float Largest frequency point in Gauss-Legendre integration frequency_scale: float Determines density of frequency points at low frequencies. A slight increase to e.g. 2.5 or 3.0 improves convergence wth respect to frequency points for metals frequencies: list List of frequancies for user-specified frequency integration weights: list list of weights (integration measure) for a user specified frequency grid. Must be specified and have the same length as frequencies if frequencies is not None truncation: str Coulomb truncation scheme. Can be either wigner-seitz, 2D, 1D, or 0D world: communicator nblocks: int Number of parallelization blocks. Frequency parallelization can be specified by setting nblocks=nfrequencies and is useful for memory consuming calculations txt: str txt file for saving and loading contributions to the correlation energy from different q-points """ if isinstance(calc, str): calc = GPAW(calc, txt=None, communicator=mpi.serial_comm) self.calc = calc if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w', 1) self.fd = txt self.timer = Timer() if frequencies is None: frequencies, weights = get_gauss_legendre_points(nfrequencies, frequency_max, frequency_scale) user_spec = False else: assert weights is not None user_spec = True self.omega_w = frequencies / Hartree self.weight_w = weights / Hartree if nblocks > 1: assert len(self.omega_w) % nblocks == 0 self.nblocks = nblocks self.world = world self.truncation = truncation self.skip_gamma = skip_gamma self.ibzq_qc = None self.weight_q = None self.initialize_q_points(qsym) # Energies for all q-vetors and cutoff energies: self.energy_qi = [] self.filename = filename self.print_initialization(xc, frequency_scale, nlambda, user_spec) def initialize_q_points(self, qsym): kd = self.calc.wfs.kd self.bzq_qc = kd.get_bz_q_points(first=True) if not qsym: self.ibzq_qc = self.bzq_qc self.weight_q = np.ones(len(self.bzq_qc)) / len(self.bzq_qc) else: U_scc = kd.symmetry.op_scc self.ibzq_qc = kd.get_ibz_q_points(self.bzq_qc, U_scc)[0] self.weight_q = kd.q_weights def read(self): lines = open(self.filename).readlines()[1:] n = 0 self.energy_qi = [] nq = len(lines) // len(self.ecut_i) for q_c in self.ibzq_qc[:nq]: self.energy_qi.append([]) for ecut in self.ecut_i: q1, q2, q3, ec, energy = [float(x) for x in lines[n].split()] self.energy_qi[-1].append(energy / Hartree) n += 1 if (abs(q_c - (q1, q2, q3)).max() > 1e-4 or abs(int(ecut * Hartree) - ec) > 0): self.energy_qi = [] return print('Read %d q-points from file: %s' % (nq, self.filename), file=self.fd) print(file=self.fd) def write(self): if self.world.rank == 0 and self.filename: fd = open(self.filename, 'w') print('#%9s %10s %10s %8s %12s' % ('q1', 'q2', 'q3', 'E_cut', 'E_c(q)'), file=fd) for energy_i, q_c in zip(self.energy_qi, self.ibzq_qc): for energy, ecut in zip(energy_i, self.ecut_i): print('%10.4f %10.4f %10.4f %8d %r' % (tuple(q_c) + (ecut * Hartree, energy * Hartree)), file=fd) def calculate(self, ecut, nbands=None, spin=False): """Calculate RPA correlation energy for one or several cutoffs. ecut: float or list of floats Plane-wave cutoff(s). nbands: int Number of bands (defaults to number of plane-waves). spin: bool Separate spin in response function. (Only needed for beyond RPA methods that inherit this function). """ p = functools.partial(print, file=self.fd) if isinstance(ecut, (float, int)): ecut = ecut * (1 + 0.5 * np.arange(6))**(-2 / 3) self.ecut_i = np.asarray(np.sort(ecut)) / Hartree ecutmax = max(self.ecut_i) if nbands is None: p('Response function bands : Equal to number of plane waves') else: p('Response function bands : %s' % nbands) p('Plane wave cutoffs (eV) :', end='') for e in self.ecut_i: p(' {0:.3f}'.format(e * Hartree), end='') p() if self.truncation is not None: p('Using %s Coulomb truncation' % self.truncation) p() if self.filename and os.path.isfile(self.filename): self.read() self.world.barrier() chi0 = Chi0(self.calc, 1j * Hartree * self.omega_w, eta=0.0, intraband=False, hilbert=False, txt='chi0.txt', timer=self.timer, world=self.world, nblocks=self.nblocks) self.blockcomm = chi0.blockcomm wfs = self.calc.wfs if self.truncation == 'wigner-seitz': self.wstc = WignerSeitzTruncatedCoulomb(wfs.gd.cell_cv, wfs.kd.N_c, self.fd) else: self.wstc = None nq = len(self.energy_qi) nw = len(self.omega_w) nGmax = max(count_reciprocal_vectors(ecutmax, wfs.gd, q_c) for q_c in self.ibzq_qc[nq:]) mynGmax = (nGmax + self.nblocks - 1) // self.nblocks nx = (1 + spin) * nw * mynGmax * nGmax A1_x = np.empty(nx, complex) if self.nblocks > 1: A2_x = np.empty(nx, complex) else: A2_x = None self.timer.start('RPA') for q_c in self.ibzq_qc[nq:]: if np.allclose(q_c, 0.0) and self.skip_gamma: self.energy_qi.append(len(self.ecut_i) * [0.0]) self.write() p('Not calculating E_c(q) at Gamma') p() continue thisqd = KPointDescriptor([q_c]) pd = PWDescriptor(ecutmax, wfs.gd, complex, thisqd) nG = pd.ngmax mynG = (nG + self.nblocks - 1) // self.nblocks chi0.Ga = self.blockcomm.rank * mynG chi0.Gb = min(chi0.Ga + mynG, nG) shape = (1 + spin, nw, chi0.Gb - chi0.Ga, nG) chi0_swGG = A1_x[:np.prod(shape)].reshape(shape) chi0_swGG[:] = 0.0 if np.allclose(q_c, 0.0): chi0_swxvG = np.zeros((1 + spin, nw, 2, 3, nG), complex) chi0_swvv = np.zeros((1 + spin, nw, 3, 3), complex) else: chi0_swxvG = None chi0_swvv = None # First not completely filled band: m1 = chi0.nocc1 p('# %s - %s' % (len(self.energy_qi), ctime().split()[-2])) p('q = [%1.3f %1.3f %1.3f]' % tuple(q_c)) energy_i = [] for ecut in self.ecut_i: if ecut == ecutmax: # Nothing to cut away: cut_G = None m2 = nbands or nG else: cut_G = np.arange(nG)[pd.G2_qG[0] <= 2 * ecut] m2 = len(cut_G) p('E_cut = %d eV / Bands = %d:' % (ecut * Hartree, m2)) self.fd.flush() energy = self.calculate_q(chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, m1, m2, cut_G, A2_x) energy_i.append(energy) m1 = m2 a = 1 / chi0.kncomm.size if ecut < ecutmax and a != 1.0: # Chi0 will be summed again over chicomm, so we divide # by its size: chi0_swGG *= a if chi0_swxvG is not None: chi0_swxvG *= a chi0_swvv *= a self.energy_qi.append(energy_i) self.write() p() e_i = np.dot(self.weight_q, np.array(self.energy_qi)) p('==========================================================') p() p('Total correlation energy:') for e_cut, e in zip(self.ecut_i, e_i): p('%6.0f: %6.4f eV' % (e_cut * Hartree, e * Hartree)) p() self.energy_qi = [] # important if another calculation is performed if len(e_i) > 1: self.extrapolate(e_i) p('Calculation completed at: ', ctime()) p() self.timer.stop('RPA') self.timer.write(self.fd) self.fd.flush() return e_i * Hartree @timer('chi0(q)') def calculate_q(self, chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, m1, m2, cut_G, A2_x): chi0_wGG = chi0_swGG[0] if chi0_swxvG is not None: chi0_wxvG = chi0_swxvG[0] chi0_wvv = chi0_swvv[0] else: chi0_wxvG = None chi0_wvv = None chi0._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv, m1, m2, [0, 1]) print('E_c(q) = ', end='', file=self.fd) chi0_wGG = chi0.redistribute(chi0_wGG, A2_x) if not pd.kd.gamma: e = self.calculate_energy(pd, chi0_wGG, cut_G) print('%.3f eV' % (e * Hartree), file=self.fd) self.fd.flush() else: from ase.dft import monkhorst_pack kd = self.calc.wfs.kd N = 4 N_c = np.array([N, N, N]) if self.truncation is not None: N_c[kd.N_c == 1] = 1 q_qc = monkhorst_pack(N_c) / kd.N_c q_qc *= 1.0e-6 U_scc = kd.symmetry.op_scc q_qc = kd.get_ibz_q_points(q_qc, U_scc)[0] weight_q = kd.q_weights q_qv = 2 * np.pi * np.dot(q_qc, pd.gd.icell_cv) nw = len(self.omega_w) mynw = nw // self.nblocks w1 = self.blockcomm.rank * mynw w2 = w1 + mynw a_qw = np.sum(np.dot(chi0_wvv[w1:w2], q_qv.T) * q_qv.T, axis=1).T a0_qwG = np.dot(q_qv, chi0_wxvG[w1:w2, 0]) a1_qwG = np.dot(q_qv, chi0_wxvG[w1:w2, 1]) e = 0 for iq in range(len(q_qv)): chi0_wGG[:, 0] = a0_qwG[iq] chi0_wGG[:, :, 0] = a1_qwG[iq] chi0_wGG[:, 0, 0] = a_qw[iq] ev = self.calculate_energy(pd, chi0_wGG, cut_G, q_v=q_qv[iq]) e += ev * weight_q[iq] print('%.3f eV' % (e * Hartree), file=self.fd) self.fd.flush() return e @timer('Energy') def calculate_energy(self, pd, chi0_wGG, cut_G, q_v=None): """Evaluate correlation energy from chi0.""" sqrV_G = get_coulomb_kernel(pd, self.calc.wfs.kd.N_c, q_v=q_v, truncation=self.truncation, wstc=self.wstc)**0.5 if cut_G is not None: sqrV_G = sqrV_G[cut_G] nG = len(sqrV_G) e_w = [] for chi0_GG in chi0_wGG: if cut_G is not None: chi0_GG = chi0_GG.take(cut_G, 0).take(cut_G, 1) e_GG = np.eye(nG) - chi0_GG * sqrV_G * sqrV_G[:, np.newaxis] e = np.log(np.linalg.det(e_GG)) + nG - np.trace(e_GG) e_w.append(e.real) E_w = np.zeros_like(self.omega_w) self.blockcomm.all_gather(np.array(e_w), E_w) energy = np.dot(E_w, self.weight_w) / (2 * np.pi) self.E_w = E_w return energy def extrapolate(self, e_i): print('Extrapolated energies:', file=self.fd) ex_i = [] for i in range(len(e_i) - 1): e1, e2 = e_i[i:i + 2] x1, x2 = self.ecut_i[i:i + 2]**-1.5 ex = (e1 * x2 - e2 * x1) / (x2 - x1) ex_i.append(ex) print(' %4.0f -%4.0f: %5.3f eV' % (self.ecut_i[i] * Hartree, self.ecut_i[i + 1] * Hartree, ex * Hartree), file=self.fd) print(file=self.fd) self.fd.flush() return e_i * Hartree def print_initialization(self, xc, frequency_scale, nlambda, user_spec): p = functools.partial(print, file=self.fd) p('----------------------------------------------------------') p('Non-self-consistent %s correlation energy' % xc) p('----------------------------------------------------------') p('Started at: ', ctime()) p() p('Atoms :', self.calc.atoms.get_chemical_formula(mode='hill')) p('Ground state XC functional :', self.calc.hamiltonian.xc.name) p('Valence electrons :', self.calc.wfs.setups.nvalence) p('Number of bands :', self.calc.wfs.bd.nbands) p('Number of spins :', self.calc.wfs.nspins) p('Number of k-points :', len(self.calc.wfs.kd.bzk_kc)) p('Number of irreducible k-points :', len(self.calc.wfs.kd.ibzk_kc)) p('Number of q-points :', len(self.bzq_qc)) p('Number of irreducible q-points :', len(self.ibzq_qc)) p() for q, weight in zip(self.ibzq_qc, self.weight_q): p(' q: [%1.4f %1.4f %1.4f] - weight: %1.3f' % (q[0], q[1], q[2], weight)) p() p('----------------------------------------------------------') p('----------------------------------------------------------') p() if nlambda is None: p('Analytical coupling constant integration') else: p('Numerical coupling constant integration using', nlambda, 'Gauss-Legendre points') p() p('Frequencies') if not user_spec: p(' Gauss-Legendre integration with %s frequency points' % len(self.omega_w)) p(' Transformed from [0,oo] to [0,1] using e^[-aw^(1/B)]') p(' Highest frequency point at %5.1f eV and B=%1.1f' % (self.omega_w[-1] * Hartree, frequency_scale)) else: p(' User specified frequency integration with', len(self.omega_w), 'frequency points') p() p('Parallelization') p(' Total number of CPUs : % s' % self.world.size) p(' G-vector decomposition : % s' % self.nblocks) p(' K-point/band decomposition : % s' % (self.world.size // self.nblocks)) p()
class GPAW(PAW, Calculator): """This is the ASE-calculator frontend for doing a PAW calculation.""" implemented_properties = [ 'energy', 'forces', 'stress', 'dipole', 'magmom', 'magmoms' ] default_parameters = { 'mode': 'fd', 'xc': 'LDA', 'occupations': None, 'poissonsolver': None, 'h': None, # Angstrom 'gpts': None, 'kpts': [(0.0, 0.0, 0.0)], 'nbands': None, 'charge': 0, 'setups': {}, 'basis': {}, 'spinpol': None, 'fixdensity': False, 'filter': None, 'mixer': None, 'eigensolver': None, 'background_charge': None, 'experimental': { 'reuse_wfs_method': 'paw', 'niter_fixdensity': 0, 'magmoms': None, 'soc': None, 'kpt_refine': None }, 'external': None, 'random': False, 'hund': False, 'maxiter': 333, 'idiotproof': True, 'symmetry': { 'point_group': True, 'time_reversal': True, 'symmorphic': True, 'tolerance': 1e-7, 'do_not_symmetrize_the_density': False }, 'convergence': { 'energy': 0.0005, # eV / electron 'density': 1.0e-4, 'eigenstates': 4.0e-8, # eV^2 'bands': 'occupied', 'forces': np.inf }, # eV / Ang 'dtype': None, # Deprecated 'width': None, # Deprecated 'verbose': 0 } default_parallel = { 'kpt': None, 'domain': gpaw.parsize_domain, 'band': gpaw.parsize_bands, 'order': 'kdb', 'stridebands': False, 'augment_grids': gpaw.augment_grids, 'sl_auto': False, 'sl_default': gpaw.sl_default, 'sl_diagonalize': gpaw.sl_diagonalize, 'sl_inverse_cholesky': gpaw.sl_inverse_cholesky, 'sl_lcao': gpaw.sl_lcao, 'sl_lrtddft': gpaw.sl_lrtddft, 'use_elpa': False, 'elpasolver': '2stage', 'buffer_size': gpaw.buffer_size } def __init__(self, restart=None, ignore_bad_restart_file=False, label=None, atoms=None, timer=None, communicator=None, txt='-', parallel=None, **kwargs): self.parallel = dict(self.default_parallel) if parallel: for key in parallel: if key not in self.default_parallel: allowed = ', '.join(list(self.default_parallel.keys())) raise TypeError('Unexpected keyword "{}" in "parallel" ' 'dictionary. Must be one of: {}'.format( key, allowed)) self.parallel.update(parallel) if timer is None: self.timer = Timer() else: self.timer = timer self.scf = None self.wfs = None self.occupations = None self.density = None self.hamiltonian = None self.spos_ac = None # XXX store this in some better way. self.observers = [] # XXX move to self.scf self.initialized = False self.world = communicator if self.world is None: self.world = mpi.world elif not hasattr(self.world, 'new_communicator'): self.world = mpi.world.new_communicator(np.asarray(self.world)) self.log = GPAWLogger(world=self.world) self.log.fd = txt self.reader = None Calculator.__init__(self, restart, ignore_bad_restart_file, label, atoms, **kwargs) def __del__(self): # Write timings and close reader if necessary. # If we crashed in the constructor (e.g. a bad keyword), we may not # have the normally expected attributes: if hasattr(self, 'timer'): self.timer.write(self.log.fd) if hasattr(self, 'reader') and self.reader is not None: self.reader.close() def write(self, filename, mode=''): self.log('Writing to {} (mode={!r})\n'.format(filename, mode)) writer = Writer(filename, self.world) self._write(writer, mode) writer.close() self.world.barrier() def _write(self, writer, mode): from ase.io.trajectory import write_atoms writer.write(version=1, gpaw_version=gpaw.__version__, ha=Ha, bohr=Bohr) write_atoms(writer.child('atoms'), self.atoms) writer.child('results').write(**self.results) writer.child('parameters').write(**self.todict()) self.density.write(writer.child('density')) self.hamiltonian.write(writer.child('hamiltonian')) self.occupations.write(writer.child('occupations')) self.scf.write(writer.child('scf')) self.wfs.write(writer.child('wave_functions'), mode == 'all') return writer def _set_atoms(self, atoms): check_atoms_too_close(atoms) self.atoms = atoms # GPAW works in terms of the scaled positions. We want to # extract the scaled positions in only one place, and that is # here. No other place may recalculate them, or we might end up # with rounding errors and inconsistencies. self.spos_ac = atoms.get_scaled_positions() % 1.0 def read(self, filename): from ase.io.trajectory import read_atoms self.log('Reading from {}'.format(filename)) self.reader = reader = Reader(filename) atoms = read_atoms(reader.atoms) self._set_atoms(atoms) res = reader.results self.results = dict((key, res.get(key)) for key in res.keys()) if self.results: self.log('Read {}'.format(', '.join(sorted(self.results)))) self.log('Reading input parameters:') # XXX param self.parameters = self.get_default_parameters() dct = {} for key, value in reader.parameters.asdict().items(): if (isinstance(value, dict) and isinstance(self.parameters[key], dict)): self.parameters[key].update(value) else: self.parameters[key] = value dct[key] = self.parameters[key] self.log.print_dict(dct) self.log() self.initialize(reading=True) self.density.read(reader) self.hamiltonian.read(reader) self.occupations.read(reader) self.scf.read(reader) self.wfs.read(reader) # We need to do this in a better way: XXX from gpaw.utilities.partition import AtomPartition atom_partition = AtomPartition(self.wfs.gd.comm, np.zeros(len(self.atoms), dtype=int)) self.wfs.atom_partition = atom_partition self.density.atom_partition = atom_partition self.hamiltonian.atom_partition = atom_partition rank_a = self.density.gd.get_ranks_from_positions(self.spos_ac) new_atom_partition = AtomPartition(self.density.gd.comm, rank_a) for obj in [self.density, self.hamiltonian]: obj.set_positions_without_ruining_everything( self.spos_ac, new_atom_partition) self.hamiltonian.xc.read(reader) if self.hamiltonian.xc.name == 'GLLBSC': # XXX GLLB: See test/lcaotddft/gllbsc.py self.occupations.calculate(self.wfs) return reader def check_state(self, atoms, tol=1e-15): system_changes = Calculator.check_state(self, atoms, tol) if 'positions' not in system_changes: if self.hamiltonian: if self.hamiltonian.vext: if self.hamiltonian.vext.vext_g is None: # QMMM atoms have moved: system_changes.append('positions') return system_changes def calculate(self, atoms=None, properties=['energy'], system_changes=['cell']): """Calculate things.""" Calculator.calculate(self, atoms) atoms = self.atoms if system_changes: self.log('System changes:', ', '.join(system_changes), '\n') if system_changes == ['positions']: # Only positions have changed: self.density.reset() else: # Drastic changes: self.wfs = None self.occupations = None self.density = None self.hamiltonian = None self.scf = None self.initialize(atoms) self.set_positions(atoms) if not self.initialized: self.initialize(atoms) self.set_positions(atoms) if not (self.wfs.positions_set and self.hamiltonian.positions_set): self.set_positions(atoms) if not self.scf.converged: print_cell(self.wfs.gd, self.atoms.pbc, self.log) with self.timer('SCF-cycle'): self.scf.run(self.wfs, self.hamiltonian, self.density, self.occupations, self.log, self.call_observers) self.log('\nConverged after {} iterations.\n'.format( self.scf.niter)) e_free = self.hamiltonian.e_total_free e_extrapolated = self.hamiltonian.e_total_extrapolated self.results['energy'] = e_extrapolated * Ha self.results['free_energy'] = e_free * Ha dipole_v = self.density.calculate_dipole_moment() * Bohr self.log( 'Dipole moment: ({:.6f}, {:.6f}, {:.6f}) |e|*Ang\n'.format( *dipole_v)) self.results['dipole'] = dipole_v if self.wfs.nspins == 2 or not self.density.collinear: totmom_v, magmom_av = self.density.estimate_magnetic_moments() self.log( 'Total magnetic moment: ({:.6f}, {:.6f}, {:.6f})'.format( *totmom_v)) self.log('Local magnetic moments:') symbols = self.atoms.get_chemical_symbols() for a, mom_v in enumerate(magmom_av): self.log('{:4} {:2} ({:9.6f}, {:9.6f}, {:9.6f})'.format( a, symbols[a], *mom_v)) self.log() self.results['magmom'] = self.occupations.magmom self.results['magmoms'] = magmom_av[:, 2].copy() self.summary() self.call_observers(self.scf.niter, final=True) if 'forces' in properties: with self.timer('Forces'): F_av = calculate_forces(self.wfs, self.density, self.hamiltonian, self.log) self.results['forces'] = F_av * (Ha / Bohr) if 'stress' in properties: with self.timer('Stress'): try: stress = calculate_stress(self).flat[[0, 4, 8, 5, 2, 1]] except NotImplementedError: # Our ASE Calculator base class will raise # PropertyNotImplementedError for us. pass else: self.results['stress'] = stress * (Ha / Bohr**3) def summary(self): efermi = self.occupations.fermilevel self.hamiltonian.summary(efermi, self.log) self.density.summary(self.atoms, self.occupations.magmom, self.log) self.occupations.summary(self.log) self.wfs.summary(self.log) try: bandgap(self, output=self.log.fd, efermi=efermi * Ha) except ValueError: pass self.log.fd.flush() def set(self, **kwargs): """Change parameters for calculator. Examples:: calc.set(xc='PBE') calc.set(nbands=20, kpts=(4, 1, 1)) """ # Verify that keys are consistent with default ones. for key in kwargs: if key != 'txt' and key not in self.default_parameters: raise TypeError('Unknown GPAW parameter: {}'.format(key)) if key in ['convergence', 'symmetry', 'experimental' ] and isinstance(kwargs[key], dict): # For values that are dictionaries, verify subkeys, too. default_dict = self.default_parameters[key] for subkey in kwargs[key]: if subkey not in default_dict: allowed = ', '.join(list(default_dict.keys())) raise TypeError('Unknown subkeyword "{}" of keyword ' '"{}". Must be one of: {}'.format( subkey, key, allowed)) changed_parameters = Calculator.set(self, **kwargs) for key in ['setups', 'basis']: if key in changed_parameters: dct = changed_parameters[key] if isinstance(dct, dict) and None in dct: dct['default'] = dct.pop(None) warnings.warn('Please use {key}={dct}'.format(key=key, dct=dct)) # We need to handle txt early in order to get logging up and running: if 'txt' in changed_parameters: self.log.fd = changed_parameters.pop('txt') if not changed_parameters: return {} self.initialized = False self.scf = None self.results = {} self.log('Input parameters:') self.log.print_dict(changed_parameters) self.log() for key in changed_parameters: if key in ['eigensolver', 'convergence'] and self.wfs: self.wfs.set_eigensolver(None) if key in [ 'mixer', 'verbose', 'txt', 'hund', 'random', 'eigensolver', 'idiotproof' ]: continue if key in ['convergence', 'fixdensity', 'maxiter']: continue # Check nested arguments if key in ['experimental']: changed_parameters2 = changed_parameters[key] for key2 in changed_parameters2: if key2 in ['kpt_refine', 'magmoms', 'soc']: self.wfs = None elif key2 in ['reuse_wfs_method', 'niter_fixdensity']: continue else: raise TypeError('Unknown keyword argument:', key2) continue # More drastic changes: if self.wfs: self.wfs.set_orthonormalized(False) if key in ['external', 'xc', 'poissonsolver']: self.hamiltonian = None elif key in ['occupations', 'width']: pass elif key in ['charge', 'background_charge']: self.hamiltonian = None self.density = None self.wfs = None elif key in ['kpts', 'nbands', 'symmetry']: self.wfs = None elif key in ['h', 'gpts', 'setups', 'spinpol', 'dtype', 'mode']: self.density = None self.hamiltonian = None self.wfs = None elif key in ['basis']: self.wfs = None else: raise TypeError('Unknown keyword argument: "%s"' % key) def initialize_positions(self, atoms=None): """Update the positions of the atoms.""" self.log('Initializing position-dependent things.\n') if atoms is None: atoms = self.atoms else: atoms = atoms.copy() self._set_atoms(atoms) mpi.synchronize_atoms(atoms, self.world) rank_a = self.wfs.gd.get_ranks_from_positions(self.spos_ac) atom_partition = AtomPartition(self.wfs.gd.comm, rank_a, name='gd') self.wfs.set_positions(self.spos_ac, atom_partition) self.density.set_positions(self.spos_ac, atom_partition) self.hamiltonian.set_positions(self.spos_ac, atom_partition) def set_positions(self, atoms=None): """Update the positions of the atoms and initialize wave functions.""" self.initialize_positions(atoms) nlcao, nrand = self.wfs.initialize(self.density, self.hamiltonian, self.spos_ac) if nlcao + nrand: self.log('Creating initial wave functions:') if nlcao: self.log(' ', plural(nlcao, 'band'), 'from LCAO basis set') if nrand: self.log(' ', plural(nrand, 'band'), 'from random numbers') self.log() self.wfs.eigensolver.reset() self.scf.reset() print_positions(self.atoms, self.log, self.density.magmom_av) def initialize(self, atoms=None, reading=False): """Inexpensive initialization.""" self.log('Initialize ...\n') if atoms is None: atoms = self.atoms else: atoms = atoms.copy() self._set_atoms(atoms) par = self.parameters natoms = len(atoms) cell_cv = atoms.get_cell() / Bohr number_of_lattice_vectors = cell_cv.any(axis=1).sum() if number_of_lattice_vectors < 3: raise ValueError( 'GPAW requires 3 lattice vectors. Your system has {}.'.format( number_of_lattice_vectors)) pbc_c = atoms.get_pbc() assert len(pbc_c) == 3 magmom_a = atoms.get_initial_magnetic_moments() if par.experimental.get('magmoms') is not None: magmom_av = np.array(par.experimental['magmoms'], float) collinear = False else: magmom_av = np.zeros((natoms, 3)) magmom_av[:, 2] = magmom_a collinear = True mpi.synchronize_atoms(atoms, self.world) # Generate new xc functional only when it is reset by set # XXX sounds like this should use the _changed_keywords dictionary. if self.hamiltonian is None or self.hamiltonian.xc is None: if isinstance(par.xc, (basestring, dict)): xc = XC(par.xc, collinear=collinear, atoms=atoms) else: xc = par.xc else: xc = self.hamiltonian.xc mode = par.mode if isinstance(mode, basestring): mode = {'name': mode} if isinstance(mode, dict): mode = create_wave_function_mode(**mode) if par.dtype == complex: warnings.warn('Use mode={}(..., force_complex_dtype=True) ' 'instead of dtype=complex'.format(mode.name.upper())) mode.force_complex_dtype = True del par['dtype'] par.mode = mode if xc.orbital_dependent and mode.name == 'lcao': raise ValueError('LCAO mode does not support ' 'orbital-dependent XC functionals.') realspace = (mode.name != 'pw' and mode.interpolation != 'fft') if not realspace: pbc_c = np.ones(3, bool) self.create_setups(mode, xc) if par.hund: if natoms != 1: raise ValueError('hund=True arg only valid for single atoms!') spinpol = True magmom_av[0, 2] = self.setups[0].get_hunds_rule_moment(par.charge) if collinear: magnetic = magmom_av.any() spinpol = par.spinpol if spinpol is None: spinpol = magnetic elif magnetic and not spinpol: raise ValueError('Non-zero initial magnetic moment for a ' + 'spin-paired calculation!') nspins = 1 + int(spinpol) if spinpol: self.log('Spin-polarized calculation.') self.log('Magnetic moment: {:.6f}\n'.format(magmom_av.sum())) else: self.log('Spin-paired calculation\n') else: nspins = 1 self.log('Non-collinear calculation.') self.log('Magnetic moment: ({:.6f}, {:.6f}, {:.6f})\n'.format( *magmom_av.sum(0))) if isinstance(par.background_charge, dict): background = create_background_charge(**par.background_charge) else: background = par.background_charge nao = self.setups.nao nvalence = self.setups.nvalence - par.charge if par.background_charge is not None: nvalence += background.charge M = np.linalg.norm(magmom_av.sum(0)) nbands = par.nbands orbital_free = any(setup.orbital_free for setup in self.setups) if orbital_free: nbands = 1 if isinstance(nbands, basestring): if nbands == 'nao': nbands = nao elif nbands[-1] == '%': basebands = (nvalence + M) / 2 nbands = int(np.ceil(float(nbands[:-1]) / 100 * basebands)) else: raise ValueError('Integer expected: Only use a string ' 'if giving a percentage of occupied bands') if nbands is None: # Number of bound partial waves: nbandsmax = sum(setup.get_default_nbands() for setup in self.setups) nbands = int(np.ceil((1.2 * (nvalence + M) / 2))) + 4 if nbands > nbandsmax: nbands = nbandsmax if mode.name == 'lcao' and nbands > nao: nbands = nao elif nbands <= 0: nbands = max(1, int(nvalence + M + 0.5) // 2 + (-nbands)) if nbands > nao and mode.name == 'lcao': raise ValueError('Too many bands for LCAO calculation: ' '%d bands and only %d atomic orbitals!' % (nbands, nao)) if nvalence < 0: raise ValueError( 'Charge %f is not possible - not enough valence electrons' % par.charge) if nvalence > 2 * nbands and not orbital_free: raise ValueError('Too few bands! Electrons: %f, bands: %d' % (nvalence, nbands)) self.create_occupations(magmom_av[:, 2].sum(), orbital_free) if self.scf is None: self.create_scf(nvalence, mode) self.create_symmetry(magmom_av, cell_cv) if not collinear: nbands *= 2 if not self.wfs: self.create_wave_functions(mode, realspace, nspins, collinear, nbands, nao, nvalence, self.setups, cell_cv, pbc_c) else: self.wfs.set_setups(self.setups) if not self.wfs.eigensolver: self.create_eigensolver(xc, nbands, mode) if self.density is None and not reading: assert not par.fixdensity, 'No density to fix!' olddens = None if (self.density is not None and (self.density.gd.parsize_c != self.wfs.gd.parsize_c).any()): # Domain decomposition has changed, so we need to # reinitialize density and hamiltonian: if par.fixdensity: olddens = self.density self.density = None self.hamiltonian = None if self.density is None: self.create_density(realspace, mode, background) # XXXXXXXXXX if setups change, then setups.core_charge may change. # But that parameter was supplied in Density constructor! # This surely is a bug! self.density.initialize(self.setups, self.timer, magmom_av, par.hund) self.density.set_mixer(par.mixer) if self.density.mixer.driver.name == 'dummy' or par.fixdensity: self.log('No density mixing\n') else: self.log(self.density.mixer, '\n') self.density.fixed = par.fixdensity self.density.log = self.log if olddens is not None: self.density.initialize_from_other_density(olddens, self.wfs.kptband_comm) if self.hamiltonian is None: self.create_hamiltonian(realspace, mode, xc) xc.initialize(self.density, self.hamiltonian, self.wfs, self.occupations) description = xc.get_description() if description is not None: self.log('XC parameters: {}\n'.format('\n '.join( description.splitlines()))) if xc.name == 'GLLBSC' and olddens is not None: xc.heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelp(olddens) self.print_memory_estimate(maxdepth=memory_estimate_depth + 1) print_parallelization_details(self.wfs, self.hamiltonian, self.log) self.log('Number of atoms:', natoms) self.log('Number of atomic orbitals:', self.wfs.setups.nao) self.log('Number of bands in calculation:', self.wfs.bd.nbands) self.log('Bands to converge: ', end='') n = par.convergence.get('bands', 'occupied') if n == 'occupied': self.log('occupied states only') elif n == 'all': self.log('all') else: self.log('%d lowest bands' % n) self.log('Number of valence electrons:', self.wfs.nvalence) self.log(flush=True) self.timer.print_info(self) if dry_run: self.dry_run() if (realspace and self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT'): self.hamiltonian.poisson.set_density(self.density) self.hamiltonian.poisson.print_messages(self.log) self.log.fd.flush() self.initialized = True self.log('... initialized\n') def create_setups(self, mode, xc): if self.parameters.filter is None and mode.name != 'pw': gamma = 1.6 N_c = self.parameters.get('gpts') if N_c is None: h = (self.parameters.h or 0.2) / Bohr else: icell_vc = np.linalg.inv(self.atoms.cell) h = ((icell_vc**2).sum(0)**-0.5 / N_c).max() / Bohr def filter(rgd, rcut, f_r, l=0): gcut = np.pi / h - 2 / rcut / gamma ftmp = rgd.filter(f_r, rcut * gamma, gcut, l) f_r[:] = ftmp[:len(f_r)] else: filter = self.parameters.filter Z_a = self.atoms.get_atomic_numbers() self.setups = Setups(Z_a, self.parameters.setups, self.parameters.basis, xc, filter, self.world) self.log(self.setups) def create_grid_descriptor(self, N_c, cell_cv, pbc_c, domain_comm, parsize_domain): return GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) def create_occupations(self, magmom, orbital_free): occ = self.parameters.occupations if occ is None: if orbital_free: occ = {'name': 'orbital-free'} else: width = self.parameters.width if width is not None: warnings.warn( 'Please use occupations=FermiDirac({})'.format(width)) elif self.atoms.pbc.any(): width = 0.1 # eV else: width = 0.0 occ = {'name': 'fermi-dirac', 'width': width} if isinstance(occ, dict): occ = create_occupation_number_object(**occ) if self.parameters.fixdensity: occ.fixed_fermilevel = True if self.occupations: occ.fermilevel = self.occupations.fermilevel self.occupations = occ # If occupation numbers are changed, and we have wave functions, # recalculate the occupation numbers if self.wfs is not None: self.occupations.calculate(self.wfs) self.occupations.magmom = magmom self.log(self.occupations) def create_scf(self, nvalence, mode): # if mode.name == 'lcao': # niter_fixdensity = 0 # else: # niter_fixdensity = 2 nv = max(nvalence, 1) cc = self.parameters.convergence self.scf = SCFLoop( cc.get('eigenstates', 4.0e-8) / Ha**2 * nv, cc.get('energy', 0.0005) / Ha * nv, cc.get('density', 1.0e-4) * nv, cc.get('forces', np.inf) / (Ha / Bohr), self.parameters.maxiter, # XXX make sure niter_fixdensity value is *always* set from default # Subdictionary defaults seem to not be set when user provides # e.g. {}. We should change that so it works like the ordinary # parameters. self.parameters.experimental.get('niter_fixdensity', 0), nv) self.log(self.scf) def create_symmetry(self, magmom_av, cell_cv): symm = self.parameters.symmetry if symm == 'off': symm = {'point_group': False, 'time_reversal': False} m_av = magmom_av.round(decimals=3) # round off id_a = [id + tuple(m_v) for id, m_v in zip(self.setups.id_a, m_av)] self.symmetry = Symmetry(id_a, cell_cv, self.atoms.pbc, **symm) self.symmetry.analyze(self.spos_ac) self.setups.set_symmetry(self.symmetry) def create_eigensolver(self, xc, nbands, mode): # Number of bands to converge: nbands_converge = self.parameters.convergence.get('bands', 'occupied') if nbands_converge == 'all': nbands_converge = nbands elif nbands_converge != 'occupied': assert isinstance(nbands_converge, int) if nbands_converge < 0: nbands_converge += nbands eigensolver = get_eigensolver(self.parameters.eigensolver, mode, self.parameters.convergence) eigensolver.nbands_converge = nbands_converge # XXX Eigensolver class doesn't define an nbands_converge property if isinstance(xc, SIC): eigensolver.blocksize = 1 self.wfs.set_eigensolver(eigensolver) self.log('Eigensolver\n ', self.wfs.eigensolver, '\n') def create_density(self, realspace, mode, background): gd = self.wfs.gd big_gd = gd.new_descriptor(comm=self.world) # Check whether grid is too small. 8 is smallest admissible. # (we decide this by how difficult it is to make the tests pass) # (Actually it depends on stencils! But let the user deal with it) N_c = big_gd.get_size_of_global_array(pad=True) too_small = np.any(N_c / big_gd.parsize_c < 8) if (self.parallel['augment_grids'] and not too_small and mode.name != 'pw'): aux_gd = big_gd else: aux_gd = gd redistributor = GridRedistributor(self.world, self.wfs.kptband_comm, gd, aux_gd) # Construct grid descriptor for fine grids for densities # and potentials: finegd = aux_gd.refine() kwargs = dict(gd=gd, finegd=finegd, nspins=self.wfs.nspins, collinear=self.wfs.collinear, charge=self.parameters.charge + self.wfs.setups.core_charge, redistributor=redistributor, background_charge=background) if realspace: self.density = RealSpaceDensity(stencil=mode.interpolation, **kwargs) else: self.density = pw.ReciprocalSpaceDensity(**kwargs) self.log(self.density, '\n') def create_hamiltonian(self, realspace, mode, xc): dens = self.density kwargs = dict(gd=dens.gd, finegd=dens.finegd, nspins=dens.nspins, collinear=dens.collinear, setups=dens.setups, timer=self.timer, xc=xc, world=self.world, redistributor=dens.redistributor, vext=self.parameters.external, psolver=self.parameters.poissonsolver) if realspace: self.hamiltonian = RealSpaceHamiltonian(stencil=mode.interpolation, **kwargs) xc.set_grid_descriptor(self.hamiltonian.finegd) else: # This code will work if dens.redistributor uses # ordinary density.gd as aux_gd gd = dens.finegd xc_redist = None if self.parallel['augment_grids']: from gpaw.grid_descriptor import BadGridError try: aux_gd = gd.new_descriptor(comm=self.world) except BadGridError as err: import warnings warnings.warn('Ignoring augment_grids: {}'.format(err)) else: bcast_comm = dens.redistributor.broadcast_comm xc_redist = GridRedistributor(self.world, bcast_comm, gd, aux_gd) self.hamiltonian = pw.ReciprocalSpaceHamiltonian( pd2=dens.pd2, pd3=dens.pd3, realpbc_c=self.atoms.pbc, xc_redistributor=xc_redist, **kwargs) xc.set_grid_descriptor(self.hamiltonian.xc_gd) self.hamiltonian.soc = self.parameters.experimental.get('soc') self.log(self.hamiltonian, '\n') def create_kpoint_descriptor(self, nspins): par = self.parameters bzkpts_kc = kpts2ndarray(par.kpts, self.atoms) kpt_refine = par.experimental.get('kpt_refine') if kpt_refine is None: kd = KPointDescriptor(bzkpts_kc, nspins) self.timer.start('Set symmetry') kd.set_symmetry(self.atoms, self.symmetry, comm=self.world) self.timer.stop('Set symmetry') else: self.timer.start('Set k-point refinement') kd = create_kpoint_descriptor_with_refinement(kpt_refine, bzkpts_kc, nspins, self.atoms, self.symmetry, comm=self.world, timer=self.timer) self.timer.stop('Set k-point refinement') # Update quantities which might have changed, if symmetry # was changed self.symmetry = kd.symmetry self.setups.set_symmetry(kd.symmetry) self.log(kd) return kd def create_wave_functions(self, mode, realspace, nspins, collinear, nbands, nao, nvalence, setups, cell_cv, pbc_c): par = self.parameters kd = self.create_kpoint_descriptor(nspins) parallelization = mpi.Parallelization(self.world, nspins * kd.nibzkpts) parsize_kpt = self.parallel['kpt'] parsize_domain = self.parallel['domain'] parsize_bands = self.parallel['band'] ndomains = None if parsize_domain is not None: ndomains = np.prod(parsize_domain) parallelization.set(kpt=parsize_kpt, domain=ndomains, band=parsize_bands) comms = parallelization.build_communicators() domain_comm = comms['d'] kpt_comm = comms['k'] band_comm = comms['b'] kptband_comm = comms['D'] domainband_comm = comms['K'] self.comms = comms if par.gpts is not None: if par.h is not None: raise ValueError("""You can't use both "gpts" and "h"!""") N_c = np.array(par.gpts) else: h = par.h if h is not None: h /= Bohr N_c = get_number_of_grid_points(cell_cv, h, mode, realspace, kd.symmetry) self.symmetry.check_grid(N_c) kd.set_communicator(kpt_comm) parstride_bands = self.parallel['stridebands'] bd = BandDescriptor(nbands, band_comm, parstride_bands) # Construct grid descriptor for coarse grids for wave functions: gd = self.create_grid_descriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) if hasattr(self, 'time') or mode.force_complex_dtype or not collinear: dtype = complex else: if kd.gamma: dtype = float else: dtype = complex wfs_kwargs = dict(gd=gd, nvalence=nvalence, setups=setups, bd=bd, dtype=dtype, world=self.world, kd=kd, kptband_comm=kptband_comm, timer=self.timer) if self.parallel['sl_auto']: # Choose scalapack parallelization automatically for key, val in self.parallel.items(): if (key.startswith('sl_') and key != 'sl_auto' and val is not None): raise ValueError("Cannot use 'sl_auto' together " "with '%s'" % key) max_scalapack_cpus = bd.comm.size * gd.comm.size sl_default = suggest_blocking(nbands, max_scalapack_cpus) else: sl_default = self.parallel['sl_default'] if mode.name == 'lcao': assert collinear # Layouts used for general diagonalizer sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default elpasolver = None if self.parallel['use_elpa']: elpasolver = self.parallel['elpasolver'] lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, bd, domainband_comm, dtype, nao=nao, timer=self.timer, elpasolver=elpasolver) self.wfs = mode(lcaoksl, **wfs_kwargs) elif mode.name == 'fd' or mode.name == 'pw': # Use (at most) all available LCAO for initialization lcaonbands = min(nbands, nao) try: lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands) except RuntimeError: initksl = None else: # Layouts used for general diagonalizer # (LCAO initialization) sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default initksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, lcaobd, domainband_comm, dtype, nao=nao, timer=self.timer) reuse_wfs_method = par.experimental.get('reuse_wfs_method', 'paw') sl = (domainband_comm, ) + (self.parallel['sl_diagonalize'] or sl_default or (1, 1, None)) self.wfs = mode(sl, initksl, reuse_wfs_method=reuse_wfs_method, collinear=collinear, **wfs_kwargs) else: self.wfs = mode(self, collinear=collinear, **wfs_kwargs) self.log(self.wfs, '\n') def dry_run(self): # Can be overridden like in gpaw.atom.atompaw print_cell(self.wfs.gd, self.atoms.pbc, self.log) print_positions(self.atoms, self.log, self.density.magmom_av) self.log.fd.flush() # Write timing info now before the interpreter shuts down: self.__del__() # Disable timing output during shut-down: del self.timer raise SystemExit
class RPACorrelation: def __init__(self, calc, xc='RPA', filename=None, skip_gamma=False, qsym=True, nlambda=None, nfrequencies=16, frequency_max=800.0, frequency_scale=2.0, frequencies=None, weights=None, world=mpi.world, nblocks=1, wstc=False, txt=sys.stdout): if isinstance(calc, str): calc = GPAW(calc, txt=None, communicator=mpi.serial_comm) self.calc = calc if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w') self.fd = txt self.timer = Timer() if frequencies is None: frequencies, weights = get_gauss_legendre_points(nfrequencies, frequency_max, frequency_scale) user_spec = False else: assert weights is not None user_spec = True self.omega_w = frequencies / Hartree self.weight_w = weights / Hartree if nblocks > 1: assert len(self.omega_w) % nblocks == 0 assert wstc self.wstc = wstc self.nblocks = nblocks self.world = world self.skip_gamma = skip_gamma self.ibzq_qc = None self.weight_q = None self.initialize_q_points(qsym) # Energies for all q-vetors and cutoff energies: self.energy_qi = [] self.filename = filename self.print_initialization(xc, frequency_scale, nlambda, user_spec) def initialize_q_points(self, qsym): kd = self.calc.wfs.kd self.bzq_qc = kd.get_bz_q_points(first=True) if not qsym: self.ibzq_qc = self.bzq_qc self.weight_q = np.ones(len(self.bzq_qc)) / len(self.bzq_qc) else: U_scc = kd.symmetry.op_scc self.ibzq_qc = kd.get_ibz_q_points(self.bzq_qc, U_scc)[0] self.weight_q = kd.q_weights def read(self): lines = open(self.filename).readlines()[1:] n = 0 self.energy_qi = [] nq = len(lines) // len(self.ecut_i) for q_c in self.ibzq_qc[:nq]: self.energy_qi.append([]) for ecut in self.ecut_i: q1, q2, q3, ec, energy = [float(x) for x in lines[n].split()] self.energy_qi[-1].append(energy / Hartree) n += 1 if (abs(q_c - (q1, q2, q3)).max() > 1e-4 or abs(int(ecut * Hartree) - ec) > 0): self.energy_qi = [] return print('Read %d q-points from file: %s' % (nq, self.filename), file=self.fd) print(file=self.fd) def write(self): if self.world.rank == 0 and self.filename: fd = open(self.filename, 'w') print('#%9s %10s %10s %8s %12s' % ('q1', 'q2', 'q3', 'E_cut', 'E_c(q)'), file=fd) for energy_i, q_c in zip(self.energy_qi, self.ibzq_qc): for energy, ecut in zip(energy_i, self.ecut_i): print('%10.4f %10.4f %10.4f %8d %r' % (tuple(q_c) + (ecut * Hartree, energy * Hartree)), file=fd) def calculate(self, ecut, nbands=None, spin=False): """Calculate RPA correlation energy for one or several cutoffs. ecut: float or list of floats Plane-wave cutoff(s). nbands: int Number of bands (defaults to number of plane-waves). spin: bool Separate spin in response funtion. (Only needed for beyond RPA methods that inherit this function). """ p = functools.partial(print, file=self.fd) if isinstance(ecut, (float, int)): ecut = ecut * (1 + 0.5 * np.arange(6))**(-2 / 3) self.ecut_i = np.asarray(np.sort(ecut)) / Hartree ecutmax = max(self.ecut_i) if nbands is None: p('Response function bands : Equal to number of plane waves') else: p('Response function bands : %s' % nbands) p('Plane wave cutoffs (eV) :', end='') for e in self.ecut_i: p(' {0:.3f}'.format(e * Hartree), end='') p() p() if self.filename and os.path.isfile(self.filename): self.read() self.world.barrier() chi0 = Chi0(self.calc, 1j * Hartree * self.omega_w, eta=0.0, intraband=False, hilbert=False, txt=self.fd, timer=self.timer, world=self.world, no_optical_limit=self.wstc, nblocks=self.nblocks) self.blockcomm = chi0.blockcomm wfs = self.calc.wfs if self.wstc: with self.timer('WSTC-init'): p('Using Wigner-Seitz truncated Coulomb potential.') self.wstc = WignerSeitzTruncatedCoulomb( wfs.gd.cell_cv, wfs.kd.N_c, self.fd) nq = len(self.energy_qi) nw = len(self.omega_w) nGmax = max(count_reciprocal_vectors(ecutmax, wfs.gd, q_c) for q_c in self.ibzq_qc[nq:]) mynGmax = (nGmax + self.nblocks - 1) // self.nblocks nx = (1 + spin) * nw * mynGmax * nGmax A1_x = np.empty(nx, complex) if self.nblocks > 1: A2_x = np.empty(nx, complex) else: A2_x = None self.timer.start('RPA') for q_c in self.ibzq_qc[nq:]: if np.allclose(q_c, 0.0) and self.skip_gamma: self.energy_qi.append(len(self.ecut_i) * [0.0]) self.write() p('Not calculating E_c(q) at Gamma') p() continue thisqd = KPointDescriptor([q_c]) pd = PWDescriptor(ecutmax, wfs.gd, complex, thisqd) nG = pd.ngmax mynG = (nG + self.nblocks - 1) // self.nblocks chi0.Ga = self.blockcomm.rank * mynG chi0.Gb = min(chi0.Ga + mynG, nG) shape = (1 + spin, nw, chi0.Gb - chi0.Ga, nG) chi0_swGG = A1_x[:np.prod(shape)].reshape(shape) chi0_swGG[:] = 0.0 if self.wstc or np.allclose(q_c, 0.0): # Wings (x=0,1) and head (G=0) for optical limit and three # directions (v=0,1,2): chi0_swxvG = np.zeros((1 + spin, nw, 2, 3, nG), complex) chi0_swvv = np.zeros((1 + spin, nw, 3, 3), complex) else: chi0_swxvG = None chi0_swvv = None Q_aGii = chi0.initialize_paw_corrections(pd) # First not completely filled band: m1 = chi0.nocc1 p('# %s - %s' % (len(self.energy_qi), ctime().split()[-2])) p('q = [%1.3f %1.3f %1.3f]' % tuple(q_c)) energy_i = [] for ecut in self.ecut_i: if ecut == ecutmax: # Nothing to cut away: cut_G = None m2 = nbands or nG else: cut_G = np.arange(nG)[pd.G2_qG[0] <= 2 * ecut] m2 = len(cut_G) p('E_cut = %d eV / Bands = %d:' % (ecut * Hartree, m2)) self.fd.flush() energy = self.calculate_q(chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, Q_aGii, m1, m2, cut_G, A2_x) energy_i.append(energy) m1 = m2 a = 1 / chi0.kncomm.size if ecut < ecutmax and a != 1.0: # Chi0 will be summed again over chicomm, so we divide # by its size: chi0_swGG *= a if chi0_swxvG is not None: chi0_swxvG *= a chi0_swvv *= a self.energy_qi.append(energy_i) self.write() p() e_i = np.dot(self.weight_q, np.array(self.energy_qi)) p('==========================================================') p() p('Total correlation energy:') for e_cut, e in zip(self.ecut_i, e_i): p('%6.0f: %6.4f eV' % (e_cut * Hartree, e * Hartree)) p() self.energy_qi = [] # important if another calculation is performed if len(e_i) > 1: self.extrapolate(e_i) p('Calculation completed at: ', ctime()) p() self.timer.stop('RPA') self.timer.write(self.fd) self.fd.flush() return e_i * Hartree @timer('chi0(q)') def calculate_q(self, chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, Q_aGii, m1, m2, cut_G, A2_x): chi0_wGG = chi0_swGG[0] if chi0_swxvG is not None: chi0_wxvG = chi0_swxvG[0] chi0_wvv = chi0_swvv[0] else: chi0_wxvG = None chi0_wvv = None chi0._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv, Q_aGii, m1, m2, [0, 1]) print('E_c(q) = ', end='', file=self.fd) chi0_wGG = chi0.redistribute(chi0_wGG, A2_x) if not pd.kd.gamma or self.wstc: e = self.calculate_energy(pd, chi0_wGG, cut_G) print('%.3f eV' % (e * Hartree), file=self.fd) self.fd.flush() else: e = 0.0 for v in range(3): chi0_wGG[:, 0] = chi0_wxvG[:, 0, v] chi0_wGG[:, :, 0] = chi0_wxvG[:, 1, v] chi0_wGG[:, 0, 0] = chi0_wvv[:, v, v] ev = self.calculate_energy(pd, chi0_wGG, cut_G) e += ev print('%.3f' % (ev * Hartree), end='', file=self.fd) if v < 2: print('/', end='', file=self.fd) else: print(' eV', file=self.fd) self.fd.flush() e /= 3 return e @timer('Energy') def calculate_energy(self, pd, chi0_wGG, cut_G): """Evaluate correlation energy from chi0.""" if self.wstc: invG_G = (self.wstc.get_potential(pd) / (4 * pi))**0.5 else: G_G = pd.G2_qG[0]**0.5 # |G+q| if pd.kd.gamma: G_G[0] = 1.0 invG_G = 1.0 / G_G if cut_G is not None: invG_G = invG_G[cut_G] nG = len(invG_G) e_w = [] for chi0_GG in chi0_wGG: if cut_G is not None: chi0_GG = chi0_GG.take(cut_G, 0).take(cut_G, 1) e_GG = (np.eye(nG) - 4 * np.pi * chi0_GG * invG_G * invG_G[:, np.newaxis]) e = np.log(np.linalg.det(e_GG)) + nG - np.trace(e_GG) e_w.append(e.real) E_w = np.zeros_like(self.omega_w) self.blockcomm.all_gather(np.array(e_w), E_w) energy = np.dot(E_w, self.weight_w) / (2 * np.pi) self.E_w = E_w return energy def extrapolate(self, e_i): print('Extrapolated energies:', file=self.fd) ex_i = [] for i in range(len(e_i) - 1): e1, e2 = e_i[i:i + 2] x1, x2 = self.ecut_i[i:i + 2]**-1.5 ex = (e1 * x2 - e2 * x1) / (x2 - x1) ex_i.append(ex) print(' %4.0f -%4.0f: %5.3f eV' % (self.ecut_i[i] * Hartree, self.ecut_i[i + 1] * Hartree, ex * Hartree), file=self.fd) print(file=self.fd) self.fd.flush() return e_i * Hartree def print_initialization(self, xc, frequency_scale, nlambda, user_spec): p = functools.partial(print, file=self.fd) p('----------------------------------------------------------') p('Non-self-consistent %s correlation energy' % xc) p('----------------------------------------------------------') p('Started at: ', ctime()) p() p('Atoms :', self.calc.atoms.get_chemical_formula(mode='hill')) p('Ground state XC functional :', self.calc.hamiltonian.xc.name) p('Valence electrons :', self.calc.wfs.setups.nvalence) p('Number of bands :', self.calc.wfs.bd.nbands) p('Number of spins :', self.calc.wfs.nspins) p('Number of k-points :', len(self.calc.wfs.kd.bzk_kc)) p('Number of irreducible k-points :', len(self.calc.wfs.kd.ibzk_kc)) p('Number of q-points :', len(self.bzq_qc)) p('Number of irreducible q-points :', len(self.ibzq_qc)) p() for q, weight in zip(self.ibzq_qc, self.weight_q): p(' q: [%1.4f %1.4f %1.4f] - weight: %1.3f' % (q[0], q[1], q[2], weight)) p() p('----------------------------------------------------------') p('----------------------------------------------------------') p() if nlambda is None: p('Analytical coupling constant integration') else: p('Numerical coupling constant integration using', nlambda, 'Gauss-Legendre points') p() p('Frequencies') if not user_spec: p(' Gauss-Legendre integration with %s frequency points' % len(self.omega_w)) p(' Transformed from [0,oo] to [0,1] using e^[-aw^(1/B)]') p(' Highest frequency point at %5.1f eV and B=%1.1f' % (self.omega_w[-1] * Hartree, frequency_scale)) else: p(' User specified frequency integration with', len(self.omega_w), 'frequency points') p() p('Parallelization') p(' Total number of CPUs : % s' % self.world.size) p(' G-vector decomposition : % s' % self.nblocks) p(' K-point/band decomposition : % s' % (self.world.size // self.nblocks)) p()
class ResonantRaman(Vibrations): """Class for calculating vibrational modes and resonant Raman intensities using finite difference. atoms: Atoms object Excitations: Class to calculate the excitations. The class object is initialized as:: Excitations(atoms.get_calculator()) or by reading form a file as:: Excitations('filename', **exkwargs) The file is written by calling the method Excitations.write('filename'). Excitations should work like a list of ex obejects, where: ex.get_dipole_me(form='v'): gives the dipole matrix element in |e| * Angstrom ex.energy: is the transition energy in Hartrees """ def __init__( self, atoms, Excitations, indices=None, gsname='rraman', # name for ground state calculations exname=None, # name for excited state calculations delta=0.01, nfree=2, directions=None, approximation='Profeta', observation={'geometry': '-Z(XX)Z'}, exkwargs={}, # kwargs to be passed to Excitations exext='.ex.gz', # extension for Excitation names txt='-', verbose=False, ): assert (nfree == 2) Vibrations.__init__(self, atoms, indices, gsname, delta, nfree) self.name = gsname + '-d%.3f' % delta if exname is None: exname = gsname self.exname = exname + '-d%.3f' % delta self.exext = exext if directions is None: self.directions = np.array([0, 1, 2]) else: self.directions = np.array(directions) self.approximation = approximation self.observation = observation self.exobj = Excitations self.exkwargs = exkwargs self.timer = Timer() self.txt = convert_string_to_fd(txt) self.verbose = verbose @staticmethod def m2(z): return (z * z.conj()).real def log(self, message, pre='# ', end='\n'): if self.verbose: self.txt.write(pre + message + end) self.txt.flush() def calculate(self, filename, fd): """Call ground and excited state calculation""" self.timer.start('Ground state') forces = self.atoms.get_forces() if rank == 0: pickle.dump(forces, fd, protocol=2) fd.close() self.timer.stop('Ground state') self.timer.start('Excitations') basename, _ = os.path.splitext(filename) excitations = self.exobj(self.atoms.get_calculator(), **self.exkwargs) excitations.write(basename + self.exext) self.timer.stop('Excitations') def read_excitations(self): self.timer.start('read excitations') self.timer.start('really read') self.log('reading ' + self.exname + '.eq' + self.exext) ex0_object = self.exobj(self.exname + '.eq' + self.exext, **self.exkwargs) self.timer.stop('really read') self.timer.start('index') matching = frozenset(ex0_object) self.timer.stop('index') def append(lst, exname, matching): self.timer.start('really read') self.log('reading ' + exname, end=' ') exo = self.exobj(exname, **self.exkwargs) lst.append(exo) self.timer.stop('really read') self.timer.start('index') matching = matching.intersection(exo) self.log('len={0}, matching={1}'.format(len(exo), len(matching)), pre='') self.timer.stop('index') return matching exm_object_list = [] exp_object_list = [] for a in self.indices: for i in 'xyz': name = '%s.%d%s' % (self.exname, a, i) matching = append(exm_object_list, name + '-' + self.exext, matching) matching = append(exp_object_list, name + '+' + self.exext, matching) self.ndof = 3 * len(self.indices) self.nex = len(matching) self.timer.stop('read excitations') self.timer.start('select') def select(exl, matching): mlst = [ex for ex in exl if ex in matching] assert (len(mlst) == len(matching)) return mlst ex0 = select(ex0_object, matching) exm = [] exp = [] r = 0 for a in self.indices: for i in 'xyz': exm.append(select(exm_object_list[r], matching)) exp.append(select(exp_object_list[r], matching)) r += 1 self.timer.stop('select') self.timer.start('me and energy') eu = units.Hartree self.ex0E_p = np.array([ex.energy * eu for ex in ex0]) self.ex0m_pc = np.array([ex.get_dipole_me(form='v') for ex in ex0]) exmE_rp = [] expE_rp = [] exF_rp = [] exmm_rpc = [] expm_rpc = [] r = 0 for a in self.indices: for i in 'xyz': exmE_rp.append([em.energy for em in exm[r]]) expE_rp.append([ep.energy for ep in exp[r]]) exF_rp.append([(ep.energy - em.energy) for ep, em in zip(exp[r], exm[r])]) exmm_rpc.append([ex.get_dipole_me(form='v') for ex in exm[r]]) expm_rpc.append([ex.get_dipole_me(form='v') for ex in exp[r]]) r += 1 self.exmE_rp = np.array(exmE_rp) * eu self.expE_rp = np.array(expE_rp) * eu self.exF_rp = np.array(exF_rp) * eu / 2 / self.delta self.exmm_rpc = np.array(exmm_rpc) self.expm_rpc = np.array(expm_rpc) self.timer.stop('me and energy') def read(self, method='standard', direction='central'): """Read data from a pre-performed calculation.""" if not hasattr(self, 'modes'): self.timer.start('read vibrations') Vibrations.read(self, method, direction) # we now have: # self.H : Hessian matrix # self.im : 1./sqrt(masses) # self.modes : Eigenmodes of the mass weighted H self.om_r = self.hnu.real # energies in eV self.timer.stop('read vibrations') if not hasattr(self, 'ex0E_p'): self.read_excitations() def get_Huang_Rhys_factors(self, forces_r): """Evaluate Huang-Rhys factors derived from forces.""" self.timer.start('Huang-Rhys') assert (len(forces_r.flat) == self.ndof) # solve the matrix equation for the equilibrium displacements # XXX why are the forces mass weighted ??? X_r = np.linalg.solve(self.im[:, None] * self.H * self.im, forces_r.flat * self.im) d_r = np.dot(self.modes, X_r) # Huang-Rhys factors S s = 1.e-20 / units.kg / units.C / units._hbar**2 # SI units self.timer.stop('Huang-Rhys') return s * d_r**2 * self.om_r / 2. def get_matrix_element_AlbrechtA(self, omega, gamma=0.1, ml=range(16)): """Evaluate Albrecht A term. Unit: |e|^2Angstrom^2/eV """ self.read() self.timer.start('AlbrechtA') if not hasattr(self, 'fco'): self.fco = FranckCondonOverlap() # excited state forces F_pr = self.exF_rp.T m_rcc = np.zeros((self.ndof, 3, 3), dtype=complex) for p, energy in enumerate(self.ex0E_p): S_r = self.get_Huang_Rhys_factors(F_pr[p]) me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj()) for m in ml: self.timer.start('0mm1') fco_r = self.fco.direct0mm1(m, S_r) self.timer.stop('0mm1') self.timer.start('einsum') m_rcc += np.einsum( 'a,bc->abc', fco_r / (energy + m * self.om_r - omega - 1j * gamma), me_cc) m_rcc += np.einsum( 'a,bc->abc', fco_r / (energy + (m - 1) * self.om_r + omega + 1j * gamma), me_cc) self.timer.stop('einsum') self.timer.stop('AlbrechtA') return m_rcc def get_matrix_element_AlbrechtBC(self, omega, gamma=0.1, ml=[1], term='BC'): """Evaluate Albrecht B and/or C term(s).""" self.read() self.timer.start('AlbrechtBC') if not hasattr(self, 'fco'): self.fco = FranckCondonOverlap() # excited state forces F_pr = self.exF_rp.T m_rcc = np.zeros((self.ndof, 3, 3), dtype=complex) for p, energy in enumerate(self.ex0E_p): S_r = self.get_Huang_Rhys_factors(F_pr[p]) for m in ml: self.timer.start('Franck-Condon overlaps') fc1mm1_r = self.fco.direct(1, m, S_r) fc0mm02_r = self.fco.direct(0, m, S_r) fc0mm02_r += np.sqrt(2) * self.fco.direct0mm2(m, S_r) # XXXXX fc1mm1_r[-1] = 1 fc0mm02_r[-1] = 1 print(m, fc1mm1_r[-1], fc0mm02_r[-1]) self.timer.stop('Franck-Condon overlaps') self.timer.start('me dervivatives') dm_rc = [] r = 0 for a in self.indices: for i in 'xyz': dm_rc.append( (self.expm_rpc[r, p] - self.exmm_rpc[r, p]) * self.im[r]) print('pm=', self.expm_rpc[r, p], self.exmm_rpc[r, p]) r += 1 dm_rc = np.array(dm_rc) / (2 * self.delta) self.timer.stop('me dervivatives') self.timer.start('map to modes') # print('dm_rc[2], dm_rc[5]', dm_rc[2], dm_rc[5]) print('dm_rc=', dm_rc) dm_rc = np.dot(dm_rc.T, self.modes.T).T print('dm_rc[-1][2]', dm_rc[-1][2]) self.timer.stop('map to modes') self.timer.start('multiply') # me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj()) for r in range(self.ndof): if 'B' in term: # XXXX denom = (1. / (energy + m * 0 * self.om_r[r] - omega - 1j * gamma)) # ok print('denom=', denom) m_rcc[r] += ( np.outer(dm_rc[r], self.ex0m_pc[p].conj()) * fc1mm1_r[r] * denom) if r == 5: print('m_rcc[r]=', m_rcc[r][2, 2]) m_rcc[r] += ( np.outer(self.ex0m_pc[p], dm_rc[r].conj()) * fc0mm02_r[r] * denom) if 'C' in term: denom = (1. / (energy + (m - 1) * self.om_r[r] + omega + 1j * gamma)) m_rcc[r] += ( np.outer(self.ex0m_pc[p], dm_rc[r].conj()) * fc1mm1_r[r] * denom) m_rcc[r] += ( np.outer(dm_rc[r], self.ex0m_pc[p].conj()) * fc0mm02_r[r] * denom) self.timer.stop('multiply') print('m_rcc[-1]=', m_rcc[-1][2, 2]) self.timer.start('pre_r') with np.errstate(divide='ignore'): pre_r = np.where(self.om_r > 0, np.sqrt(units._hbar**2 / 2. / self.om_r), 0) # print('BC: pre_r=', pre_r) for r, p in enumerate(pre_r): m_rcc[r] *= p self.timer.stop('pre_r') self.timer.stop('AlbrechtBC') return m_rcc def get_matrix_element_Profeta(self, omega, gamma=0.1, energy_derivative=False): """Evaluate Albrecht B+C term in Profeta and Mauri approximation""" self.read() self.timer.start('amplitudes') self.timer.start('init') V_rcc = np.zeros((self.ndof, 3, 3), dtype=complex) pre = 1. / (2 * self.delta) self.timer.stop('init') def kappa(me_pc, e_p, omega, gamma, form='v'): """Kappa tensor after Profeta and Mauri PRB 63 (2001) 245415""" me_ccp = np.empty((3, 3, len(e_p)), dtype=complex) for p, me_c in enumerate(me_pc): me_ccp[:, :, p] = np.outer(me_pc[p], me_pc[p].conj()) # print('kappa: me_ccp=', me_ccp[2,2,0]) # ok print('kappa: den=', 1./(e_p - omega - 1j * gamma)) kappa_ccp = (me_ccp / (e_p - omega - 1j * gamma) + me_ccp.conj() / (e_p + omega + 1j * gamma)) return kappa_ccp.sum(2) self.timer.start('kappa') r = 0 for a in self.indices: for i in 'xyz': if not energy_derivative < 0: V_rcc[r] = pre * self.im[r] * ( kappa(self.expm_rpc[r], self.ex0E_p, omega, gamma) - kappa(self.exmm_rpc[r], self.ex0E_p, omega, gamma)) if energy_derivative: V_rcc[r] += pre * self.im[r] * ( kappa(self.ex0m_pc, self.expE_rp[r], omega, gamma) - kappa(self.ex0m_pc, self.exmE_rp[r], omega, gamma)) r += 1 self.timer.stop('kappa') # print('V_rcc[2], V_rcc[5]=', V_rcc[2,2,2], V_rcc[5,2,2]) self.timer.stop('amplitudes') # map to modes self.timer.start('pre_r') with np.errstate(divide='ignore'): pre_r = np.where(self.om_r > 0, np.sqrt(units._hbar**2 / 2. / self.om_r), 0) V_rcc = np.dot(V_rcc.T, self.modes.T).T # looks ok print('self.modes.T[-1]',self.modes.T) # looks ok print('V_rcc[-1]=', V_rcc[-1][2,2]) # ok print('Profeta: pre_r=', pre_r) for r, p in enumerate(pre_r): V_rcc[r] *= p self.timer.stop('pre_r') return V_rcc def get_matrix_element(self, omega, gamma): self.read() V_rcc = np.zeros((self.ndof, 3, 3), dtype=complex) if self.approximation.lower() == 'profeta': V_rcc += self.get_matrix_element_Profeta(omega, gamma) elif self.approximation.lower() == 'placzek': V_rcc += self.get_matrix_element_Profeta(omega, gamma, True) elif self.approximation.lower() == 'p-p': V_rcc += self.get_matrix_element_Profeta(omega, gamma, -1) elif self.approximation.lower() == 'albrecht a': V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma) elif self.approximation.lower() == 'albrecht b': raise NotImplementedError('not working') V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma, term='B') elif self.approximation.lower() == 'albrecht c': raise NotImplementedError('not working') V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma, term='C') elif self.approximation.lower() == 'albrecht bc': raise NotImplementedError('not working') V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma) elif self.approximation.lower() == 'albrecht': raise NotImplementedError('not working') V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma) V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma) elif self.approximation.lower() == 'albrecht+profeta': V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma) V_rcc += self.get_matrix_element_Profeta(omega, gamma) else: raise NotImplementedError( 'Approximation {0} not implemented. '.format( self.approximation) + 'Please use "Profeta", "Albrecht A/B/C/BC", ' + 'or "Albrecht".') return V_rcc def get_intensities(self, omega, gamma=0.1): m2 = ResonantRaman.m2 alpha_rcc = self.get_matrix_element(omega, gamma) if not self.observation: # XXXX remove """Simple sum, maybe too simple""" return m2(alpha_rcc).sum(axis=1).sum(axis=1) # XXX enable when appropraiate # if self.observation['orientation'].lower() != 'random': # raise NotImplementedError('not yet') # random orientation of the molecular frame # Woodward & Long, # Guthmuller, J. J. Chem. Phys. 2016, 144 (6), 64106 m2 = ResonantRaman.m2 alpha2_r = m2(alpha_rcc[:, 0, 0] + alpha_rcc[:, 1, 1] + alpha_rcc[:, 2, 2]) / 9. delta2_r = 3 / 4. * (m2(alpha_rcc[:, 0, 1] - alpha_rcc[:, 1, 0]) + m2(alpha_rcc[:, 0, 2] - alpha_rcc[:, 2, 0]) + m2(alpha_rcc[:, 1, 2] - alpha_rcc[:, 2, 1])) gamma2_r = (3 / 4. * (m2(alpha_rcc[:, 0, 1] + alpha_rcc[:, 1, 0]) + m2(alpha_rcc[:, 0, 2] + alpha_rcc[:, 2, 0]) + m2(alpha_rcc[:, 1, 2] + alpha_rcc[:, 2, 1])) + (m2(alpha_rcc[:, 0, 0] - alpha_rcc[:, 1, 1]) + m2(alpha_rcc[:, 0, 0] - alpha_rcc[:, 2, 2]) + m2(alpha_rcc[:, 1, 1] - alpha_rcc[:, 2, 2])) / 2) if self.observation['geometry'] == '-Z(XX)Z': # Porto's notation return (45 * alpha2_r + 5 * delta2_r + 4 * gamma2_r) / 45. elif self.observation['geometry'] == '-Z(XY)Z': # Porto's notation return gamma2_r / 15. elif self.observation['scattered'] == 'Z': # scattered light in direction of incoming light return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45. elif self.observation['scattered'] == 'parallel': # scattered light perendicular and # polarization in plane return 6 * gamma2_r / 45. elif self.observation['scattered'] == 'perpendicular': # scattered light perendicular and # polarization out of plane return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45. else: raise NotImplementedError def get_cross_sections(self, omega, gamma=0.1): I_r = self.get_intensities(omega, gamma) pre = 1. / 16 / np.pi**2 / units.eps0**2 / units.c**4 # frequency of scattered light omS_r = omega - self.hnu return pre * omega * omS_r**3 * I_r def get_spectrum(self, omega, gamma=0.1, start=200.0, end=4000.0, npts=None, width=4.0, type='Gaussian', method='standard', direction='central', intensity_unit='????', normalize=False): """Get resonant Raman spectrum. The method returns wavenumbers in cm^-1 with corresponding Raman cross section. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1. """ self.type = type.lower() assert self.type in ['gaussian', 'lorentzian'] if not npts: npts = int((end - start) / width * 10 + 1) frequencies = self.get_frequencies(method, direction).real intensities = self.get_cross_sections(omega, gamma) prefactor = 1 if type == 'lorentzian': intensities = intensities * width * np.pi / 2. if normalize: prefactor = 2. / width / np.pi else: sigma = width / 2. / np.sqrt(2. * np.log(2.)) if normalize: prefactor = 1. / sigma / np.sqrt(2 * np.pi) # Make array with spectrum data spectrum = np.empty(npts) energies = np.linspace(start, end, npts) for i, energy in enumerate(energies): energies[i] = energy if type == 'lorentzian': spectrum[i] = (intensities * 0.5 * width / np.pi / ( (frequencies - energy)**2 + 0.25 * width**2)).sum() else: spectrum[i] = ( intensities * np.exp(-(frequencies - energy)**2 / 2. / sigma**2)).sum() return [energies, prefactor * spectrum] def write_spectrum(self, omega, gamma, out='resonant-raman-spectra.dat', start=200, end=4000, npts=None, width=10, type='Gaussian', method='standard', direction='central'): """Write out spectrum to file. First column is the wavenumber in cm^-1, the second column the absolute infrared intensities, and the third column the absorbance scaled so that data runs from 1 to 0. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1.""" energies, spectrum = self.get_spectrum(omega, gamma, start, end, npts, width, type, method, direction) # Write out spectrum in file. First column is absolute intensities. outdata = np.empty([len(energies), 3]) outdata.T[0] = energies outdata.T[1] = spectrum fd = open(out, 'w') fd.write('# Resonant Raman spectrum\n') fd.write('# omega={0:g} eV, gamma={1:g} eV\n'.format(omega, gamma)) fd.write('# %s folded, width=%g cm^-1\n' % (type.title(), width)) fd.write('# [cm^-1] [a.u.]\n') for row in outdata: fd.write('%.3f %15.5g\n' % (row[0], row[1])) fd.close() def summary(self, omega, gamma=0.1, method='standard', direction='central', log=sys.stdout): """Print summary for given omega [eV]""" hnu = self.get_energies(method, direction) s = 0.01 * units._e / units._c / units._hplanck intensities = self.get_intensities(omega, gamma) if isinstance(log, basestring): log = paropen(log, 'a') parprint('-------------------------------------', file=log) parprint(' excitation at ' + str(omega) + ' eV', file=log) parprint(' gamma ' + str(gamma) + ' eV', file=log) parprint(' approximation:', self.approximation, file=log) parprint(' observation:', self.observation, '\n', file=log) parprint(' Mode Frequency Intensity', file=log) parprint(' # meV cm^-1 [e^4A^4/eV^2]', file=log) parprint('-------------------------------------', file=log) for n, e in enumerate(hnu): if e.imag != 0: c = 'i' e = e.imag else: c = ' ' e = e.real parprint('%3d %6.1f%s %7.1f%s %9.3g' % (n, 1000 * e, c, s * e, c, intensities[n]), file=log) parprint('-------------------------------------', file=log) parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy(), file=log) def __del__(self): self.timer.write(self.txt)
class ResonantRaman(Vibrations): """Class for calculating vibrational modes and resonant Raman intensities using finite difference. atoms: Atoms object Excitations: Class to calculate the excitations. The class object is initialized as:: Excitations(atoms.get_calculator()) or by reading form a file as:: Excitations('filename', **exkwargs) The file is written by calling the method Excitations.write('filename'). """ def __init__( self, atoms, Excitations, indices=None, gsname='rraman', # name for ground state calculations exname=None, # name for excited state calculations delta=0.01, nfree=2, directions=None, exkwargs={}, # kwargs to be passed to Excitations exext='.ex.gz', # extension for Excitation names txt='-', verbose=False): assert (nfree == 2) Vibrations.__init__(self, atoms, indices, gsname, delta, nfree) self.name = gsname + '-d%.3f' % delta if exname is None: exname = gsname self.exname = exname + '-d%.3f' % delta self.exext = exext if directions is None: self.directions = np.array([0, 1, 2]) else: self.directions = np.array(directions) self.exobj = Excitations self.exkwargs = exkwargs self.timer = Timer() self.txt = get_txt(txt, rank) self.verbose = verbose def log(self, message, pre='# ', end='\n'): if self.verbose: self.txt.write(pre + message + end) self.txt.flush() def calculate(self, filename, fd): """Call ground and excited state calculation""" self.timer.start('Ground state') forces = self.atoms.get_forces() if rank == 0: pickle.dump(forces, fd) fd.close() self.timer.stop('Ground state') self.timer.start('Excitations') basename, _ = os.path.splitext(filename) excitations = self.exobj(self.atoms.get_calculator()) excitations.write(basename + self.exext) self.timer.stop('Excitations') def get_intensity_tensor(self, omega, gamma=0.1): if not hasattr(self, 'modes'): self.read() if not hasattr(self, 'ex0'): eu = units.Hartree def get_me_tensor(exname, n, form='v'): def outer(ex): me = ex.get_dipole_me(form=form) return np.outer(me, me.conj()) self.log('reading ' + exname) ex_p = self.exobj(exname, **self.exkwargs) if len(ex_p) != n: raise RuntimeError( ('excitations {0} of wrong length: {1} != {2}' + ' exkwargs={3}').format(exname, len(ex_p), n, self.exkwargs)) m_ccp = np.empty((3, 3, len(ex_p)), dtype=complex) for p, ex in enumerate(ex_p): m_ccp[:, :, p] = outer(ex) return m_ccp self.timer.start('reading excitations') self.log('reading ' + self.exname + '.eq' + self.exext) ex_p = self.exobj(self.exname + '.eq' + self.exext, **self.exkwargs) n = len(ex_p) self.ex0 = np.array([ex.energy * eu for ex in ex_p]) self.exminus = [] self.explus = [] for a in self.indices: for i in 'xyz': name = '%s.%d%s' % (self.exname, a, i) self.exminus.append( get_me_tensor(name + '-' + self.exext, n)) self.explus.append( get_me_tensor(name + '+' + self.exext, n)) self.timer.stop('reading excitations') self.timer.start('amplitudes') self.timer.start('init') ndof = 3 * len(self.indices) amplitudes = np.zeros((ndof, 3, 3), dtype=complex) pre = 1. / (2 * self.delta) self.timer.stop('init') def kappa(me_ccp, e_p, omega, gamma, form='v'): """Kappa tensor after Profeta and Mauri PRB 63 (2001) 245415""" result = (me_ccp / (e_p - omega - 1j * gamma) + me_ccp.conj() / (e_p + omega + 1j * gamma)) return result.sum(2) r = 0 for a in self.indices: for i in 'xyz': amplitudes[r] = pre * ( kappa(self.explus[r], self.ex0, omega, gamma) - kappa(self.exminus[r], self.ex0, omega, gamma)) r += 1 self.timer.stop('amplitudes') # map to modes am = np.dot(amplitudes.T, self.modes.T).T return omega**4 * (am * am.conj()).real def get_intensities(self, omega, gamma=0.1): return self.get_intensity_tensor(omega, gamma).sum(axis=1).sum(axis=1) def get_spectrum(self, omega, gamma=0.1, start=200.0, end=4000.0, npts=None, width=4.0, type='Gaussian', method='standard', direction='central', intensity_unit='????', normalize=False): """Get resonant Raman spectrum. The method returns wavenumbers in cm^-1 with corresponding absolute infrared intensity. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1. normalize=True ensures the integral over the peaks to give the intensity. """ self.type = type.lower() assert self.type in ['gaussian', 'lorentzian'] if not npts: npts = int((end - start) / width * 10 + 1) frequencies = self.get_frequencies(method, direction).real intensities = self.get_intensities(omega, gamma) prefactor = 1 if type == 'lorentzian': intensities = intensities * width * np.pi / 2. if normalize: prefactor = 2. / width / np.pi else: sigma = width / 2. / np.sqrt(2. * np.log(2.)) if normalize: prefactor = 1. / sigma / np.sqrt(2 * np.pi) # Make array with spectrum data spectrum = np.empty(npts) energies = np.linspace(start, end, npts) for i, energy in enumerate(energies): energies[i] = energy if type == 'lorentzian': spectrum[i] = (intensities * 0.5 * width / np.pi / ( (frequencies - energy)**2 + 0.25 * width**2)).sum() else: spectrum[i] = ( intensities * np.exp(-(frequencies - energy)**2 / 2. / sigma**2)).sum() return [energies, prefactor * spectrum] def write_spectrum(self, omega, gamma, out='resonant-raman-spectra.dat', start=200, end=4000, npts=None, width=10, type='Gaussian', method='standard', direction='central'): """Write out spectrum to file. First column is the wavenumber in cm^-1, the second column the absolute infrared intensities, and the third column the absorbance scaled so that data runs from 1 to 0. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1.""" energies, spectrum = self.get_spectrum(omega, gamma, start, end, npts, width, type, method, direction) # Write out spectrum in file. First column is absolute intensities. outdata = np.empty([len(energies), 3]) outdata.T[0] = energies outdata.T[1] = spectrum fd = open(out, 'w') fd.write('# Resonant Raman spectrum\n') fd.write('# omega={0:g} eV, gamma={1:g} eV\n'.format(omega, gamma)) fd.write('# %s folded, width=%g cm^-1\n' % (type.title(), width)) fd.write('# [cm^-1] [a.u.]\n') for row in outdata: fd.write('%.3f %15.5g\n' % (row[0], row[1])) fd.close() def summary(self, omega, gamma=0.1, method='standard', direction='central', intensity_unit='(D/A)2/amu', log=sys.stdout): """Print summary for given omega [eV]""" hnu = self.get_energies(method, direction) s = 0.01 * units._e / units._c / units._hplanck intensities = self.get_intensities(omega, gamma) if isinstance(log, str): log = paropen(log, 'a') parprint('-------------------------------------', file=log) parprint(' excitation at ' + str(omega) + ' eV', file=log) parprint(' gamma ' + str(gamma) + ' eV\n', file=log) parprint(' Mode Frequency Intensity', file=log) parprint(' # meV cm^-1 [a.u.]', file=log) parprint('-------------------------------------', file=log) for n, e in enumerate(hnu): if e.imag != 0: c = 'i' e = e.imag else: c = ' ' e = e.real parprint('%3d %6.1f%s %7.1f%s %9.3g' % (n, 1000 * e, c, s * e, c, intensities[n]), file=log) parprint('-------------------------------------', file=log) parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy(), file=log) def __del__(self): self.timer.write(self.txt)
class LrTDDFT(ExcitationList): """Linear Response TDDFT excitation class Input parameters: calculator: the calculator object after a ground state calculation nspins: number of spins considered in the calculation Note: Valid only for unpolarised ground state calculation eps: Minimal occupation difference for a transition (default 0.001) istart: First occupied state to consider jend: Last unoccupied state to consider xc: Exchange-Correlation approximation in the Kernel derivative_level: 0: use Exc, 1: use vxc, 2: use fxc if available filename: read from a file """ default_parameters = { 'nspins': None, 'eps': 0.001, 'istart': 0, 'jend': sys.maxsize, 'energy_range': None, 'xc': 'GS', 'derivative_level': 1, 'numscale': 0.00001, 'txt': None, 'filename': None, 'finegrid': 2, 'force_ApmB': False, # for tests 'eh_comm': None, # parallelization over eh-pairs 'poisson': None } # use calculator's Poisson def __init__(self, calculator=None, **kwargs): self.timer = Timer() self.diagonalized = False changed = self.set(**kwargs) if isinstance(calculator, basestring): ExcitationList.__init__(self, None, self.txt) self.filename = calculator else: ExcitationList.__init__(self, calculator, self.txt) if self.filename is not None: self.read(self.filename) if set(['istart', 'jend', 'energy_range']) & set(changed): # the user has explicitely demanded these self.diagonalize() return if self.eh_comm is None: self.eh_comm = mpi.serial_comm elif isinstance(self.eh_comm, (mpi.world.__class__, mpi.serial_comm.__class__)): # Correct type already. pass else: # world should be a list of ranks: self.eh_comm = mpi.world.new_communicator(np.asarray(self.eh_comm)) if calculator is not None and calculator.initialized: # XXXX not ready for k-points assert (len(calculator.wfs.kd.ibzk_kc) == 1) if not isinstance(calculator.wfs, FDWaveFunctions): raise RuntimeError( 'Linear response TDDFT supported only in real space mode') if calculator.wfs.kd.comm.size > 1: err_txt = 'Spin parallelization with Linear response ' err_txt += "TDDFT. Use parallel={'domain': world.size} " err_txt += 'calculator parameter.' raise NotImplementedError(err_txt) if self.xc == 'GS': self.xc = calculator.hamiltonian.xc if calculator.parameters.mode != 'lcao': calculator.converge_wave_functions() if calculator.density.nct_G is None: spos_ac = calculator.initialize_positions() calculator.wfs.initialize(calculator.density, calculator.hamiltonian, spos_ac) self.update(calculator) def set(self, **kwargs): """Change parameters.""" changed = [] for key, value in LrTDDFT.default_parameters.items(): if hasattr(self, key): value = getattr(self, key) # do not overwrite setattr(self, key, kwargs.pop(key, value)) if value != getattr(self, key): changed.append(key) for key in kwargs: raise KeyError('Unknown key ' + key) return changed def set_calculator(self, calculator): self.calculator = calculator # self.force_ApmB = parameters['force_ApmB'] self.force_ApmB = None # XXX def analyse(self, what=None, out=None, min=0.1): """Print info about the transitions. Parameters: 1. what: I list of excitation indicees, None means all 2. out : I where to send the output, None means sys.stdout 3. min : I minimal contribution to list (0<min<1) """ if what is None: what = range(len(self)) elif isinstance(what, numbers.Integral): what = [what] if out is None: out = sys.stdout for i in what: print(str(i) + ':', self[i].analyse(min=min), file=out) def update(self, calculator=None, **kwargs): changed = self.set(**kwargs) if calculator is not None: changed = True self.set_calculator(calculator) if not changed: return self.forced_update() def forced_update(self): """Recalc yourself.""" if not self.force_ApmB: Om = OmegaMatrix name = 'LrTDDFT' if self.xc: if isinstance(self.xc, basestring): xc = XC(self.xc) else: xc = self.xc if hasattr(xc, 'hybrid') and xc.hybrid > 0.0: Om = ApmB name = 'LrTDDFThyb' else: Om = ApmB name = 'LrTDDFThyb' self.kss = KSSingles(calculator=self.calculator, nspins=self.nspins, eps=self.eps, istart=self.istart, jend=self.jend, energy_range=self.energy_range, txt=self.txt) self.Om = Om(self.calculator, self.kss, self.xc, self.derivative_level, self.numscale, finegrid=self.finegrid, eh_comm=self.eh_comm, poisson=self.poisson, txt=self.txt) self.name = name def diagonalize(self, **kwargs): self.set(**kwargs) self.timer.start('diagonalize') self.timer.start('omega') self.Om.diagonalize(self.istart, self.jend, self.energy_range) self.timer.stop('omega') self.diagonalized = True # remove old stuff self.timer.start('clean') while len(self): self.pop() self.timer.stop('clean') print('LrTDDFT digonalized:', file=self.txt) self.timer.start('build') for j in range(len(self.Om.kss)): self.append(LrTDDFTExcitation(self.Om, j)) print(' ', str(self[-1]), file=self.txt) self.timer.stop('build') self.timer.stop('diagonalize') def get_Om(self): return self.Om def read(self, filename=None, fh=None): """Read myself from a file""" timer = self.timer timer.start('name') if fh is None: if filename.endswith('.gz'): try: import gzip f = gzip.open(filename, 'rt') except: f = open(filename, 'r') else: f = open(filename, 'r') self.filename = filename else: f = fh self.filename = None timer.stop('name') timer.start('header') # get my name s = f.readline().strip() self.name = s.split()[1] self.xc = XC(f.readline().strip().split()[0]) values = f.readline().split() self.eps = float(values[0]) if len(values) > 1: self.derivative_level = int(values[1]) self.numscale = float(values[2]) self.finegrid = int(values[3]) else: # old writing style, use old defaults self.numscale = 0.001 timer.stop('header') timer.start('init_kss') self.kss = KSSingles(filehandle=f) timer.stop('init_kss') timer.start('init_obj') if self.name == 'LrTDDFT': self.Om = OmegaMatrix(kss=self.kss, filehandle=f, txt=self.txt) else: self.Om = ApmB(kss=self.kss, filehandle=f, txt=self.txt) self.Om.fullkss = self.kss timer.stop('init_obj') timer.start('read diagonalized') # check if already diagonalized p = f.tell() s = f.readline() if s != '# Eigenvalues\n': # go back to previous position f.seek(p) else: self.diagonalized = True # load the eigenvalues n = int(f.readline().split()[0]) for i in range(n): self.append(LrTDDFTExcitation(string=f.readline())) # load the eigenvectors timer.start('read eigenvectors') f.readline() for i in range(n): self[i].f = np.array([float(x) for x in f.readline().split()]) self[i].kss = self.kss timer.stop('read eigenvectors') timer.stop('read diagonalized') if fh is None: f.close() def singlets_triplets(self): """Split yourself into a singlet and triplet object""" slr = LrTDDFT(None, nspins=self.nspins, eps=self.eps, istart=self.istart, jend=self.jend, xc=self.xc, derivative_level=self.derivative_level, numscale=self.numscale) tlr = LrTDDFT(None, nspins=self.nspins, eps=self.eps, istart=self.istart, jend=self.jend, xc=self.xc, derivative_level=self.derivative_level, numscale=self.numscale) slr.Om, tlr.Om = self.Om.singlets_triplets() for lr in [slr, tlr]: lr.kss = lr.Om.fullkss return slr, tlr def single_pole_approximation(self, i, j): """Return the excitation according to the single pole approximation. See e.g.: Grabo et al, Theochem 501 (2000) 353-367 """ for ij, kss in enumerate(self.kss): if kss.i == i and kss.j == j: return sqrt(self.Om.full[ij][ij]) * Hartree return self.Om.full[ij][ij] / kss.energy * Hartree def __str__(self): string = ExcitationList.__str__(self) string += '# derived from:\n' string += self.Om.kss.__str__() return string def write(self, filename=None, fh=None): """Write current state to a file. 'filename' is the filename. If the filename ends in .gz, the file is automatically saved in compressed gzip format. 'fh' is a filehandle. This can be used to write into already opened files. """ if self.calculator is None: rank = mpi.world.rank else: rank = self.calculator.wfs.world.rank if rank == 0: if fh is None: if filename.endswith('.gz'): try: import gzip f = gzip.open(filename, 'wt') except: f = open(filename, 'w') else: f = open(filename, 'w') else: f = fh f.write('# ' + self.name + '\n') if isinstance(self.xc, basestring): xc = self.xc else: xc = self.xc.tostring() if xc is None: xc = 'RPA' if self.calculator is not None: xc += ' ' + self.calculator.get_xc_functional() f.write(xc + '\n') f.write('%g %d %g %d' % (self.eps, int(self.derivative_level), self.numscale, int(self.finegrid)) + '\n') self.kss.write(fh=f) self.Om.write(fh=f) if len(self): f.write('# Eigenvalues\n') istart = self.istart if istart is None: istart = self.kss.istart jend = self.jend if jend is None: jend = self.kss.jend f.write('%d %d %d' % (len(self), istart, jend) + '\n') for ex in self: f.write(ex.outstring()) f.write('# Eigenvectors\n') for ex in self: for w in ex.f: f.write('%g ' % w) f.write('\n') if fh is None: f.close() mpi.world.barrier() def overlap(self, ov_nn, other): """Matrix element overlap determined from pair density overlaps. Parameters ---------- ov_nn: array Wave function overlap factors from a displaced calculator. Index 0 corresponds to our own wavefunctions and index 1 to the others wavefunctions Returns ------- ov_pp: array Overlap """ #ov_pp = self.kss.overlap(ov_nn, other.kss) ov_pp = self.Om.kss.overlap(ov_nn, other.Om.kss) self.diagonalize() other.diagonalize() # ov[pLm, pLo] = Om[pLm, :pKm]* ov[:pKm, pLo] return np.dot( self.Om.eigenvectors.conj(), # ov[pKm, pLo] = ov[pKm, :pKo] Om[pLo, :pKo].T np.dot(ov_pp, other.Om.eigenvectors.T)) def __getitem__(self, i): if not self.diagonalized: self.diagonalize() return list.__getitem__(self, i) def __iter__(self): if not self.diagonalized: self.diagonalize() return list.__iter__(self) def __len__(self): if not self.diagonalized: self.diagonalize() return list.__len__(self) def __del__(self): self.timer.write(self.txt)
E = {} niter = {} for fg in fgl: if fg: tstr = 'Exx on fine grid' else: tstr = 'Exx on coarse grid' timer.start(tstr) calc = GPAW(h=0.3, eigensolver='rmm-diis', xc='PBE', nbands=4, convergence={'eigenstates': 1e-4}, charge=-1) loa.set_calculator(calc) E[fg] = loa.get_potential_energy() calc.set(xc=HybridXC('PBE0', finegrid=fg)) E[fg] = loa.get_potential_energy() niter[fg] = calc.get_number_of_iterations() timer.stop(tstr) timer.write(sys.stdout) print('Total energy on the fine grid =', E[True]) print('Total energy on the coarse grid =', E[False]) equal(E[True], E[False], 0.01) energy_tolerance = 0.0003 equal(E[False], 6.97818, energy_tolerance) equal(E[True], 6.97153, energy_tolerance)
class ResonantRaman(Vibrations): """Class for calculating vibrational modes and resonant Raman intensities using finite difference. atoms: Atoms object Excitations: Class to calculate the excitations. The class object is initialized as:: Excitations(atoms.get_calculator()) or by reading form a file as:: Excitations('filename', **exkwargs) The file is written by calling the method Excitations.write('filename'). Excitations should work like a list of ex obejects, where: ex.get_dipole_me(form='v'): gives the dipole matrix element in |e| * Angstrom ex.energy: is the transition energy in Hartrees """ def __init__(self, atoms, Excitations, indices=None, gsname='rraman', # name for ground state calculations exname=None, # name for excited state calculations delta=0.01, nfree=2, directions=None, approximation='Profeta', observation={'geometry': '-Z(XX)Z'}, exkwargs={}, # kwargs to be passed to Excitations exext='.ex.gz', # extension for Excitation names txt='-', verbose=False,): assert(nfree == 2) Vibrations.__init__(self, atoms, indices, gsname, delta, nfree) self.name = gsname + '-d%.3f' % delta if exname is None: exname = gsname self.exname = exname + '-d%.3f' % delta self.exext = exext if directions is None: self.directions = np.array([0, 1, 2]) else: self.directions = np.array(directions) self.approximation = approximation self.observation = observation self.exobj = Excitations self.exkwargs = exkwargs self.timer = Timer() self.txt = convert_string_to_fd(txt) self.verbose = verbose @staticmethod def m2(z): return (z * z.conj()).real def log(self, message, pre='# ', end='\n'): if self.verbose: self.txt.write(pre + message + end) self.txt.flush() def calculate(self, filename, fd): """Call ground and excited state calculation""" self.timer.start('Ground state') forces = self.atoms.get_forces() if rank == 0: pickle.dump(forces, fd, protocol=2) fd.close() self.timer.stop('Ground state') self.timer.start('Excitations') basename, _ = os.path.splitext(filename) excitations = self.exobj( self.atoms.get_calculator(), **self.exkwargs) excitations.write(basename + self.exext) self.timer.stop('Excitations') def read_excitations(self): self.timer.start('read excitations') self.timer.start('really read') self.log('reading ' + self.exname + '.eq' + self.exext) ex0_object = self.exobj(self.exname + '.eq' + self.exext, **self.exkwargs) self.timer.stop('really read') self.timer.start('index') matching = frozenset(ex0_object) self.timer.stop('index') def append(lst, exname, matching): self.timer.start('really read') self.log('reading ' + exname, end=' ') exo = self.exobj(exname, **self.exkwargs) lst.append(exo) self.timer.stop('really read') self.timer.start('index') matching = matching.intersection(exo) self.log('len={0}, matching={1}'.format(len(exo), len(matching)), pre='') self.timer.stop('index') return matching exm_object_list = [] exp_object_list = [] for a in self.indices: for i in 'xyz': name = '%s.%d%s' % (self.exname, a, i) matching = append(exm_object_list, name + '-' + self.exext, matching) matching = append(exp_object_list, name + '+' + self.exext, matching) self.ndof = 3 * len(self.indices) self.nex = len(matching) self.timer.stop('read excitations') self.timer.start('select') def select(exl, matching): mlst = [ex for ex in exl if ex in matching] assert(len(mlst) == len(matching)) return mlst ex0 = select(ex0_object, matching) exm = [] exp = [] r = 0 for a in self.indices: for i in 'xyz': exm.append(select(exm_object_list[r], matching)) exp.append(select(exp_object_list[r], matching)) r += 1 self.timer.stop('select') self.timer.start('me and energy') eu = units.Hartree self.ex0E_p = np.array([ex.energy * eu for ex in ex0]) self.ex0m_pc = np.array( [ex.get_dipole_me(form='v') for ex in ex0]) exmE_rp = [] expE_rp = [] exF_rp = [] exmm_rpc = [] expm_rpc = [] r = 0 for a in self.indices: for i in 'xyz': exmE_rp.append([em.energy for em in exm[r]]) expE_rp.append([ep.energy for ep in exp[r]]) exF_rp.append( [(ep.energy - em.energy) for ep, em in zip(exp[r], exm[r])]) exmm_rpc.append( [ex.get_dipole_me(form='v') for ex in exm[r]]) expm_rpc.append( [ex.get_dipole_me(form='v') for ex in exp[r]]) r += 1 self.exmE_rp = np.array(exmE_rp) * eu self.expE_rp = np.array(expE_rp) * eu self.exF_rp = np.array(exF_rp) * eu / 2 / self.delta self.exmm_rpc = np.array(exmm_rpc) self.expm_rpc = np.array(expm_rpc) self.timer.stop('me and energy') def read(self, method='standard', direction='central'): """Read data from a pre-performed calculation.""" if not hasattr(self, 'modes'): self.timer.start('read vibrations') Vibrations.read(self, method, direction) # we now have: # self.H : Hessian matrix # self.im : 1./sqrt(masses) # self.modes : Eigenmodes of the mass weighted H self.om_r = self.hnu.real # energies in eV self.timer.stop('read vibrations') if not hasattr(self, 'ex0E_p'): self.read_excitations() def get_Huang_Rhys_factors(self, forces_r): """Evaluate Huang-Rhys factors derived from forces.""" self.timer.start('Huang-Rhys') assert(len(forces_r.flat) == self.ndof) # solve the matrix equation for the equilibrium displacements # XXX why are the forces mass weighted ??? X_r = np.linalg.solve(self.im[:, None] * self.H * self.im, forces_r.flat * self.im) d_r = np.dot(self.modes, X_r) # Huang-Rhys factors S s = 1.e-20 / units.kg / units.C / units._hbar**2 # SI units self.timer.stop('Huang-Rhys') return s * d_r**2 * self.om_r / 2. def get_matrix_element_AlbrechtA(self, omega, gamma=0.1, ml=range(16)): """Evaluate Albrecht A term. Unit: |e|^2Angstrom^2/eV """ self.read() self.timer.start('AlbrechtA') if not hasattr(self, 'fco'): self.fco = FranckCondonOverlap() # excited state forces F_pr = self.exF_rp.T m_rcc = np.zeros((self.ndof, 3, 3), dtype=complex) for p, energy in enumerate(self.ex0E_p): S_r = self.get_Huang_Rhys_factors(F_pr[p]) me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj()) for m in ml: self.timer.start('0mm1') fco_r = self.fco.direct0mm1(m, S_r) self.timer.stop('0mm1') self.timer.start('einsum') m_rcc += np.einsum('a,bc->abc', fco_r / (energy + m * self.om_r - omega - 1j * gamma), me_cc) m_rcc += np.einsum('a,bc->abc', fco_r / (energy + (m - 1) * self.om_r + omega + 1j * gamma), me_cc) self.timer.stop('einsum') self.timer.stop('AlbrechtA') return m_rcc def get_matrix_element_AlbrechtBC(self, omega, gamma=0.1, ml=[1], term='BC'): """Evaluate Albrecht B and/or C term(s).""" self.read() self.timer.start('AlbrechtBC') if not hasattr(self, 'fco'): self.fco = FranckCondonOverlap() # excited state forces F_pr = self.exF_rp.T m_rcc = np.zeros((self.ndof, 3, 3), dtype=complex) for p, energy in enumerate(self.ex0E_p): S_r = self.get_Huang_Rhys_factors(F_pr[p]) for m in ml: self.timer.start('Franck-Condon overlaps') fc1mm1_r = self.fco.direct(1, m, S_r) fc0mm02_r = self.fco.direct(0, m, S_r) fc0mm02_r += np.sqrt(2) * self.fco.direct0mm2(m, S_r) # XXXXX fc1mm1_r[-1] = 1 fc0mm02_r[-1] = 1 print(m, fc1mm1_r[-1], fc0mm02_r[-1]) self.timer.stop('Franck-Condon overlaps') self.timer.start('me dervivatives') dm_rc = [] r = 0 for a in self.indices: for i in 'xyz': dm_rc.append( (self.expm_rpc[r, p] - self.exmm_rpc[r, p]) * self.im[r]) print('pm=', self.expm_rpc[r, p], self.exmm_rpc[r, p]) r += 1 dm_rc = np.array(dm_rc) / (2 * self.delta) self.timer.stop('me dervivatives') self.timer.start('map to modes') # print('dm_rc[2], dm_rc[5]', dm_rc[2], dm_rc[5]) print('dm_rc=', dm_rc) dm_rc = np.dot(dm_rc.T, self.modes.T).T print('dm_rc[-1][2]', dm_rc[-1][2]) self.timer.stop('map to modes') self.timer.start('multiply') # me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj()) for r in range(self.ndof): if 'B' in term: # XXXX denom = (1. / (energy + m * 0 * self.om_r[r] - omega - 1j * gamma)) # ok print('denom=', denom) m_rcc[r] += (np.outer(dm_rc[r], self.ex0m_pc[p].conj()) * fc1mm1_r[r] * denom) if r == 5: print('m_rcc[r]=', m_rcc[r][2, 2]) m_rcc[r] += (np.outer(self.ex0m_pc[p], dm_rc[r].conj()) * fc0mm02_r[r] * denom) if 'C' in term: denom = (1. / (energy + (m - 1) * self.om_r[r] + omega + 1j * gamma)) m_rcc[r] += (np.outer(self.ex0m_pc[p], dm_rc[r].conj()) * fc1mm1_r[r] * denom) m_rcc[r] += (np.outer(dm_rc[r], self.ex0m_pc[p].conj()) * fc0mm02_r[r] * denom) self.timer.stop('multiply') print('m_rcc[-1]=', m_rcc[-1][2, 2]) self.timer.start('pre_r') with np.errstate(divide='ignore'): pre_r = np.where(self.om_r > 0, np.sqrt(units._hbar**2 / 2. / self.om_r), 0) # print('BC: pre_r=', pre_r) for r, p in enumerate(pre_r): m_rcc[r] *= p self.timer.stop('pre_r') self.timer.stop('AlbrechtBC') return m_rcc def get_matrix_element_Profeta(self, omega, gamma=0.1, energy_derivative=False): """Evaluate Albrecht B+C term in Profeta and Mauri approximation""" self.read() self.timer.start('amplitudes') self.timer.start('init') V_rcc = np.zeros((self.ndof, 3, 3), dtype=complex) pre = 1. / (2 * self.delta) self.timer.stop('init') def kappa(me_pc, e_p, omega, gamma, form='v'): """Kappa tensor after Profeta and Mauri PRB 63 (2001) 245415""" me_ccp = np.empty((3, 3, len(e_p)), dtype=complex) for p, me_c in enumerate(me_pc): me_ccp[:, :, p] = np.outer(me_pc[p], me_pc[p].conj()) # print('kappa: me_ccp=', me_ccp[2,2,0]) # ok print('kappa: den=', 1./(e_p - omega - 1j * gamma)) kappa_ccp = (me_ccp / (e_p - omega - 1j * gamma) + me_ccp.conj() / (e_p + omega + 1j * gamma)) return kappa_ccp.sum(2) self.timer.start('kappa') r = 0 for a in self.indices: for i in 'xyz': if not energy_derivative < 0: V_rcc[r] = pre * self.im[r] * ( kappa(self.expm_rpc[r], self.ex0E_p, omega, gamma) - kappa(self.exmm_rpc[r], self.ex0E_p, omega, gamma)) if energy_derivative: V_rcc[r] += pre * self.im[r] * ( kappa(self.ex0m_pc, self.expE_rp[r], omega, gamma) - kappa(self.ex0m_pc, self.exmE_rp[r], omega, gamma)) r += 1 self.timer.stop('kappa') # print('V_rcc[2], V_rcc[5]=', V_rcc[2,2,2], V_rcc[5,2,2]) self.timer.stop('amplitudes') # map to modes self.timer.start('pre_r') with np.errstate(divide='ignore'): pre_r = np.where(self.om_r > 0, np.sqrt(units._hbar**2 / 2. / self.om_r), 0) V_rcc = np.dot(V_rcc.T, self.modes.T).T # looks ok print('self.modes.T[-1]',self.modes.T) # looks ok print('V_rcc[-1]=', V_rcc[-1][2,2]) # ok print('Profeta: pre_r=', pre_r) for r, p in enumerate(pre_r): V_rcc[r] *= p self.timer.stop('pre_r') return V_rcc def get_matrix_element(self, omega, gamma): self.read() V_rcc = np.zeros((self.ndof, 3, 3), dtype=complex) if self.approximation.lower() == 'profeta': V_rcc += self.get_matrix_element_Profeta(omega, gamma) elif self.approximation.lower() == 'placzek': V_rcc += self.get_matrix_element_Profeta(omega, gamma, True) elif self.approximation.lower() == 'p-p': V_rcc += self.get_matrix_element_Profeta(omega, gamma, -1) elif self.approximation.lower() == 'albrecht a': V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma) elif self.approximation.lower() == 'albrecht b': raise NotImplementedError('not working') V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma, term='B') elif self.approximation.lower() == 'albrecht c': raise NotImplementedError('not working') V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma, term='C') elif self.approximation.lower() == 'albrecht bc': raise NotImplementedError('not working') V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma) elif self.approximation.lower() == 'albrecht': raise NotImplementedError('not working') V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma) V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma) elif self.approximation.lower() == 'albrecht+profeta': V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma) V_rcc += self.get_matrix_element_Profeta(omega, gamma) else: raise NotImplementedError( 'Approximation {0} not implemented. '.format( self.approximation) + 'Please use "Profeta", "Albrecht A/B/C/BC", ' + 'or "Albrecht".') return V_rcc def get_intensities(self, omega, gamma=0.1): m2 = ResonantRaman.m2 alpha_rcc = self.get_matrix_element(omega, gamma) if not self.observation: # XXXX remove """Simple sum, maybe too simple""" return m2(alpha_rcc).sum(axis=1).sum(axis=1) # XXX enable when appropraiate # if self.observation['orientation'].lower() != 'random': # raise NotImplementedError('not yet') # random orientation of the molecular frame # Woodward & Long, # Guthmuller, J. J. Chem. Phys. 2016, 144 (6), 64106 m2 = ResonantRaman.m2 alpha2_r = m2(alpha_rcc[:, 0, 0] + alpha_rcc[:, 1, 1] + alpha_rcc[:, 2, 2]) / 9. delta2_r = 3 / 4. * ( m2(alpha_rcc[:, 0, 1] - alpha_rcc[:, 1, 0]) + m2(alpha_rcc[:, 0, 2] - alpha_rcc[:, 2, 0]) + m2(alpha_rcc[:, 1, 2] - alpha_rcc[:, 2, 1])) gamma2_r = (3 / 4. * (m2(alpha_rcc[:, 0, 1] + alpha_rcc[:, 1, 0]) + m2(alpha_rcc[:, 0, 2] + alpha_rcc[:, 2, 0]) + m2(alpha_rcc[:, 1, 2] + alpha_rcc[:, 2, 1])) + (m2(alpha_rcc[:, 0, 0] - alpha_rcc[:, 1, 1]) + m2(alpha_rcc[:, 0, 0] - alpha_rcc[:, 2, 2]) + m2(alpha_rcc[:, 1, 1] - alpha_rcc[:, 2, 2])) / 2) if self.observation['geometry'] == '-Z(XX)Z': # Porto's notation return (45 * alpha2_r + 5 * delta2_r + 4 * gamma2_r) / 45. elif self.observation['geometry'] == '-Z(XY)Z': # Porto's notation return gamma2_r / 15. elif self.observation['scattered'] == 'Z': # scattered light in direction of incoming light return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45. elif self.observation['scattered'] == 'parallel': # scattered light perendicular and # polarization in plane return 6 * gamma2_r / 45. elif self.observation['scattered'] == 'perpendicular': # scattered light perendicular and # polarization out of plane return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45. else: raise NotImplementedError def get_cross_sections(self, omega, gamma=0.1): I_r = self.get_intensities(omega, gamma) pre = 1. / 16 / np.pi**2 / units.eps0**2 / units.c**4 # frequency of scattered light omS_r = omega - self.hnu return pre * omega * omS_r**3 * I_r def get_spectrum(self, omega, gamma=0.1, start=200.0, end=4000.0, npts=None, width=4.0, type='Gaussian', method='standard', direction='central', intensity_unit='????', normalize=False): """Get resonant Raman spectrum. The method returns wavenumbers in cm^-1 with corresponding Raman cross section. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1. """ self.type = type.lower() assert self.type in ['gaussian', 'lorentzian'] if not npts: npts = int((end - start) / width * 10 + 1) frequencies = self.get_frequencies(method, direction).real intensities = self.get_cross_sections(omega, gamma) prefactor = 1 if type == 'lorentzian': intensities = intensities * width * np.pi / 2. if normalize: prefactor = 2. / width / np.pi else: sigma = width / 2. / np.sqrt(2. * np.log(2.)) if normalize: prefactor = 1. / sigma / np.sqrt(2 * np.pi) # Make array with spectrum data spectrum = np.empty(npts) energies = np.linspace(start, end, npts) for i, energy in enumerate(energies): energies[i] = energy if type == 'lorentzian': spectrum[i] = (intensities * 0.5 * width / np.pi / ((frequencies - energy)**2 + 0.25 * width**2)).sum() else: spectrum[i] = (intensities * np.exp(-(frequencies - energy)**2 / 2. / sigma**2)).sum() return [energies, prefactor * spectrum] def write_spectrum(self, omega, gamma, out='resonant-raman-spectra.dat', start=200, end=4000, npts=None, width=10, type='Gaussian', method='standard', direction='central'): """Write out spectrum to file. First column is the wavenumber in cm^-1, the second column the absolute infrared intensities, and the third column the absorbance scaled so that data runs from 1 to 0. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1.""" energies, spectrum = self.get_spectrum(omega, gamma, start, end, npts, width, type, method, direction) # Write out spectrum in file. First column is absolute intensities. outdata = np.empty([len(energies), 3]) outdata.T[0] = energies outdata.T[1] = spectrum fd = open(out, 'w') fd.write('# Resonant Raman spectrum\n') fd.write('# omega={0:g} eV, gamma={1:g} eV\n'.format(omega, gamma)) fd.write('# %s folded, width=%g cm^-1\n' % (type.title(), width)) fd.write('# [cm^-1] [a.u.]\n') for row in outdata: fd.write('%.3f %15.5g\n' % (row[0], row[1])) fd.close() def summary(self, omega, gamma=0.1, method='standard', direction='central', log=sys.stdout): """Print summary for given omega [eV]""" hnu = self.get_energies(method, direction) s = 0.01 * units._e / units._c / units._hplanck intensities = self.get_intensities(omega, gamma) if isinstance(log, basestring): log = paropen(log, 'a') parprint('-------------------------------------', file=log) parprint(' excitation at ' + str(omega) + ' eV', file=log) parprint(' gamma ' + str(gamma) + ' eV', file=log) parprint(' approximation:', self.approximation, file=log) parprint(' observation:', self.observation, '\n', file=log) parprint(' Mode Frequency Intensity', file=log) parprint(' # meV cm^-1 [e^4A^4/eV^2]', file=log) parprint('-------------------------------------', file=log) for n, e in enumerate(hnu): if e.imag != 0: c = 'i' e = e.imag else: c = ' ' e = e.real parprint('%3d %6.1f%s %7.1f%s %9.3g' % (n, 1000 * e, c, s * e, c, intensities[n]), file=log) parprint('-------------------------------------', file=log) parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy(), file=log) def __del__(self): self.timer.write(self.txt)
class ResonantRaman(Vibrations): """Class for calculating vibrational modes and resonant Raman intensities using finite difference. atoms: Atoms object Excitations: Class to calculate the excitations. The class object is initialized as:: Excitations(atoms.get_calculator()) or by reading form a file as:: Excitations('filename', **exkwargs) The file is written by calling the method Excitations.write('filename'). """ def __init__(self, atoms, Excitations, indices=None, gsname='rraman', # name for ground state calculations exname=None, # name for excited state calculations delta=0.01, nfree=2, directions=None, exkwargs={}, # kwargs to be passed to Excitations txt='-'): assert(nfree == 2) Vibrations.__init__(self, atoms, indices, gsname, delta, nfree) self.name = gsname + '-d%.3f' % delta if exname is None: exname = gsname self.exname = exname + '-d%.3f' % delta if directions is None: self.directions = np.array([0, 1, 2]) else: self.directions = np.array(directions) self.exobj = Excitations self.exkwargs = exkwargs self.timer = Timer() self.txt = get_txt(txt, rank) def calculate(self, filename, fd): """Call ground and excited state calculation""" self.timer.start('Ground state') forces = self.atoms.get_forces() if rank == 0: pickle.dump(forces, fd) fd.close() self.timer.stop('Ground state') self.timer.start('Excitations') basename, _ = os.path.splitext(filename) excitations = self.exobj(self.atoms.get_calculator()) excitations.write(basename + '.excitations') self.timer.stop('Excitations') def get_intensity_tensor(self, omega, gamma=0.1): if not hasattr(self, 'modes'): self.read() if not hasattr(self, 'ex0'): eu = units.Hartree def get_me_tensor(exname, n, form='v'): def outer(ex): me = ex.get_dipole_me(form=form) return np.outer(me, me.conj()) ex_p = self.exobj(exname, **self.exkwargs) if len(ex_p) != n: raise RuntimeError( ('excitations {0} of wrong length: {1} != {2}' + ' exkwargs={3}').format( exname, len(ex_p), n, self.exkwargs)) m_ccp = np.empty((3, 3, len(ex_p)), dtype=complex) for p, ex in enumerate(ex_p): m_ccp[:, :, p] = outer(ex) return m_ccp self.timer.start('reading excitations') ex_p = self.exobj(self.exname + '.eq.excitations', **self.exkwargs) n = len(ex_p) self.ex0 = np.array([ex.energy * eu for ex in ex_p]) self.exminus = [] self.explus = [] for a in self.indices: for i in 'xyz': name = '%s.%d%s' % (self.exname, a, i) self.exminus.append(get_me_tensor( name + '-.excitations', n)) self.explus.append(get_me_tensor( name + '+.excitations', n)) self.timer.stop('reading excitations') self.timer.start('amplitudes') self.timer.start('init') ndof = 3 * len(self.indices) amplitudes = np.zeros((ndof, 3, 3), dtype=complex) pre = 1. / (2 * self.delta) self.timer.stop('init') def kappa(me_ccp, e_p, omega, gamma, form='v'): """Kappa tensor after Profeta and Mauri PRB 63 (2001) 245415""" result = (me_ccp / (e_p - omega - 1j * gamma) + me_ccp.conj() / (e_p + omega + 1j * gamma)) return result.sum(2) r = 0 for a in self.indices: for i in 'xyz': amplitudes[r] = pre * ( kappa(self.explus[r], self.ex0, omega, gamma) - kappa(self.exminus[r], self.ex0, omega, gamma)) r += 1 self.timer.stop('amplitudes') # map to modes am = np.dot(amplitudes.T, self.modes.T).T return omega**4 * (am * am.conj()).real def get_intensities(self, omega, gamma=0.1): return self.get_intensity_tensor(omega, gamma).sum(axis=1).sum(axis=1) def get_spectrum(self, omega, gamma=0.1, start=200, end=4000, npts=None, width=4, type='Gaussian', method='standard', direction='central', intensity_unit='????', normalize=False): """Get resonant Raman spectrum. The method returns wavenumbers in cm^-1 with corresponding absolute infrared intensity. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1. normalize=True ensures the integral over the peaks to give the intensity. """ self.type = type.lower() assert self.type in ['gaussian', 'lorentzian'] if not npts: npts = (end - start) / width * 10 + 1 frequencies = self.get_frequencies(method, direction).real intensities = self.get_intensities(omega, gamma) prefactor = 1 if type == 'lorentzian': intensities = intensities * width * np.pi / 2. if normalize: prefactor = 2. / width / np.pi else: sigma = width / 2. / np.sqrt(2. * np.log(2.)) if normalize: prefactor = 1. / sigma / np.sqrt(2 * np.pi) #Make array with spectrum data spectrum = np.empty(npts, np.float) energies = np.empty(npts, np.float) ediff = (end - start) / float(npts - 1) energies = np.arange(start, end + ediff / 2, ediff) for i, energy in enumerate(energies): energies[i] = energy if type == 'lorentzian': spectrum[i] = (intensities * 0.5 * width / np.pi / ( (frequencies - energy)**2 + 0.25 * width**2)).sum() else: spectrum[i] = (intensities * np.exp(-(frequencies - energy)**2 / 2. / sigma**2)).sum() return [energies, prefactor * spectrum] def write_spectra(self, omega, gamma, out='resonant-raman-spectra.dat', start=200, end=4000, npts=None, width=10, type='Gaussian', method='standard', direction='central'): """Write out spectrum to file. First column is the wavenumber in cm^-1, the second column the absolute infrared intensities, and the third column the absorbance scaled so that data runs from 1 to 0. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1.""" energies, spectrum = self.get_spectrum(omega, gamma, start, end, npts, width, type, method, direction) #Write out spectrum in file. First column is absolute intensities. outdata = np.empty([len(energies), 3]) outdata.T[0] = energies outdata.T[1] = spectrum fd = open(out, 'w') fd.write('# Resonat Raman spectrum\n') fd.write('# omega={0:g} eV, gamma={1:g} eV\n'.format(omega, gamma)) fd.write('# %s folded, width=%g cm^-1\n' % (type.title(), width)) fd.write('# [cm^-1] [a.u.]\n') for row in outdata: fd.write('%.3f %15.5g\n' % (row[0], row[1])) fd.close() def summary(self, omega, gamma=0.1, method='standard', direction='central', intensity_unit='(D/A)2/amu', log=sys.stdout): """Print summary for given omega [eV]""" hnu = self.get_energies(method, direction) s = 0.01 * units._e / units._c / units._hplanck intensities = self.get_intensities(omega, gamma) if isinstance(log, str): log = paropen(log, 'a') parprint('-------------------------------------', file=log) parprint(' excitation at ' + str(omega) + ' eV', file=log) parprint(' gamma ' + str(gamma) + ' eV\n', file=log) parprint(' Mode Frequency Intensity', file=log) parprint(' # meV cm^-1 [a.u.]', file=log) parprint('-------------------------------------', file=log) for n, e in enumerate(hnu): if e.imag != 0: c = 'i' e = e.imag else: c = ' ' e = e.real parprint('%3d %6.1f%s %7.1f%s %9.3g' % (n, 1000 * e, c, s * e, c, intensities[n]), file=log) parprint('-------------------------------------', file=log) parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy(), file=log) def __del__(self): self.timer.write(self.txt)
class HybridXC(HybridXCBase): orbital_dependent = True def __init__(self, name, hybrid=None, xc=None, alpha=None, gamma_point=1, method='standard', bandstructure=False, logfilename='-', bands=None, fcut=1e-10, molecule=False, qstride=1, world=None): """Mix standard functionals with exact exchange. name: str Name of functional: EXX, PBE0, HSE03, HSE06 hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. method: str Use 'standard' standard formula and 'acdf for adiabatic-connection dissipation fluctuation formula. alpha: float XXX describe gamma_point: bool 0: Skip k2-k1=0 interactions. 1: Use the alpha method. 2: Integrate the gamma point. bandstructure: bool Calculate bandstructure instead of just the total energy. bands: list of int List of bands to calculate bandstructure for. Default is all bands. molecule: bool Decouple electrostatic interactions between periodically repeated images. fcut: float Threshold for empty band. """ self.alpha = alpha self.fcut = fcut self.gamma_point = gamma_point self.method = method self.bandstructure = bandstructure self.bands = bands self.fd = logfilename self.write_timing_information = True HybridXCBase.__init__(self, name, hybrid, xc) # EXX energies: self.exx = None # total self.evv = None # valence-valence (pseudo part) self.evvacdf = None # valence-valence (pseudo part) self.devv = None # valence-valence (PAW correction) self.evc = None # valence-core self.ecc = None # core-core self.exx_skn = None # bandstructure self.qlatest = None if world is None: world = mpi.world self.world = world self.molecule = molecule if isinstance(qstride, int): qstride = [qstride] * 3 self.qstride_c = np.asarray(qstride) self.timer = Timer() def log(self, *args, **kwargs): prnt(file=self.fd, *args, **kwargs) self.fd.flush() def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, dens, ham, wfs, occupations): assert wfs.bd.comm.size == 1 self.xc.initialize(dens, ham, wfs, occupations) self.dens = dens self.wfs = wfs # Make a k-point descriptor that is not distributed # (self.kd.comm is serial_comm): self.kd = wfs.kd.copy() self.fd = logfile(self.fd, self.world.rank) wfs.initialize_wave_functions_from_restart_file() def set_positions(self, spos_ac): self.spos_ac = spos_ac def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx * self.hybrid def calculate_exx(self): """Non-selfconsistent calculation.""" self.timer.start('EXX') self.timer.start('Initialization') kd = self.kd wfs = self.wfs if fftw.FFTPlan is fftw.NumpyFFTPlan: self.log('NOT USING FFTW !!') self.log('Spins:', self.wfs.nspins) W = max(1, self.wfs.kd.comm.size // self.wfs.nspins) # Are the k-points distributed? kparallel = (W > 1) # Find number of occupied bands: self.nocc_sk = np.zeros((self.wfs.nspins, kd.nibzkpts), int) for kpt in self.wfs.kpt_u: for n, f in enumerate(kpt.f_n): if abs(f) < self.fcut: self.nocc_sk[kpt.s, kpt.k] = n break else: self.nocc_sk[kpt.s, kpt.k] = self.wfs.bd.nbands self.wfs.kd.comm.sum(self.nocc_sk) noccmin = self.nocc_sk.min() noccmax = self.nocc_sk.max() self.log('Number of occupied bands (min, max): %d, %d' % (noccmin, noccmax)) self.log('Number of valence electrons:', self.wfs.setups.nvalence) if self.bandstructure: self.log('Calculating eigenvalue shifts.') # allocate array for eigenvalue shifts: self.exx_skn = np.zeros( (self.wfs.nspins, kd.nibzkpts, self.wfs.bd.nbands)) if self.bands is None: noccmax = self.wfs.bd.nbands else: noccmax = max(max(self.bands) + 1, noccmax) N_c = self.kd.N_c vol = wfs.gd.dv * wfs.gd.N_c.prod() if self.alpha is None: alpha = 6 * vol**(2 / 3.0) / pi**2 else: alpha = self.alpha if self.gamma_point == 1: if alpha == 0.0: qvol = (2 * np.pi)**3 / vol / N_c.prod() self.gamma = 4 * np.pi * (3 * qvol / (4 * np.pi))**(1 / 3.) / qvol else: self.gamma = self.calculate_gamma(vol, alpha) else: kcell_cv = wfs.gd.cell_cv.copy() kcell_cv[0] *= N_c[0] kcell_cv[1] *= N_c[1] kcell_cv[2] *= N_c[2] self.gamma = madelung(kcell_cv) * vol * N_c.prod() / (4 * np.pi) self.log('Value of alpha parameter: %.3f Bohr^2' % alpha) self.log('Value of gamma parameter: %.3f Bohr^2' % self.gamma) # Construct all possible q=k2-k1 vectors: Nq_c = (N_c - 1) // self.qstride_c i_qc = np.indices(Nq_c * 2 + 1, float).transpose((1, 2, 3, 0)).reshape( (-1, 3)) self.bzq_qc = (i_qc - Nq_c) / N_c * self.qstride_c self.q0 = ((Nq_c * 2 + 1).prod() - 1) // 2 # index of q=(0,0,0) assert not self.bzq_qc[self.q0].any() # Count number of pairs for each q-vector: self.npairs_q = np.zeros(len(self.bzq_qc), int) for s in range(kd.nspins): for k1 in range(kd.nibzkpts): for k2 in range(kd.nibzkpts): for K2, q, n1_n, n2 in self.indices(s, k1, k2): self.npairs_q[q] += len(n1_n) self.npairs0 = self.npairs_q.sum() # total number of pairs self.log('Number of pairs:', self.npairs0) # Distribute q-vectors to Q processors: Q = self.world.size // self.wfs.kd.comm.size myrank = self.world.rank // self.wfs.kd.comm.size rank = 0 N = 0 myq = [] nq = 0 for q, n in enumerate(self.npairs_q): if n > 0: nq += 1 if rank == myrank: myq.append(q) N += n if N >= (rank + 1.0) * self.npairs0 / Q: rank += 1 assert len(myq) > 0, 'Too few q-vectors for too many processes!' self.bzq_qc = self.bzq_qc[myq] try: self.q0 = myq.index(self.q0) except ValueError: self.q0 = None self.log('%d x %d x %d k-points' % tuple(self.kd.N_c)) self.log('Distributing %d IBZ k-points over %d process(es).' % (kd.nibzkpts, self.wfs.kd.comm.size)) self.log('Distributing %d q-vectors over %d process(es).' % (nq, Q)) # q-point descriptor for my q-vectors: qd = KPointDescriptor(self.bzq_qc) # Plane-wave descriptor for all wave-functions: self.pd = PWDescriptor(wfs.pd.ecut, wfs.gd, dtype=wfs.pd.dtype, kd=kd) # Plane-wave descriptor pair-densities: self.pd2 = PWDescriptor(self.dens.pd2.ecut, self.dens.gd, dtype=wfs.dtype, kd=qd) self.log('Cutoff energies:') self.log(' Wave functions: %10.3f eV' % (self.pd.ecut * Hartree)) self.log(' Density: %10.3f eV' % (self.pd2.ecut * Hartree)) # Calculate 1/|G+q|^2 with special treatment of |G+q|=0: G2_qG = self.pd2.G2_qG if self.q0 is None: if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] else: self.iG2_qG = [ (1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG ] else: G2_qG[self.q0][0] = 117.0 # avoid division by zero if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] self.iG2_qG[self.q0][0] = self.gamma else: self.iG2_qG = [ (1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG ] self.iG2_qG[self.q0][0] = 1 / (4 * self.omega**2) G2_qG[self.q0][0] = 0.0 # restore correct value # Compensation charges: self.ghat = PWLFC([setup.ghat_l for setup in wfs.setups], self.pd2) self.ghat.set_positions(self.spos_ac) if self.molecule: self.initialize_gaussian() self.log('Value of beta parameter: %.3f 1/Bohr^2' % self.beta) self.timer.stop('Initialization') # Ready ... set ... go: self.t0 = time() self.npairs = 0 self.evv = 0.0 self.evvacdf = 0.0 for s in range(self.wfs.nspins): kpt1_q = [ KPoint(self.wfs, noccmax).initialize(kpt) for kpt in self.wfs.kpt_u if kpt.s == s ] kpt2_q = kpt1_q[:] if len(kpt1_q) == 0: # No s-spins on this CPU: continue # Send and receive ranks: srank = self.wfs.kd.get_rank_and_index(s, (kpt1_q[0].k - 1) % kd.nibzkpts)[0] rrank = self.wfs.kd.get_rank_and_index(s, (kpt1_q[-1].k + 1) % kd.nibzkpts)[0] # Shift k-points kd.nibzkpts - 1 times: for i in range(kd.nibzkpts): if i < kd.nibzkpts - 1: if kparallel: kpt = kpt2_q[-1].next(self.wfs) kpt.start_receiving(rrank) kpt2_q[0].start_sending(srank) else: kpt = kpt2_q[0] self.timer.start('Calculate') for kpt1, kpt2 in zip(kpt1_q, kpt2_q): # Loop over all k-points that k2 can be mapped to: for K2, q, n1_n, n2 in self.indices(s, kpt1.k, kpt2.k): self.apply(K2, q, kpt1, kpt2, n1_n, n2) self.timer.stop('Calculate') if i < kd.nibzkpts - 1: self.timer.start('Wait') if kparallel: kpt.wait() kpt2_q[0].wait() self.timer.stop('Wait') kpt2_q.pop(0) kpt2_q.append(kpt) self.evv = self.world.sum(self.evv) self.evvacdf = self.world.sum(self.evvacdf) self.calculate_exx_paw_correction() if self.method == 'standard': self.exx = self.evv + self.devv + self.evc + self.ecc elif self.method == 'acdf': self.exx = self.evvacdf + self.devv + self.evc + self.ecc else: 1 / 0 self.log('Exact exchange energy:') for txt, e in [('core-core', self.ecc), ('valence-core', self.evc), ('valence-valence (pseudo, acdf)', self.evvacdf), ('valence-valence (pseudo, standard)', self.evv), ('valence-valence (correction)', self.devv), ('total (%s)' % self.method, self.exx)]: self.log(' %-36s %14.6f eV' % (txt + ':', e * Hartree)) self.log('Total time: %10.3f seconds' % (time() - self.t0)) self.npairs = self.world.sum(self.npairs) assert self.npairs == self.npairs0 self.timer.stop('EXX') self.timer.write(self.fd) def calculate_gamma(self, vol, alpha): if self.molecule: return 0.0 N_c = self.kd.N_c offset_c = (N_c + 1) % 2 * 0.5 / N_c bzq_qc = monkhorst_pack(N_c) + offset_c qd = KPointDescriptor(bzq_qc) pd = PWDescriptor(self.wfs.pd.ecut, self.wfs.gd, kd=qd) gamma = (vol / (2 * pi)**2 * sqrt(pi / alpha) * self.kd.nbzkpts) for G2_G in pd.G2_qG: if G2_G[0] < 1e-7: G2_G = G2_G[1:] gamma -= np.dot(np.exp(-alpha * G2_G), G2_G**-1) return gamma / self.qstride_c.prod() def indices(self, s, k1, k2): """Generator for (K2, q, n1, n2) indices for (k1, k2) pair. s: int Spin index. k1: int Index of k-point in the IBZ. k2: int Index of k-point in the IBZ. Returns (K, q, n1_n, n2), where K then index of the k-point in the BZ that k2 is mapped to, q is the index of the q-vector between K and k1, and n1_n is a list of bands that should be combined with band n2.""" for K, k in enumerate(self.kd.bz2ibz_k): if k == k2: for K, q, n1_n, n2 in self._indices(s, k1, k2, K): yield K, q, n1_n, n2 def _indices(self, s, k1, k2, K2): k1_c = self.kd.ibzk_kc[k1] k2_c = self.kd.bzk_kc[K2] q_c = k2_c - k1_c q = abs(self.bzq_qc - q_c).sum(1).argmin() if abs(self.bzq_qc[q] - q_c).sum() > 1e-7: return if self.gamma_point == 0 and q == self.q0: return nocc1 = self.nocc_sk[s, k1] nocc2 = self.nocc_sk[s, k2] # Is k2 in the IBZ? is_ibz2 = (self.kd.ibz2bz_k[k2] == K2) for n2 in range(self.wfs.bd.nbands): # Find range of n1's (from n1a to n1b-1): if is_ibz2: # We get this combination twice, so let's only do half: if k1 >= k2: n1a = n2 else: n1a = n2 + 1 else: n1a = 0 n1b = self.wfs.bd.nbands if self.bandstructure: if n2 >= nocc2: n1b = min(n1b, nocc1) else: if n2 >= nocc2: break n1b = min(n1b, nocc1) if self.bands is not None: assert self.bandstructure n1_n = [] for n1 in range(n1a, n1b): if (n1 in self.bands and n2 < nocc2 or is_ibz2 and n2 in self.bands and n1 < nocc1): n1_n.append(n1) n1_n = np.array(n1_n) else: n1_n = np.arange(n1a, n1b) if len(n1_n) == 0: continue yield K2, q, n1_n, n2 def apply(self, K2, q, kpt1, kpt2, n1_n, n2): k20_c = self.kd.ibzk_kc[kpt2.k] k2_c = self.kd.bzk_kc[K2] if k2_c.any(): self.timer.start('Initialize plane waves') eik2r_R = self.wfs.gd.plane_wave(k2_c) eik20r_R = self.wfs.gd.plane_wave(k20_c) self.timer.stop('Initialize plane waves') else: eik2r_R = 1.0 eik20r_R = 1.0 w1 = self.kd.weight_k[kpt1.k] w2 = self.kd.weight_k[kpt2.k] # Is k2 in the 1. BZ? is_ibz2 = (self.kd.ibz2bz_k[kpt2.k] == K2) e_n = self.calculate_interaction(n1_n, n2, kpt1, kpt2, q, K2, eik20r_R, eik2r_R, is_ibz2) e_n *= 1.0 / self.kd.nbzkpts / self.wfs.nspins * self.qstride_c.prod() if q == self.q0: e_n[n1_n == n2] *= 0.5 f1_n = kpt1.f_n[n1_n] eps1_n = kpt1.eps_n[n1_n] f2 = kpt2.f_n[n2] eps2 = kpt2.eps_n[n2] s_n = np.sign(eps2 - eps1_n) evv = (f1_n * f2 * e_n).sum() evvacdf = 0.5 * (f1_n * (1 - s_n) * e_n + f2 * (1 + s_n) * e_n).sum() self.evv += evv * w1 self.evvacdf += evvacdf * w1 if is_ibz2: self.evv += evv * w2 self.evvacdf += evvacdf * w2 if self.bandstructure: x = self.wfs.nspins self.exx_skn[kpt1.s, kpt1.k, n1_n] += x * f2 * e_n if is_ibz2: self.exx_skn[kpt2.s, kpt2.k, n2] += x * np.dot(f1_n, e_n) def calculate_interaction(self, n1_n, n2, kpt1, kpt2, q, k, eik20r_R, eik2r_R, is_ibz2): """Calculate Coulomb interactions. For all n1 in the n1_n list, calculate interaction with n2.""" # number of plane waves: ng1 = self.wfs.ng_k[kpt1.k] ng2 = self.wfs.ng_k[kpt2.k] # Transform to real space and apply symmetry operation: self.timer.start('IFFT1') if is_ibz2: u2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) else: psit2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) * eik20r_R self.timer.start('Symmetry transform') u2_R = self.kd.transform_wave_function(psit2_R, k) / eik2r_R self.timer.stop() self.timer.stop() # Calculate pair densities: nt_nG = self.pd2.zeros(len(n1_n), q=q) for n1, nt_G in zip(n1_n, nt_nG): self.timer.start('IFFT2') u1_R = self.pd.ifft(kpt1.psit_nG[n1, :ng1], kpt1.k) self.timer.stop() nt_R = u1_R.conj() * u2_R self.timer.start('FFT') nt_G[:] = self.pd2.fft(nt_R, q) self.timer.stop() s = self.kd.sym_k[k] time_reversal = self.kd.time_reversal_k[k] k2_c = self.kd.ibzk_kc[kpt2.k] self.timer.start('Compensation charges') Q_anL = {} # coefficients for shape functions for a, P1_ni in kpt1.P_ani.items(): P1_ni = P1_ni[n1_n] if is_ibz2: P2_i = kpt2.P_ani[a][n2] else: b = self.kd.symmetry.a_sa[s, a] S_c = (np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s]) - self.spos_ac[b]) assert abs(S_c.round() - S_c).max() < 1e-5 if self.ghat.dtype == complex: x = np.exp(2j * pi * np.dot(k2_c, S_c)) else: x = 1.0 P2_i = np.dot(self.wfs.setups[a].R_sii[s], kpt2.P_ani[b][n2]) * x if time_reversal: P2_i = P2_i.conj() D_np = [] for P1_i in P1_ni: D_ii = np.outer(P1_i.conj(), P2_i) D_np.append(pack(D_ii)) Q_anL[a] = np.dot(D_np, self.wfs.setups[a].Delta_pL) self.timer.start('Expand') if q != self.qlatest: self.f_IG = self.ghat.expand(q) self.qlatest = q self.timer.stop('Expand') # Add compensation charges: self.ghat.add(nt_nG, Q_anL, q, self.f_IG) self.timer.stop('Compensation charges') if self.molecule and n2 in n1_n: nn = (n1_n == n2).nonzero()[0][0] nt_nG[nn] -= self.ngauss_G else: nn = None iG2_G = self.iG2_qG[q] # Calculate energies: e_n = np.empty(len(n1_n)) for n, nt_G in enumerate(nt_nG): e_n[n] = -4 * pi * np.real(self.pd2.integrate(nt_G, nt_G * iG2_G)) self.npairs += 1 if nn is not None: e_n[nn] -= 2 * (self.pd2.integrate(nt_nG[nn], self.vgauss_G) + (self.beta / 2 / pi)**0.5) if self.write_timing_information: t = (time() - self.t0) / len(n1_n) self.log('Time for first pair-density: %10.3f seconds' % t) self.log('Estimated total time: %10.3f seconds' % (t * self.npairs0 / self.world.size)) self.write_timing_information = False return e_n def calculate_exx_paw_correction(self): self.timer.start('PAW correction') self.devv = 0.0 self.evc = 0.0 self.ecc = 0.0 deg = 2 // self.wfs.nspins # spin degeneracy for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.devv -= D_ii[i1, i2] * A / deg self.evc -= np.dot(D_p, setup.X_p) self.ecc += setup.ExxC if not self.bandstructure: self.timer.stop('PAW correction') return Q = self.world.size // self.wfs.kd.comm.size self.exx_skn *= Q for kpt in self.wfs.kpt_u: for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) P_ni = kpt.P_ani[a] for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.exx_skn[kpt.s, kpt.k] -= \ (A * P_ni[:, i1].conj() * P_ni[:, i2]).real p12 = packed_index(i1, i2, ni) self.exx_skn[kpt.s, kpt.k] -= \ (P_ni[:, i1].conj() * setup.X_p[p12] * P_ni[:, i2]).real / self.wfs.nspins self.world.sum(self.exx_skn) self.exx_skn *= self.hybrid / Q self.timer.stop('PAW correction') def initialize_gaussian(self): """Calculate gaussian compensation charge and its potential. Used to decouple electrostatic interactions between periodically repeated images for molecular calculations. Charge containing one electron:: (beta/pi)^(3/2)*exp(-beta*r^2), its Fourier transform:: exp(-G^2/(4*beta)), and its potential:: erf(beta^0.5*r)/r. """ gd = self.wfs.gd # Set exponent of exp-function to -19 on the boundary: self.beta = 4 * 19 * (gd.icell_cv**2).sum(1).max() # Calculate gaussian: G_Gv = self.pd2.get_reciprocal_vectors() G2_G = self.pd2.G2_qG[0] C_v = gd.cell_cv.sum(0) / 2 # center of cell self.ngauss_G = np.exp(-1.0 / (4 * self.beta) * G2_G + 1j * np.dot(G_Gv, C_v)) / gd.dv # Calculate potential from gaussian: R_Rv = gd.get_grid_point_coordinates().transpose((1, 2, 3, 0)) r_R = ((R_Rv - C_v)**2).sum(3)**0.5 if (gd.N_c % 2 == 0).all(): r_R[tuple(gd.N_c // 2)] = 1.0 # avoid dividing by zero v_R = erf(self.beta**0.5 * r_R) / r_R if (gd.N_c % 2 == 0).all(): v_R[tuple(gd.N_c // 2)] = (4 * self.beta / pi)**0.5 self.vgauss_G = self.pd2.fft(v_R) # Compare self-interaction to analytic result: assert abs(0.5 * self.pd2.integrate(self.ngauss_G, self.vgauss_G) - (self.beta / 2 / pi)**0.5) < 1e-6
class ResonantRaman(Vibrations): """Base Class for resonant Raman intensities using finite differences. Parameters ---------- overlap : function or False Function to calculate overlaps between excitation at equilibrium and at a displaced position. Calculators are given as first and second argument, respectively. """ def __init__(self, atoms, Excitations, indices=None, gsname='rraman', # name for ground state calculations exname=None, # name for excited state calculations delta=0.01, nfree=2, directions=None, observation={'geometry': '-Z(XX)Z'}, form='v', # form of the dipole operator exkwargs={}, # kwargs to be passed to Excitations exext='.ex.gz', # extension for Excitation names txt='-', verbose=False, overlap=False, minoverlap=0.02, minrep=0.8, comm=world, ): """ Parameters ---------- atoms: ase Atoms object Excitations: class Type of the excitation list object. The class object is initialized as:: Excitations(atoms.get_calculator()) or by reading form a file as:: Excitations('filename', **exkwargs) The file is written by calling the method Excitations.write('filename'). Excitations should work like a list of ex obejects, where: ex.get_dipole_me(form='v'): gives the velocity form dipole matrix element in units |e| * Angstrom ex.energy: is the transition energy in Hartrees indices: list gsname: string name for ground state calculations exname: string name for excited state calculations delta: float Finite difference displacement in Angstrom. nfree: float directions: approximation: string Level of approximation used. observation: dict Polarization settings form: string Form of the dipole operator, 'v' for velocity form (default) and 'r' for length form. exkwargs: dict Arguments given to the Excitations objects in reading. exext: string Extension for filenames of Excitation lists. txt: Output stream verbose: Verbosity level of output overlap: bool or function Use wavefunction overlaps. minoverlap: float ord dict Minimal absolute overlap to consider. Defaults to 0.02 to avoid numerical garbage. minrep: float Minimal represention to consider derivative, defaults to 0.8 """ assert(nfree == 2) Vibrations.__init__(self, atoms, indices, gsname, delta, nfree) self.name = gsname + '-d%.3f' % delta if exname is None: exname = gsname self.exname = exname + '-d%.3f' % delta self.exext = exext if directions is None: self.directions = np.array([0, 1, 2]) else: self.directions = np.array(directions) self.observation = observation self.exobj = Excitations self.exkwargs = exkwargs self.dipole_form = form self.timer = Timer() self.txt = convert_string_to_fd(txt) self.verbose = verbose self.overlap = overlap if not isinstance(minoverlap, dict): # assume it's a number self.minoverlap = {'orbitals': minoverlap, 'excitations': minoverlap} else: self.minoverlap = minoverlap self.minrep = minrep self.comm = comm @property def approximation(self): return self._approx @approximation.setter def approximation(self, value): self.set_approximation(value) @staticmethod def m2(z): return (z * z.conj()).real def log(self, message, pre='# ', end='\n'): if self.verbose: self.txt.write(pre + message + end) self.txt.flush() def run(self): if self.overlap: # XXXX stupid way to make a copy self.atoms.get_potential_energy() self.eq_calculator = self.atoms.get_calculator() fname = self.exname + '.eq.gpw' self.eq_calculator.write(fname, 'all') self.eq_calculator = self.eq_calculator.__class__(fname) self.eq_calculator.converge_wave_functions() Vibrations.run(self) def calculate(self, atoms, filename, fd): """Call ground and excited state calculation""" assert(atoms == self.atoms) # XXX action required self.timer.start('Ground state') forces = self.atoms.get_forces() if rank == 0: pickle.dump(forces, fd, protocol=2) fd.close() if self.overlap: self.timer.start('Overlap') """Overlap is determined as ov_ij = int dr displaced*_i(r) eqilibrium_j(r) """ ov_nn = self.overlap(self.atoms.get_calculator(), self.eq_calculator) if rank == 0: np.save(filename + '.ov', ov_nn) self.timer.stop('Overlap') self.timer.stop('Ground state') self.timer.start('Excitations') basename, _ = os.path.splitext(filename) excitations = self.exobj( self.atoms.get_calculator(), **self.exkwargs) excitations.write(basename + self.exext) self.timer.stop('Excitations') def init_parallel_read(self): """Initialize variables for parallel read""" rank = self.comm.rank self.ndof = 3 * len(self.indices) myn = -(-self.ndof // self.comm.size) # ceil divide self.slize = s = slice(myn * rank, myn * (rank + 1)) self.myindices = np.repeat(self.indices, 3)[s] self.myxyz = ('xyz' * len(self.indices))[s] self.myr = range(self.ndof)[s] self.mynd = len(self.myr) def read_excitations(self): """Read all finite difference excitations and select matching.""" self.timer.start('read excitations') self.timer.start('really read') self.log('reading ' + self.exname + '.eq' + self.exext) ex0_object = self.exobj(self.exname + '.eq' + self.exext, **self.exkwargs) self.timer.stop('really read') self.timer.start('index') matching = frozenset(ex0_object) self.timer.stop('index') def append(lst, exname, matching): self.timer.start('really read') self.log('reading ' + exname, end=' ') exo = self.exobj(exname, **self.exkwargs) lst.append(exo) self.timer.stop('really read') self.timer.start('index') matching = matching.intersection(exo) self.log('len={0}, matching={1}'.format(len(exo), len(matching)), pre='') self.timer.stop('index') return matching exm_object_list = [] exp_object_list = [] for a, i in zip(self.myindices, self.myxyz): name = '%s.%d%s' % (self.exname, a, i) matching = append(exm_object_list, name + '-' + self.exext, matching) matching = append(exp_object_list, name + '+' + self.exext, matching) self.ndof = 3 * len(self.indices) self.nex = len(matching) self.timer.stop('read excitations') self.timer.start('select') def select(exl, matching): mlst = [ex for ex in exl if ex in matching] assert(len(mlst) == len(matching)) return mlst ex0 = select(ex0_object, matching) exm = [] exp = [] r = 0 for a, i in zip(self.myindices, self.myxyz): exm.append(select(exm_object_list[r], matching)) exp.append(select(exp_object_list[r], matching)) r += 1 self.timer.stop('select') self.timer.start('me and energy') eu = u.Hartree self.ex0E_p = np.array([ex.energy * eu for ex in ex0]) self.ex0m_pc = (np.array( [ex.get_dipole_me(form=self.dipole_form) for ex in ex0]) * u.Bohr) exmE_rp = [] expE_rp = [] exF_rp = [] exmm_rpc = [] expm_rpc = [] r = 0 for a, i in zip(self.myindices, self.myxyz): exmE_rp.append([em.energy for em in exm[r]]) expE_rp.append([ep.energy for ep in exp[r]]) exF_rp.append( [(em.energy - ep.energy) for ep, em in zip(exp[r], exm[r])]) exmm_rpc.append( [ex.get_dipole_me(form=self.dipole_form) for ex in exm[r]]) expm_rpc.append( [ex.get_dipole_me(form=self.dipole_form) for ex in exp[r]]) r += 1 # indicees: r=coordinate, p=excitation # energies in eV self.exmE_rp = np.array(exmE_rp) * eu self.expE_rp = np.array(expE_rp) * eu # forces in eV / Angstrom self.exF_rp = np.array(exF_rp) * eu / 2 / self.delta # matrix elements in e * Angstrom self.exmm_rpc = np.array(exmm_rpc) * u.Bohr self.expm_rpc = np.array(expm_rpc) * u.Bohr self.timer.stop('me and energy') def read_excitations_overlap(self): """Read all finite difference excitations and wf overlaps. We assume that the wave function overlaps are determined as ov_ij = int dr displaced*_i(r) eqilibrium_j(r) """ self.timer.start('read excitations') self.timer.start('read+rotate') self.log('reading ' + self.exname + '.eq' + self.exext) ex0 = self.exobj(self.exname + '.eq' + self.exext, **self.exkwargs) rep0_p = np.ones((len(ex0)), dtype=float) def load(name, pm, rep0_p): self.log('reading ' + name + pm + self.exext) ex_p = self.exobj(name + pm + self.exext, **self.exkwargs) self.log('reading ' + name + pm + '.pckl.ov.npy') ov_nn = np.load(name + pm + '.pckl.ov.npy') # remove numerical garbage ov_nn = np.where(np.abs(ov_nn) > self.minoverlap['orbitals'], ov_nn, 0) self.timer.start('ex overlap') ov_pp = ex_p.overlap(ov_nn, ex0) # remove numerical garbage ov_pp = np.where(np.abs(ov_pp) > self.minoverlap['excitations'], ov_pp, 0) rep0_p *= (ov_pp.real**2 + ov_pp.imag**2).sum(axis=0) self.timer.stop('ex overlap') return ex_p, ov_pp def rotate(ex_p, ov_pp): e_p = np.array([ex.energy for ex in ex_p]) m_pc = np.array( [ex.get_dipole_me(form=self.dipole_form) for ex in ex_p]) r_pp = ov_pp.T return ((r_pp.real**2 + r_pp.imag**2).dot(e_p), r_pp.dot(m_pc)) exmE_rp = [] expE_rp = [] exF_rp = [] exmm_rpc = [] expm_rpc = [] exdmdr_rpc = [] for a, i in zip(self.myindices, self.myxyz): name = '%s.%d%s' % (self.exname, a, i) ex, ov = load(name, '-', rep0_p) exmE_p, exmm_pc = rotate(ex, ov) ex, ov = load(name, '+', rep0_p) expE_p, expm_pc = rotate(ex, ov) exmE_rp.append(exmE_p) expE_rp.append(expE_p) exF_rp.append(exmE_p - expE_p) exmm_rpc.append(exmm_pc) expm_rpc.append(expm_pc) exdmdr_rpc.append(expm_pc - exmm_pc) self.timer.stop('read+rotate') self.timer.start('me and energy') # select only excitations that are sufficiently represented self.comm.product(rep0_p) select = np.where(rep0_p > self.minrep)[0] eu = u.Hartree self.ex0E_p = np.array([ex.energy * eu for ex in ex0])[select] self.ex0m_pc = (np.array( [ex.get_dipole_me(form=self.dipole_form) for ex in ex0])[select] * u.Bohr) if len(self.myr): # indicees: r=coordinate, p=excitation # energies in eV self.exmE_rp = np.array(exmE_rp)[:,select] * eu ##print(len(select), np.array(exmE_rp).shape, self.exmE_rp.shape) self.expE_rp = np.array(expE_rp)[:,select] * eu # forces in eV / Angstrom self.exF_rp = np.array(exF_rp)[:,select] * eu / 2 / self.delta # matrix elements in e * Angstrom self.exmm_rpc = np.array(exmm_rpc)[:,select,:] * u.Bohr self.expm_rpc = np.array(expm_rpc)[:,select,:] * u.Bohr # matrix element derivatives in e self.exdmdr_rpc = (np.array(exdmdr_rpc)[:,select,:] * u.Bohr / 2 / self.delta) else: # did not read self.exmE_rp = self.expE_rp = self.exF_rp = np.empty((0)) self.exmm_rpc = self.expm_rpc = self.exdmdr_rpc = np.empty((0)) self.timer.stop('me and energy') self.timer.stop('read excitations') def read(self, method='standard', direction='central'): """Read data from a pre-performed calculation.""" self.timer.start('read') self.timer.start('vibrations') Vibrations.read(self, method, direction) # we now have: # self.H : Hessian matrix # self.im : 1./sqrt(masses) # self.modes : Eigenmodes of the mass weighted Hessian self.om_Q = self.hnu.real # energies in eV self.om_v = self.om_Q # pre-factors for one vibrational excitation with np.errstate(divide='ignore'): self.vib01_Q = np.where(self.om_Q > 0, 1. / np.sqrt(2 * self.om_Q), 0) # -> sqrt(amu) * Angstrom self.vib01_Q *= np.sqrt(u.Ha * u._me / u._amu) * u.Bohr self.timer.stop('vibrations') self.timer.start('excitations') self.init_parallel_read() if not hasattr(self, 'ex0E_p'): if self.overlap: self.read_excitations_overlap() else: self.read_excitations() self.timer.stop('excitations') self.timer.stop('read') def me_Qcc(self, omega, gamma): """Full matrix element Returns ------- Matrix element in e^2 Angstrom^2 / eV """ # Angstrom^2 / sqrt(amu) elme_Qcc = self.electronic_me_Qcc(omega, gamma) # Angstrom^3 -> e^2 Angstrom^2 / eV elme_Qcc /= u.Hartree * u.Bohr # e^2 Angstrom / eV / sqrt(amu) return elme_Qcc * self.vib01_Q[:, None, None] def intensity(self, omega, gamma=0.1): """Raman intensity Returns ------- unit e^4 Angstrom^4 / eV^2 """ m2 = ResonantRaman.m2 alpha_Qcc = self.me_Qcc(omega, gamma) if not self.observation: # XXXX remove """Simple sum, maybe too simple""" return m2(alpha_Qcc).sum(axis=1).sum(axis=1) # XXX enable when appropriate # if self.observation['orientation'].lower() != 'random': # raise NotImplementedError('not yet') # random orientation of the molecular frame # Woodward & Long, # Guthmuller, J. J. Chem. Phys. 2016, 144 (6), 64106 alpha2_r, gamma2_r, delta2_r = self._invariants(alpha_Qcc) if self.observation['geometry'] == '-Z(XX)Z': # Porto's notation return (45 * alpha2_r + 5 * delta2_r + 4 * gamma2_r) / 45. elif self.observation['geometry'] == '-Z(XY)Z': # Porto's notation return gamma2_r / 15. elif self.observation['scattered'] == 'Z': # scattered light in direction of incoming light return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45. elif self.observation['scattered'] == 'parallel': # scattered light perendicular and # polarization in plane return 6 * gamma2_r / 45. elif self.observation['scattered'] == 'perpendicular': # scattered light perendicular and # polarization out of plane return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45. else: raise NotImplementedError def _invariants(self, alpha_Qcc): """Raman invariants Parameter --------- alpha_Qcc: array Matrix element or polarizability tensor Reference --------- Derek A. Long, The Raman Effect, ISBN 0-471-49028-8 Returns ------- mean polarizability, anisotropy, asymmetric anisotropy """ m2 = ResonantRaman.m2 alpha2_r = m2(alpha_Qcc[:, 0, 0] + alpha_Qcc[:, 1, 1] + alpha_Qcc[:, 2, 2]) / 9. delta2_r = 3 / 4. * ( m2(alpha_Qcc[:, 0, 1] - alpha_Qcc[:, 1, 0]) + m2(alpha_Qcc[:, 0, 2] - alpha_Qcc[:, 2, 0]) + m2(alpha_Qcc[:, 1, 2] - alpha_Qcc[:, 2, 1])) gamma2_r = (3 / 4. * (m2(alpha_Qcc[:, 0, 1] + alpha_Qcc[:, 1, 0]) + m2(alpha_Qcc[:, 0, 2] + alpha_Qcc[:, 2, 0]) + m2(alpha_Qcc[:, 1, 2] + alpha_Qcc[:, 2, 1])) + (m2(alpha_Qcc[:, 0, 0] - alpha_Qcc[:, 1, 1]) + m2(alpha_Qcc[:, 0, 0] - alpha_Qcc[:, 2, 2]) + m2(alpha_Qcc[:, 1, 1] - alpha_Qcc[:, 2, 2])) / 2) return alpha2_r, gamma2_r, delta2_r def absolute_intensity(self, omega, gamma=0.1, delta=0): """Absolute Raman intensity or Raman scattering factor Parameter --------- omega: float incoming laser energy, unit eV gamma: float width (imaginary energy), unit eV delta: float pre-factor for asymmetric anisotropy, default 0 References ---------- Porezag and Pederson, PRB 54 (1996) 7830-7836 (delta=0) Baiardi and Barone, JCTC 11 (2015) 3267-3280 (delta=5) Returns ------- raman intensity, unit Ang**4/amu """ alpha2_r, gamma2_r, delta2_r = self._invariants( self.electronic_me_Qcc(omega, gamma)) return 45 * alpha2_r + delta * delta2_r + 7 * gamma2_r def get_cross_sections(self, omega, gamma=0.1): """Returns Raman cross sections for each vibration.""" I_v = self.intensity(omega, gamma) pre = 1. / 16 / np.pi**2 / u._eps0**2 / u._c**4 # frequency of scattered light omS_v = omega - self.om_v return pre * omega * omS_v**3 * I_v def get_spectrum(self, omega, gamma=0.1, start=None, end=None, npts=None, width=20, type='Gaussian', method='standard', direction='central', intensity_unit='????', normalize=False): """Get resonant Raman spectrum. The method returns wavenumbers in cm^-1 with corresponding Raman cross section. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1. """ self.type = type.lower() assert self.type in ['gaussian', 'lorentzian'] frequencies = self.get_frequencies(method, direction).real intensities = self.get_cross_sections(omega, gamma) if width is None: return [frequencies, intensities] if start is None: start = min(self.om_v) / u.invcm - 3 * width if end is None: end = max(self.om_v) / u.invcm + 3 * width if not npts: npts = int((end - start) / width * 10 + 1) prefactor = 1 if self.type == 'lorentzian': intensities = intensities * width * np.pi / 2. if normalize: prefactor = 2. / width / np.pi else: sigma = width / 2. / np.sqrt(2. * np.log(2.)) if normalize: prefactor = 1. / sigma / np.sqrt(2 * np.pi) # Make array with spectrum data spectrum = np.empty(npts) energies = np.linspace(start, end, npts) for i, energy in enumerate(energies): energies[i] = energy if self.type == 'lorentzian': spectrum[i] = (intensities * 0.5 * width / np.pi / ((frequencies - energy)**2 + 0.25 * width**2)).sum() else: spectrum[i] = (intensities * np.exp(-(frequencies - energy)**2 / 2. / sigma**2)).sum() return [energies, prefactor * spectrum] def write_spectrum(self, omega, gamma, out='resonant-raman-spectra.dat', start=200, end=4000, npts=None, width=10, type='Gaussian', method='standard', direction='central'): """Write out spectrum to file. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1.""" energies, spectrum = self.get_spectrum(omega, gamma, start, end, npts, width, type, method, direction) # Write out spectrum in file. First column is absolute intensities. outdata = np.empty([len(energies), 3]) outdata.T[0] = energies outdata.T[1] = spectrum fd = paropen(out, 'w') fd.write('# Resonant Raman spectrum\n') if hasattr(self, '_approx'): fd.write('# approximation: {0}\n'.format(self._approx)) for key in self.observation: fd.write('# {0}: {1}\n'.format(key, self.observation[key])) fd.write('# omega={0:g} eV, gamma={1:g} eV\n'.format(omega, gamma)) if width is not None: fd.write('# %s folded, width=%g cm^-1\n' % (type.title(), width)) fd.write('# [cm^-1] [a.u.]\n') for row in outdata: fd.write('%.3f %15.5g\n' % (row[0], row[1])) fd.close() def summary(self, omega=0, gamma=0, method='standard', direction='central', log=sys.stdout): """Print summary for given omega [eV]""" hnu = self.get_energies(method, direction) intensities = self.absolute_intensity(omega, gamma) te = int(np.log10(intensities.max())) - 2 scale = 10**(-te) if not te: ts = '' elif te > -2 and te < 3: ts = str(10**te) else: ts = '10^{0}'.format(te) if isinstance(log, basestring): log = paropen(log, 'a') parprint('-------------------------------------', file=log) parprint(' excitation at ' + str(omega) + ' eV', file=log) parprint(' gamma ' + str(gamma) + ' eV', file=log) parprint(' method:', self.method, file=log) parprint(' approximation:', self.approximation, file=log) parprint(' Mode Frequency Intensity', file=log) parprint(' # meV cm^-1 [{0}A^4/amu]'.format(ts), file=log) parprint('-------------------------------------', file=log) for n, e in enumerate(hnu): if e.imag != 0: c = 'i' e = e.imag else: c = ' ' e = e.real parprint('%3d %6.1f%s %7.1f%s %9.2f' % (n, 1000 * e, c, e / u.invcm, c, intensities[n] * scale), file=log) parprint('-------------------------------------', file=log) parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy(), file=log) def __del__(self): self.timer.write(self.txt)
class GPAW(Calculator, PAW): """This is the ASE-calculator frontend for doing a PAW calculation.""" implemented_properties = [ 'energy', 'forces', 'stress', 'dipole', 'magmom', 'magmoms' ] default_parameters = { 'mode': 'fd', 'xc': 'LDA', 'occupations': None, 'poissonsolver': None, 'h': None, # Angstrom 'gpts': None, 'kpts': [(0.0, 0.0, 0.0)], 'nbands': None, 'charge': 0, 'setups': {}, 'basis': {}, 'spinpol': None, 'fixdensity': False, 'filter': None, 'mixer': None, 'eigensolver': None, 'background_charge': None, 'external': None, 'random': False, 'hund': False, 'maxiter': 333, 'idiotproof': True, 'symmetry': { 'point_group': True, 'time_reversal': True, 'symmorphic': True, 'tolerance': 1e-7 }, 'convergence': { 'energy': 0.0005, # eV / electron 'density': 1.0e-4, 'eigenstates': 4.0e-8, # eV^2 'bands': 'occupied', 'forces': np.inf }, # eV / Ang 'dtype': None, # Deprecated 'width': None, # Deprecated 'verbose': 0 } default_parallel = { 'kpt': None, 'domain': gpaw.parsize_domain, 'band': gpaw.parsize_bands, 'order': 'kdb', 'stridebands': False, 'augment_grids': gpaw.augment_grids, 'sl_auto': False, 'sl_default': gpaw.sl_default, 'sl_diagonalize': gpaw.sl_diagonalize, 'sl_inverse_cholesky': gpaw.sl_inverse_cholesky, 'sl_lcao': gpaw.sl_lcao, 'sl_lrtddft': gpaw.sl_lrtddft, 'buffer_size': gpaw.buffer_size } def __init__(self, restart=None, ignore_bad_restart_file=False, label=None, atoms=None, timer=None, communicator=None, txt='-', parallel=None, **kwargs): self.parallel = dict(self.default_parallel) if parallel: self.parallel.update(parallel) if timer is None: self.timer = Timer() else: self.timer = timer self.scf = None self.wfs = None self.occupations = None self.density = None self.hamiltonian = None self.observers = [] # XXX move to self.scf self.initialized = False self.world = communicator if self.world is None: self.world = mpi.world elif not hasattr(self.world, 'new_communicator'): self.world = mpi.world.new_communicator(np.asarray(self.world)) self.log = GPAWLogger(world=self.world) self.log.fd = txt self.reader = None Calculator.__init__(self, restart, ignore_bad_restart_file, label, atoms, **kwargs) def __del__(self): self.timer.write(self.log.fd) if self.reader is not None: self.reader.close() def write(self, filename, mode=''): self.log('Writing to {0} (mode={1!r})\n'.format(filename, mode)) writer = Writer(filename, self.world) self._write(writer, mode) writer.close() self.world.barrier() def _write(self, writer, mode): from ase.io.trajectory import write_atoms writer.write(version=1, gpaw_version=gpaw.__version__, ha=Ha, bohr=Bohr) write_atoms(writer.child('atoms'), self.atoms) writer.child('results').write(**self.results) writer.child('parameters').write(**self.todict()) self.density.write(writer.child('density')) self.hamiltonian.write(writer.child('hamiltonian')) self.occupations.write(writer.child('occupations')) self.scf.write(writer.child('scf')) self.wfs.write(writer.child('wave_functions'), mode == 'all') return writer def read(self, filename): from ase.io.trajectory import read_atoms self.log('Reading from {0}'.format(filename)) self.reader = reader = Reader(filename) self.atoms = read_atoms(reader.atoms) res = reader.results self.results = dict((key, res.get(key)) for key in res.keys()) if self.results: self.log('Read {0}'.format(', '.join(sorted(self.results)))) self.log('Reading input parameters:') self.parameters = self.get_default_parameters() dct = {} for key, value in reader.parameters.asdict().items(): if (isinstance(value, dict) and isinstance(self.parameters[key], dict)): self.parameters[key].update(value) else: self.parameters[key] = value dct[key] = self.parameters[key] self.log.print_dict(dct) self.log() self.initialize(reading=True) self.density.read(reader) self.hamiltonian.read(reader) self.occupations.read(reader) self.scf.read(reader) self.wfs.read(reader) # We need to do this in a better way: XXX from gpaw.utilities.partition import AtomPartition atom_partition = AtomPartition(self.wfs.gd.comm, np.zeros(len(self.atoms), dtype=int)) self.wfs.atom_partition = atom_partition self.density.atom_partition = atom_partition self.hamiltonian.atom_partition = atom_partition spos_ac = self.atoms.get_scaled_positions() % 1.0 rank_a = self.density.gd.get_ranks_from_positions(spos_ac) new_atom_partition = AtomPartition(self.density.gd.comm, rank_a) for obj in [self.density, self.hamiltonian]: obj.set_positions_without_ruining_everything( spos_ac, new_atom_partition) self.hamiltonian.xc.read(reader) if self.hamiltonian.xc.name == 'GLLBSC': # XXX GLLB: See lcao/tdgllbsc.py test self.occupations.calculate(self.wfs) return reader def check_state(self, atoms, tol=1e-15): system_changes = Calculator.check_state(self, atoms, tol) if 'positions' not in system_changes: if self.hamiltonian: if self.hamiltonian.vext: if self.hamiltonian.vext.vext_g is None: # QMMM atoms have moved: system_changes.append('positions') return system_changes def calculate(self, atoms=None, properties=['energy'], system_changes=['cell']): """Calculate things.""" Calculator.calculate(self, atoms) atoms = self.atoms if system_changes: self.log('System changes:', ', '.join(system_changes), '\n') if system_changes == ['positions']: # Only positions have changed: self.density.reset() else: # Drastic changes: self.wfs = None self.occupations = None self.density = None self.hamiltonian = None self.scf = None self.initialize(atoms) self.set_positions(atoms) if not self.initialized: self.initialize(atoms) self.set_positions(atoms) if not (self.wfs.positions_set and self.hamiltonian.positions_set): self.set_positions(atoms) if not self.scf.converged: print_cell(self.wfs.gd, self.atoms.pbc, self.log) with self.timer('SCF-cycle'): self.scf.run(self.wfs, self.hamiltonian, self.density, self.occupations, self.log, self.call_observers) self.log('\nConverged after {0} iterations.\n'.format( self.scf.niter)) e_free = self.hamiltonian.e_total_free e_extrapolated = self.hamiltonian.e_total_extrapolated self.results['energy'] = e_extrapolated * Ha self.results['free_energy'] = e_free * Ha if not self.atoms.pbc.all(): dipole_v = self.density.calculate_dipole_moment() * Bohr self.log( 'Dipole moment: ({0:.6f}, {1:.6f}, {2:.6f}) |e|*Ang\n'. format(*dipole_v)) self.results['dipole'] = dipole_v if self.wfs.nspins == 2: magmom = self.occupations.magmom magmom_a = self.density.estimate_magnetic_moments(total=magmom) self.log('Total magnetic moment: %f' % magmom) self.log('Local magnetic moments:') symbols = self.atoms.get_chemical_symbols() for a, mom in enumerate(magmom_a): self.log('{0:4} {1:2} {2:.6f}'.format(a, symbols[a], mom)) self.log() self.results['magmom'] = self.occupations.magmom self.results['magmoms'] = magmom_a self.summary() self.call_observers(self.scf.niter, final=True) if 'forces' in properties: with self.timer('Forces'): F_av = calculate_forces(self.wfs, self.density, self.hamiltonian, self.log) self.results['forces'] = F_av * (Ha / Bohr) if 'stress' in properties: with self.timer('Stress'): try: stress = calculate_stress(self).flat[[0, 4, 8, 5, 2, 1]] except NotImplementedError: # Our ASE Calculator base class will raise # PropertyNotImplementedError for us. pass else: self.results['stress'] = stress * (Ha / Bohr**3) def summary(self): self.hamiltonian.summary(self.occupations.fermilevel, self.log) self.density.summary(self.atoms, self.occupations.magmom, self.log) self.occupations.summary(self.log) self.wfs.summary(self.log) self.log.fd.flush() def set(self, **kwargs): """Change parameters for calculator. Examples:: calc.set(xc='PBE') calc.set(nbands=20, kpts=(4, 1, 1)) """ changed_parameters = Calculator.set(self, **kwargs) for key in ['setups', 'basis']: if key in changed_parameters: dct = changed_parameters[key] if isinstance(dct, dict) and None in dct: dct['default'] = dct.pop(None) warnings.warn('Please use {key}={dct}'.format(key=key, dct=dct)) # We need to handle txt early in order to get logging up and running: if 'txt' in changed_parameters: self.log.fd = changed_parameters.pop('txt') if not changed_parameters: return {} self.initialized = False self.scf = None self.results = {} self.log('Input parameters:') self.log.print_dict(changed_parameters) self.log() for key in changed_parameters: if key in ['eigensolver', 'convergence'] and self.wfs: self.wfs.set_eigensolver(None) if key in [ 'mixer', 'verbose', 'txt', 'hund', 'random', 'eigensolver', 'idiotproof' ]: continue if key in ['convergence', 'fixdensity', 'maxiter']: continue # More drastic changes: if self.wfs: self.wfs.set_orthonormalized(False) if key in ['external', 'xc', 'poissonsolver']: self.hamiltonian = None elif key in ['occupations', 'width']: pass elif key in ['charge', 'background_charge']: self.hamiltonian = None self.density = None self.wfs = None elif key in ['kpts', 'nbands', 'symmetry']: self.wfs = None elif key in ['h', 'gpts', 'setups', 'spinpol', 'dtype', 'mode']: self.density = None self.hamiltonian = None self.wfs = None elif key in ['basis']: self.wfs = None else: raise TypeError('Unknown keyword argument: "%s"' % key) def initialize_positions(self, atoms=None): """Update the positions of the atoms.""" self.log('Initializing position-dependent things.\n') if atoms is None: atoms = self.atoms else: # Save the state of the atoms: self.atoms = atoms.copy() mpi.synchronize_atoms(atoms, self.world) spos_ac = atoms.get_scaled_positions() % 1.0 rank_a = self.wfs.gd.get_ranks_from_positions(spos_ac) atom_partition = AtomPartition(self.wfs.gd.comm, rank_a, name='gd') self.wfs.set_positions(spos_ac, atom_partition) self.density.set_positions(spos_ac, atom_partition) self.hamiltonian.set_positions(spos_ac, atom_partition) return spos_ac def set_positions(self, atoms=None): """Update the positions of the atoms and initialize wave functions.""" spos_ac = self.initialize_positions(atoms) nlcao, nrand = self.wfs.initialize(self.density, self.hamiltonian, spos_ac) if nlcao + nrand: self.log('Creating initial wave functions:') if nlcao: self.log(' ', plural(nlcao, 'band'), 'from LCAO basis set') if nrand: self.log(' ', plural(nrand, 'band'), 'from random numbers') self.log() self.wfs.eigensolver.reset() self.scf.reset() print_positions(self.atoms, self.log) def initialize(self, atoms=None, reading=False): """Inexpensive initialization.""" self.log('Initialize ...\n') if atoms is None: atoms = self.atoms else: # Save the state of the atoms: self.atoms = atoms.copy() par = self.parameters natoms = len(atoms) cell_cv = atoms.get_cell() / Bohr number_of_lattice_vectors = cell_cv.any(axis=1).sum() if number_of_lattice_vectors < 3: raise ValueError( 'GPAW requires 3 lattice vectors. Your system has {0}.'. format(number_of_lattice_vectors)) pbc_c = atoms.get_pbc() assert len(pbc_c) == 3 magmom_a = atoms.get_initial_magnetic_moments() mpi.synchronize_atoms(atoms, self.world) # Generate new xc functional only when it is reset by set # XXX sounds like this should use the _changed_keywords dictionary. if self.hamiltonian is None or self.hamiltonian.xc is None: if isinstance(par.xc, basestring): xc = XC(par.xc) else: xc = par.xc else: xc = self.hamiltonian.xc mode = par.mode if isinstance(mode, basestring): mode = {'name': mode} if isinstance(mode, dict): mode = create_wave_function_mode(**mode) if par.dtype == complex: warnings.warn('Use mode={0}(..., force_complex_dtype=True) ' 'instead of dtype=complex'.format(mode.name.upper())) mode.force_complex_dtype = True del par['dtype'] par.mode = mode if xc.orbital_dependent and mode.name == 'lcao': raise ValueError('LCAO mode does not support ' 'orbital-dependent XC functionals.') realspace = (mode.name != 'pw' and mode.interpolation != 'fft') if not realspace: pbc_c = np.ones(3, bool) self.create_setups(mode, xc) magnetic = magmom_a.any() spinpol = par.spinpol if par.hund: if natoms != 1: raise ValueError('hund=True arg only valid for single atoms!') spinpol = True magmom_a[0] = self.setups[0].get_hunds_rule_moment(par.charge) if spinpol is None: spinpol = magnetic elif magnetic and not spinpol: raise ValueError('Non-zero initial magnetic moment for a ' + 'spin-paired calculation!') nspins = 1 + int(spinpol) if spinpol: self.log('Spin-polarized calculation.') self.log('Magnetic moment: {0:.6f}\n'.format(magmom_a.sum())) else: self.log('Spin-paired calculation\n') if isinstance(par.background_charge, dict): background = create_background_charge(**par.background_charge) else: background = par.background_charge nao = self.setups.nao nvalence = self.setups.nvalence - par.charge if par.background_charge is not None: nvalence += background.charge M = abs(magmom_a.sum()) nbands = par.nbands orbital_free = any(setup.orbital_free for setup in self.setups) if orbital_free: nbands = 1 if isinstance(nbands, basestring): if nbands[-1] == '%': basebands = int(nvalence + M + 0.5) // 2 nbands = int((float(nbands[:-1]) / 100) * basebands) else: raise ValueError('Integer expected: Only use a string ' 'if giving a percentage of occupied bands') if nbands is None: nbands = 0 for setup in self.setups: nbands_from_atom = setup.get_default_nbands() # Any obscure setup errors? if nbands_from_atom < -(-setup.Nv // 2): raise ValueError('Bad setup: This setup requests %d' ' bands but has %d electrons.' % (nbands_from_atom, setup.Nv)) nbands += nbands_from_atom nbands = min(nao, nbands) elif nbands > nao and mode.name == 'lcao': raise ValueError('Too many bands for LCAO calculation: ' '%d bands and only %d atomic orbitals!' % (nbands, nao)) if nvalence < 0: raise ValueError( 'Charge %f is not possible - not enough valence electrons' % par.charge) if nbands <= 0: nbands = int(nvalence + M + 0.5) // 2 + (-nbands) if nvalence > 2 * nbands and not orbital_free: raise ValueError('Too few bands! Electrons: %f, bands: %d' % (nvalence, nbands)) self.create_occupations(magmom_a.sum(), orbital_free) if self.scf is None: self.create_scf(nvalence, mode) self.create_symmetry(magmom_a, cell_cv) if not self.wfs: self.create_wave_functions(mode, realspace, nspins, nbands, nao, nvalence, self.setups, magmom_a, cell_cv, pbc_c) else: self.wfs.set_setups(self.setups) if not self.wfs.eigensolver: self.create_eigensolver(xc, nbands, mode) if self.density is None and not reading: assert not par.fixdensity, 'No density to fix!' olddens = None if (self.density is not None and (self.density.gd.parsize_c != self.wfs.gd.parsize_c).any()): # Domain decomposition has changed, so we need to # reinitialize density and hamiltonian: if par.fixdensity: olddens = self.density self.density = None self.hamiltonian = None if self.density is None: self.create_density(realspace, mode, background) # XXXXXXXXXX if setups change, then setups.core_charge may change. # But that parameter was supplied in Density constructor! # This surely is a bug! self.density.initialize(self.setups, self.timer, magmom_a, par.hund) self.density.set_mixer(par.mixer) if self.density.mixer.driver.name == 'dummy' or par.fixdensity: self.log('No density mixing\n') else: self.log(self.density.mixer, '\n') self.density.fixed = par.fixdensity self.density.log = self.log if olddens is not None: self.density.initialize_from_other_density(olddens, self.wfs.kptband_comm) if self.hamiltonian is None: self.create_hamiltonian(realspace, mode, xc) xc.initialize(self.density, self.hamiltonian, self.wfs, self.occupations) if xc.name == 'GLLBSC' and olddens is not None: xc.heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelp(olddens) self.print_memory_estimate(maxdepth=memory_estimate_depth + 1) print_parallelization_details(self.wfs, self.density, self.log) self.log('Number of atoms:', natoms) self.log('Number of atomic orbitals:', self.wfs.setups.nao) if self.nbands_parallelization_adjustment != 0: self.log( 'Adjusting number of bands by %+d to match parallelization' % self.nbands_parallelization_adjustment) self.log('Number of bands in calculation:', self.wfs.bd.nbands) self.log('Bands to converge: ', end='') n = par.convergence.get('bands', 'occupied') if n == 'occupied': self.log('occupied states only') elif n == 'all': self.log('all') else: self.log('%d lowest bands' % n) self.log('Number of valence electrons:', self.wfs.nvalence) self.log(flush=True) self.timer.print_info(self) if dry_run: self.dry_run() if (realspace and self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT'): self.hamiltonian.poisson.set_density(self.density) self.hamiltonian.poisson.print_messages(self.log) self.log.fd.flush() self.initialized = True self.log('... initialized\n') def create_setups(self, mode, xc): if self.parameters.filter is None and mode.name != 'pw': gamma = 1.6 N_c = self.parameters.get('gpts') if N_c is None: h = (self.parameters.h or 0.2) / Bohr else: icell_vc = np.linalg.inv(self.atoms.cell) h = ((icell_vc**2).sum(0)**-0.5 / N_c).max() / Bohr def filter(rgd, rcut, f_r, l=0): gcut = np.pi / h - 2 / rcut / gamma f_r[:] = rgd.filter(f_r, rcut * gamma, gcut, l) else: filter = self.parameters.filter Z_a = self.atoms.get_atomic_numbers() self.setups = Setups(Z_a, self.parameters.setups, self.parameters.basis, xc, filter, self.world) self.log(self.setups) def create_grid_descriptor(self, N_c, cell_cv, pbc_c, domain_comm, parsize_domain): return GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) def create_occupations(self, magmom, orbital_free): occ = self.parameters.occupations if occ is None: if orbital_free: occ = {'name': 'orbital-free'} else: width = self.parameters.width if width is not None: warnings.warn( 'Please use occupations=FermiDirac({0})'.format(width)) elif self.atoms.pbc.any(): width = 0.1 # eV else: width = 0.0 occ = {'name': 'fermi-dirac', 'width': width} if isinstance(occ, dict): occ = create_occupation_number_object(**occ) if self.parameters.fixdensity: occ.fixed_fermilevel = True if self.occupations: occ.fermilevel = self.occupations.fermilevel self.occupations = occ # If occupation numbers are changed, and we have wave functions, # recalculate the occupation numbers if self.wfs is not None: self.occupations.calculate(self.wfs) self.occupations.magmom = magmom self.log(self.occupations) def create_scf(self, nvalence, mode): if mode.name == 'lcao': niter_fixdensity = 0 else: niter_fixdensity = 2 nv = max(nvalence, 1) cc = self.parameters.convergence self.scf = SCFLoop( cc.get('eigenstates', 4.0e-8) / Ha**2 * nv, cc.get('energy', 0.0005) / Ha * nv, cc.get('density', 1.0e-4) * nv, cc.get('forces', np.inf) / (Ha / Bohr), self.parameters.maxiter, niter_fixdensity, nv) self.log(self.scf) def create_symmetry(self, magmom_a, cell_cv): symm = self.parameters.symmetry if symm == 'off': symm = {'point_group': False, 'time_reversal': False} m_a = magmom_a.round(decimals=3) # round off id_a = list(zip(self.setups.id_a, m_a)) self.symmetry = Symmetry(id_a, cell_cv, self.atoms.pbc, **symm) self.symmetry.analyze(self.atoms.get_scaled_positions()) self.setups.set_symmetry(self.symmetry) def create_eigensolver(self, xc, nbands, mode): # Number of bands to converge: nbands_converge = self.parameters.convergence.get('bands', 'occupied') if nbands_converge == 'all': nbands_converge = nbands elif nbands_converge != 'occupied': assert isinstance(nbands_converge, int) if nbands_converge < 0: nbands_converge += nbands eigensolver = get_eigensolver(self.parameters.eigensolver, mode, self.parameters.convergence) eigensolver.nbands_converge = nbands_converge # XXX Eigensolver class doesn't define an nbands_converge property if isinstance(xc, SIC): eigensolver.blocksize = 1 self.wfs.set_eigensolver(eigensolver) self.log(self.wfs.eigensolver, '\n') def create_density(self, realspace, mode, background): gd = self.wfs.gd big_gd = gd.new_descriptor(comm=self.world) # Check whether grid is too small. 8 is smallest admissible. # (we decide this by how difficult it is to make the tests pass) # (Actually it depends on stencils! But let the user deal with it) N_c = big_gd.get_size_of_global_array(pad=True) too_small = np.any(N_c / big_gd.parsize_c < 8) if self.parallel['augment_grids'] and not too_small: aux_gd = big_gd else: aux_gd = gd redistributor = GridRedistributor(self.world, self.wfs.kptband_comm, gd, aux_gd) # Construct grid descriptor for fine grids for densities # and potentials: finegd = aux_gd.refine() kwargs = dict(gd=gd, finegd=finegd, nspins=self.wfs.nspins, charge=self.parameters.charge + self.wfs.setups.core_charge, redistributor=redistributor, background_charge=background) if realspace: self.density = RealSpaceDensity(stencil=mode.interpolation, **kwargs) else: self.density = pw.ReciprocalSpaceDensity(**kwargs) self.log(self.density, '\n') def create_hamiltonian(self, realspace, mode, xc): dens = self.density kwargs = dict(gd=dens.gd, finegd=dens.finegd, nspins=dens.nspins, setups=dens.setups, timer=self.timer, xc=xc, world=self.world, redistributor=dens.redistributor, vext=self.parameters.external, psolver=self.parameters.poissonsolver) if realspace: self.hamiltonian = RealSpaceHamiltonian(stencil=mode.interpolation, **kwargs) xc.set_grid_descriptor(self.hamiltonian.finegd) # XXX else: self.hamiltonian = pw.ReciprocalSpaceHamiltonian( pd2=dens.pd2, pd3=dens.pd3, realpbc_c=self.atoms.pbc, **kwargs) xc.set_grid_descriptor(dens.xc_redistributor.aux_gd) # XXX self.log(self.hamiltonian, '\n') def create_wave_functions(self, mode, realspace, nspins, nbands, nao, nvalence, setups, magmom_a, cell_cv, pbc_c): par = self.parameters bzkpts_kc = kpts2ndarray(par.kpts, self.atoms) kd = KPointDescriptor(bzkpts_kc, nspins) self.timer.start('Set symmetry') kd.set_symmetry(self.atoms, self.symmetry, comm=self.world) self.timer.stop('Set symmetry') self.log(kd) parallelization = mpi.Parallelization(self.world, nspins * kd.nibzkpts) parsize_kpt = self.parallel['kpt'] parsize_domain = self.parallel['domain'] parsize_bands = self.parallel['band'] ndomains = None if parsize_domain is not None: ndomains = np.prod(parsize_domain) if mode.name == 'pw': if ndomains is not None and ndomains > 1: raise ValueError('Planewave mode does not support ' 'domain decomposition.') ndomains = 1 parallelization.set(kpt=parsize_kpt, domain=ndomains, band=parsize_bands) comms = parallelization.build_communicators() domain_comm = comms['d'] kpt_comm = comms['k'] band_comm = comms['b'] kptband_comm = comms['D'] domainband_comm = comms['K'] self.comms = comms if par.gpts is not None: if par.h is not None: raise ValueError("""You can't use both "gpts" and "h"!""") N_c = np.array(par.gpts) else: h = par.h if h is not None: h /= Bohr N_c = get_number_of_grid_points(cell_cv, h, mode, realspace, kd.symmetry) self.symmetry.check_grid(N_c) kd.set_communicator(kpt_comm) parstride_bands = self.parallel['stridebands'] # Unfortunately we need to remember that we adjusted the # number of bands so we can print a warning if it differs # from the number specified by the user. (The number can # be inferred from the input parameters, but it's tricky # because we allow negative numbers) self.nbands_parallelization_adjustment = -nbands % band_comm.size nbands += self.nbands_parallelization_adjustment bd = BandDescriptor(nbands, band_comm, parstride_bands) # Construct grid descriptor for coarse grids for wave functions: gd = self.create_grid_descriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) if hasattr(self, 'time') or mode.force_complex_dtype: dtype = complex else: if kd.gamma: dtype = float else: dtype = complex wfs_kwargs = dict(gd=gd, nvalence=nvalence, setups=setups, bd=bd, dtype=dtype, world=self.world, kd=kd, kptband_comm=kptband_comm, timer=self.timer) if self.parallel['sl_auto']: # Choose scalapack parallelization automatically for key, val in self.parallel.items(): if (key.startswith('sl_') and key != 'sl_auto' and val is not None): raise ValueError("Cannot use 'sl_auto' together " "with '%s'" % key) max_scalapack_cpus = bd.comm.size * gd.comm.size nprow = max_scalapack_cpus npcol = 1 # Get a sort of reasonable number of columns/rows while npcol < nprow and nprow % 2 == 0: npcol *= 2 nprow //= 2 assert npcol * nprow == max_scalapack_cpus # ScaLAPACK creates trouble if there aren't at least a few # whole blocks; choose block size so there will always be # several blocks. This will crash for small test systems, # but so will ScaLAPACK in any case blocksize = min(-(-nbands // 4), 64) sl_default = (nprow, npcol, blocksize) else: sl_default = self.parallel['sl_default'] if mode.name == 'lcao': # Layouts used for general diagonalizer sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, bd, domainband_comm, dtype, nao=nao, timer=self.timer) self.wfs = mode(lcaoksl, **wfs_kwargs) elif mode.name == 'fd' or mode.name == 'pw': # buffer_size keyword only relevant for fdpw buffer_size = self.parallel['buffer_size'] # Layouts used for diagonalizer sl_diagonalize = self.parallel['sl_diagonalize'] if sl_diagonalize is None: sl_diagonalize = sl_default diagksl = get_KohnSham_layouts( sl_diagonalize, 'fd', # XXX # choice of key 'fd' not so nice gd, bd, domainband_comm, dtype, buffer_size=buffer_size, timer=self.timer) # Layouts used for orthonormalizer sl_inverse_cholesky = self.parallel['sl_inverse_cholesky'] if sl_inverse_cholesky is None: sl_inverse_cholesky = sl_default if sl_inverse_cholesky != sl_diagonalize: message = 'sl_inverse_cholesky != sl_diagonalize ' \ 'is not implemented.' raise NotImplementedError(message) orthoksl = get_KohnSham_layouts(sl_inverse_cholesky, 'fd', gd, bd, domainband_comm, dtype, buffer_size=buffer_size, timer=self.timer) # Use (at most) all available LCAO for initialization lcaonbands = min(nbands, nao // band_comm.size * band_comm.size) try: lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands) except RuntimeError: initksl = None else: # Layouts used for general diagonalizer # (LCAO initialization) sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default initksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, lcaobd, domainband_comm, dtype, nao=nao, timer=self.timer) self.wfs = mode(diagksl, orthoksl, initksl, **wfs_kwargs) else: self.wfs = mode(self, **wfs_kwargs) self.log(self.wfs, '\n') def dry_run(self): # Can be overridden like in gpaw.atom.atompaw print_cell(self.wfs.gd, self.atoms.pbc, self.log) print_positions(self.atoms, self.log) self.log.fd.flush() raise SystemExit
class HybridXC(HybridXCBase): orbital_dependent = True def __init__(self, name, hybrid=None, xc=None, alpha=None, gamma_point=1, method='standard', bandstructure=False, logfilename='-', bands=None, fcut=1e-10, molecule=False, qstride=1, world=None): """Mix standard functionals with exact exchange. name: str Name of functional: EXX, PBE0, HSE03, HSE06 hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. method: str Use 'standard' standard formula and 'acdf for adiabatic-connection dissipation fluctuation formula. alpha: float XXX describe gamma_point: bool 0: Skip k2-k1=0 interactions. 1: Use the alpha method. 2: Integrate the gamma point. bandstructure: bool Calculate bandstructure instead of just the total energy. bands: list of int List of bands to calculate bandstructure for. Default is all bands. molecule: bool Decouple electrostatic interactions between periodically repeated images. fcut: float Threshold for empty band. """ self.alpha = alpha self.fcut = fcut self.gamma_point = gamma_point self.method = method self.bandstructure = bandstructure self.bands = bands self.fd = logfilename self.write_timing_information = True HybridXCBase.__init__(self, name, hybrid, xc) # EXX energies: self.exx = None # total self.evv = None # valence-valence (pseudo part) self.evvacdf = None # valence-valence (pseudo part) self.devv = None # valence-valence (PAW correction) self.evc = None # valence-core self.ecc = None # core-core self.exx_skn = None # bandstructure self.qlatest = None if world is None: world = mpi.world self.world = world self.molecule = molecule if isinstance(qstride, int): qstride = [qstride] * 3 self.qstride_c = np.asarray(qstride) self.timer = Timer() def log(self, *args, **kwargs): prnt(file=self.fd, *args, **kwargs) self.fd.flush() def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, dens, ham, wfs, occupations): assert wfs.bd.comm.size == 1 self.xc.initialize(dens, ham, wfs, occupations) self.dens = dens self.wfs = wfs # Make a k-point descriptor that is not distributed # (self.kd.comm is serial_comm): self.kd = wfs.kd.copy() self.fd = logfile(self.fd, self.world.rank) wfs.initialize_wave_functions_from_restart_file() def set_positions(self, spos_ac): self.spos_ac = spos_ac def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx * self.hybrid def calculate_exx(self): """Non-selfconsistent calculation.""" self.timer.start('EXX') self.timer.start('Initialization') kd = self.kd wfs = self.wfs if fftw.FFTPlan is fftw.NumpyFFTPlan: self.log('NOT USING FFTW !!') self.log('Spins:', self.wfs.nspins) W = max(1, self.wfs.kd.comm.size // self.wfs.nspins) # Are the k-points distributed? kparallel = (W > 1) # Find number of occupied bands: self.nocc_sk = np.zeros((self.wfs.nspins, kd.nibzkpts), int) for kpt in self.wfs.kpt_u: for n, f in enumerate(kpt.f_n): if abs(f) < self.fcut: self.nocc_sk[kpt.s, kpt.k] = n break else: self.nocc_sk[kpt.s, kpt.k] = self.wfs.bd.nbands self.wfs.kd.comm.sum(self.nocc_sk) noccmin = self.nocc_sk.min() noccmax = self.nocc_sk.max() self.log('Number of occupied bands (min, max): %d, %d' % (noccmin, noccmax)) self.log('Number of valence electrons:', self.wfs.setups.nvalence) if self.bandstructure: self.log('Calculating eigenvalue shifts.') # allocate array for eigenvalue shifts: self.exx_skn = np.zeros((self.wfs.nspins, kd.nibzkpts, self.wfs.bd.nbands)) if self.bands is None: noccmax = self.wfs.bd.nbands else: noccmax = max(max(self.bands) + 1, noccmax) N_c = self.kd.N_c vol = wfs.gd.dv * wfs.gd.N_c.prod() if self.alpha is None: alpha = 6 * vol**(2 / 3.0) / pi**2 else: alpha = self.alpha if self.gamma_point == 1: if alpha == 0.0: qvol = (2*np.pi)**3 / vol / N_c.prod() self.gamma = 4*np.pi * (3*qvol / (4*np.pi))**(1/3.) / qvol else: self.gamma = self.calculate_gamma(vol, alpha) else: kcell_cv = wfs.gd.cell_cv.copy() kcell_cv[0] *= N_c[0] kcell_cv[1] *= N_c[1] kcell_cv[2] *= N_c[2] self.gamma = madelung(kcell_cv) * vol * N_c.prod() / (4 * np.pi) self.log('Value of alpha parameter: %.3f Bohr^2' % alpha) self.log('Value of gamma parameter: %.3f Bohr^2' % self.gamma) # Construct all possible q=k2-k1 vectors: Nq_c = (N_c - 1) // self.qstride_c i_qc = np.indices(Nq_c * 2 + 1, float).transpose( (1, 2, 3, 0)).reshape((-1, 3)) self.bzq_qc = (i_qc - Nq_c) / N_c * self.qstride_c self.q0 = ((Nq_c * 2 + 1).prod() - 1) // 2 # index of q=(0,0,0) assert not self.bzq_qc[self.q0].any() # Count number of pairs for each q-vector: self.npairs_q = np.zeros(len(self.bzq_qc), int) for s in range(kd.nspins): for k1 in range(kd.nibzkpts): for k2 in range(kd.nibzkpts): for K2, q, n1_n, n2 in self.indices(s, k1, k2): self.npairs_q[q] += len(n1_n) self.npairs0 = self.npairs_q.sum() # total number of pairs self.log('Number of pairs:', self.npairs0) # Distribute q-vectors to Q processors: Q = self.world.size // self.wfs.kd.comm.size myrank = self.world.rank // self.wfs.kd.comm.size rank = 0 N = 0 myq = [] nq = 0 for q, n in enumerate(self.npairs_q): if n > 0: nq += 1 if rank == myrank: myq.append(q) N += n if N >= (rank + 1.0) * self.npairs0 / Q: rank += 1 assert len(myq) > 0, 'Too few q-vectors for too many processes!' self.bzq_qc = self.bzq_qc[myq] try: self.q0 = myq.index(self.q0) except ValueError: self.q0 = None self.log('%d x %d x %d k-points' % tuple(self.kd.N_c)) self.log('Distributing %d IBZ k-points over %d process(es).' % (kd.nibzkpts, self.wfs.kd.comm.size)) self.log('Distributing %d q-vectors over %d process(es).' % (nq, Q)) # q-point descriptor for my q-vectors: qd = KPointDescriptor(self.bzq_qc) # Plane-wave descriptor for all wave-functions: self.pd = PWDescriptor(wfs.pd.ecut, wfs.gd, dtype=wfs.pd.dtype, kd=kd) # Plane-wave descriptor pair-densities: self.pd2 = PWDescriptor(self.dens.pd2.ecut, self.dens.gd, dtype=wfs.dtype, kd=qd) self.log('Cutoff energies:') self.log(' Wave functions: %10.3f eV' % (self.pd.ecut * Hartree)) self.log(' Density: %10.3f eV' % (self.pd2.ecut * Hartree)) # Calculate 1/|G+q|^2 with special treatment of |G+q|=0: G2_qG = self.pd2.G2_qG if self.q0 is None: if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] else: self.iG2_qG = [(1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG] else: G2_qG[self.q0][0] = 117.0 # avoid division by zero if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] self.iG2_qG[self.q0][0] = self.gamma else: self.iG2_qG = [(1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG] self.iG2_qG[self.q0][0] = 1 / (4 * self.omega**2) G2_qG[self.q0][0] = 0.0 # restore correct value # Compensation charges: self.ghat = PWLFC([setup.ghat_l for setup in wfs.setups], self.pd2) self.ghat.set_positions(self.spos_ac) if self.molecule: self.initialize_gaussian() self.log('Value of beta parameter: %.3f 1/Bohr^2' % self.beta) self.timer.stop('Initialization') # Ready ... set ... go: self.t0 = time() self.npairs = 0 self.evv = 0.0 self.evvacdf = 0.0 for s in range(self.wfs.nspins): kpt1_q = [KPoint(self.wfs, noccmax).initialize(kpt) for kpt in self.wfs.kpt_u if kpt.s == s] kpt2_q = kpt1_q[:] if len(kpt1_q) == 0: # No s-spins on this CPU: continue # Send and receive ranks: srank = self.wfs.kd.get_rank_and_index( s, (kpt1_q[0].k - 1) % kd.nibzkpts)[0] rrank = self.wfs.kd.get_rank_and_index( s, (kpt1_q[-1].k + 1) % kd.nibzkpts)[0] # Shift k-points kd.nibzkpts - 1 times: for i in range(kd.nibzkpts): if i < kd.nibzkpts - 1: if kparallel: kpt = kpt2_q[-1].next(self.wfs) kpt.start_receiving(rrank) kpt2_q[0].start_sending(srank) else: kpt = kpt2_q[0] self.timer.start('Calculate') for kpt1, kpt2 in zip(kpt1_q, kpt2_q): # Loop over all k-points that k2 can be mapped to: for K2, q, n1_n, n2 in self.indices(s, kpt1.k, kpt2.k): self.apply(K2, q, kpt1, kpt2, n1_n, n2) self.timer.stop('Calculate') if i < kd.nibzkpts - 1: self.timer.start('Wait') if kparallel: kpt.wait() kpt2_q[0].wait() self.timer.stop('Wait') kpt2_q.pop(0) kpt2_q.append(kpt) self.evv = self.world.sum(self.evv) self.evvacdf = self.world.sum(self.evvacdf) self.calculate_exx_paw_correction() if self.method == 'standard': self.exx = self.evv + self.devv + self.evc + self.ecc elif self.method == 'acdf': self.exx = self.evvacdf + self.devv + self.evc + self.ecc else: 1 / 0 self.log('Exact exchange energy:') for txt, e in [ ('core-core', self.ecc), ('valence-core', self.evc), ('valence-valence (pseudo, acdf)', self.evvacdf), ('valence-valence (pseudo, standard)', self.evv), ('valence-valence (correction)', self.devv), ('total (%s)' % self.method, self.exx)]: self.log(' %-36s %14.6f eV' % (txt + ':', e * Hartree)) self.log('Total time: %10.3f seconds' % (time() - self.t0)) self.npairs = self.world.sum(self.npairs) assert self.npairs == self.npairs0 self.timer.stop('EXX') self.timer.write(self.fd) def calculate_gamma(self, vol, alpha): if self.molecule: return 0.0 N_c = self.kd.N_c offset_c = (N_c + 1) % 2 * 0.5 / N_c bzq_qc = monkhorst_pack(N_c) + offset_c qd = KPointDescriptor(bzq_qc) pd = PWDescriptor(self.wfs.pd.ecut, self.wfs.gd, kd=qd) gamma = (vol / (2 * pi)**2 * sqrt(pi / alpha) * self.kd.nbzkpts) for G2_G in pd.G2_qG: if G2_G[0] < 1e-7: G2_G = G2_G[1:] gamma -= np.dot(np.exp(-alpha * G2_G), G2_G**-1) return gamma / self.qstride_c.prod() def indices(self, s, k1, k2): """Generator for (K2, q, n1, n2) indices for (k1, k2) pair. s: int Spin index. k1: int Index of k-point in the IBZ. k2: int Index of k-point in the IBZ. Returns (K, q, n1_n, n2), where K then index of the k-point in the BZ that k2 is mapped to, q is the index of the q-vector between K and k1, and n1_n is a list of bands that should be combined with band n2.""" for K, k in enumerate(self.kd.bz2ibz_k): if k == k2: for K, q, n1_n, n2 in self._indices(s, k1, k2, K): yield K, q, n1_n, n2 def _indices(self, s, k1, k2, K2): k1_c = self.kd.ibzk_kc[k1] k2_c = self.kd.bzk_kc[K2] q_c = k2_c - k1_c q = abs(self.bzq_qc - q_c).sum(1).argmin() if abs(self.bzq_qc[q] - q_c).sum() > 1e-7: return if self.gamma_point == 0 and q == self.q0: return nocc1 = self.nocc_sk[s, k1] nocc2 = self.nocc_sk[s, k2] # Is k2 in the IBZ? is_ibz2 = (self.kd.ibz2bz_k[k2] == K2) for n2 in range(self.wfs.bd.nbands): # Find range of n1's (from n1a to n1b-1): if is_ibz2: # We get this combination twice, so let's only do half: if k1 >= k2: n1a = n2 else: n1a = n2 + 1 else: n1a = 0 n1b = self.wfs.bd.nbands if self.bandstructure: if n2 >= nocc2: n1b = min(n1b, nocc1) else: if n2 >= nocc2: break n1b = min(n1b, nocc1) if self.bands is not None: assert self.bandstructure n1_n = [] for n1 in range(n1a, n1b): if (n1 in self.bands and n2 < nocc2 or is_ibz2 and n2 in self.bands and n1 < nocc1): n1_n.append(n1) n1_n = np.array(n1_n) else: n1_n = np.arange(n1a, n1b) if len(n1_n) == 0: continue yield K2, q, n1_n, n2 def apply(self, K2, q, kpt1, kpt2, n1_n, n2): k20_c = self.kd.ibzk_kc[kpt2.k] k2_c = self.kd.bzk_kc[K2] if k2_c.any(): self.timer.start('Initialize plane waves') eik2r_R = self.wfs.gd.plane_wave(k2_c) eik20r_R = self.wfs.gd.plane_wave(k20_c) self.timer.stop('Initialize plane waves') else: eik2r_R = 1.0 eik20r_R = 1.0 w1 = self.kd.weight_k[kpt1.k] w2 = self.kd.weight_k[kpt2.k] # Is k2 in the 1. BZ? is_ibz2 = (self.kd.ibz2bz_k[kpt2.k] == K2) e_n = self.calculate_interaction(n1_n, n2, kpt1, kpt2, q, K2, eik20r_R, eik2r_R, is_ibz2) e_n *= 1.0 / self.kd.nbzkpts / self.wfs.nspins * self.qstride_c.prod() if q == self.q0: e_n[n1_n == n2] *= 0.5 f1_n = kpt1.f_n[n1_n] eps1_n = kpt1.eps_n[n1_n] f2 = kpt2.f_n[n2] eps2 = kpt2.eps_n[n2] s_n = np.sign(eps2 - eps1_n) evv = (f1_n * f2 * e_n).sum() evvacdf = 0.5 * (f1_n * (1 - s_n) * e_n + f2 * (1 + s_n) * e_n).sum() self.evv += evv * w1 self.evvacdf += evvacdf * w1 if is_ibz2: self.evv += evv * w2 self.evvacdf += evvacdf * w2 if self.bandstructure: x = self.wfs.nspins self.exx_skn[kpt1.s, kpt1.k, n1_n] += x * f2 * e_n if is_ibz2: self.exx_skn[kpt2.s, kpt2.k, n2] += x * np.dot(f1_n, e_n) def calculate_interaction(self, n1_n, n2, kpt1, kpt2, q, k, eik20r_R, eik2r_R, is_ibz2): """Calculate Coulomb interactions. For all n1 in the n1_n list, calculate interaction with n2.""" # number of plane waves: ng1 = self.wfs.ng_k[kpt1.k] ng2 = self.wfs.ng_k[kpt2.k] # Transform to real space and apply symmetry operation: self.timer.start('IFFT1') if is_ibz2: u2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) else: psit2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) * eik20r_R self.timer.start('Symmetry transform') u2_R = self.kd.transform_wave_function(psit2_R, k) / eik2r_R self.timer.stop() self.timer.stop() # Calculate pair densities: nt_nG = self.pd2.zeros(len(n1_n), q=q) for n1, nt_G in zip(n1_n, nt_nG): self.timer.start('IFFT2') u1_R = self.pd.ifft(kpt1.psit_nG[n1, :ng1], kpt1.k) self.timer.stop() nt_R = u1_R.conj() * u2_R self.timer.start('FFT') nt_G[:] = self.pd2.fft(nt_R, q) self.timer.stop() s = self.kd.sym_k[k] time_reversal = self.kd.time_reversal_k[k] k2_c = self.kd.ibzk_kc[kpt2.k] self.timer.start('Compensation charges') Q_anL = {} # coefficients for shape functions for a, P1_ni in kpt1.P_ani.items(): P1_ni = P1_ni[n1_n] if is_ibz2: P2_i = kpt2.P_ani[a][n2] else: b = self.kd.symmetry.a_sa[s, a] S_c = (np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s]) - self.spos_ac[b]) assert abs(S_c.round() - S_c).max() < 1e-5 if self.ghat.dtype == complex: x = np.exp(2j * pi * np.dot(k2_c, S_c)) else: x = 1.0 P2_i = np.dot(self.wfs.setups[a].R_sii[s], kpt2.P_ani[b][n2]) * x if time_reversal: P2_i = P2_i.conj() D_np = [] for P1_i in P1_ni: D_ii = np.outer(P1_i.conj(), P2_i) D_np.append(pack(D_ii)) Q_anL[a] = np.dot(D_np, self.wfs.setups[a].Delta_pL) self.timer.start('Expand') if q != self.qlatest: self.f_IG = self.ghat.expand(q) self.qlatest = q self.timer.stop('Expand') # Add compensation charges: self.ghat.add(nt_nG, Q_anL, q, self.f_IG) self.timer.stop('Compensation charges') if self.molecule and n2 in n1_n: nn = (n1_n == n2).nonzero()[0][0] nt_nG[nn] -= self.ngauss_G else: nn = None iG2_G = self.iG2_qG[q] # Calculate energies: e_n = np.empty(len(n1_n)) for n, nt_G in enumerate(nt_nG): e_n[n] = -4 * pi * np.real(self.pd2.integrate(nt_G, nt_G * iG2_G)) self.npairs += 1 if nn is not None: e_n[nn] -= 2 * (self.pd2.integrate(nt_nG[nn], self.vgauss_G) + (self.beta / 2 / pi)**0.5) if self.write_timing_information: t = (time() - self.t0) / len(n1_n) self.log('Time for first pair-density: %10.3f seconds' % t) self.log('Estimated total time: %10.3f seconds' % (t * self.npairs0 / self.world.size)) self.write_timing_information = False return e_n def calculate_exx_paw_correction(self): self.timer.start('PAW correction') self.devv = 0.0 self.evc = 0.0 self.ecc = 0.0 deg = 2 // self.wfs.nspins # spin degeneracy for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.devv -= D_ii[i1, i2] * A / deg self.evc -= np.dot(D_p, setup.X_p) self.ecc += setup.ExxC if not self.bandstructure: self.timer.stop('PAW correction') return Q = self.world.size // self.wfs.kd.comm.size self.exx_skn *= Q for kpt in self.wfs.kpt_u: for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) P_ni = kpt.P_ani[a] for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.exx_skn[kpt.s, kpt.k] -= \ (A * P_ni[:, i1].conj() * P_ni[:, i2]).real p12 = packed_index(i1, i2, ni) self.exx_skn[kpt.s, kpt.k] -= \ (P_ni[:, i1].conj() * setup.X_p[p12] * P_ni[:, i2]).real / self.wfs.nspins self.world.sum(self.exx_skn) self.exx_skn *= self.hybrid / Q self.timer.stop('PAW correction') def initialize_gaussian(self): """Calculate gaussian compensation charge and its potential. Used to decouple electrostatic interactions between periodically repeated images for molecular calculations. Charge containing one electron:: (beta/pi)^(3/2)*exp(-beta*r^2), its Fourier transform:: exp(-G^2/(4*beta)), and its potential:: erf(beta^0.5*r)/r. """ gd = self.wfs.gd # Set exponent of exp-function to -19 on the boundary: self.beta = 4 * 19 * (gd.icell_cv**2).sum(1).max() # Calculate gaussian: G_Gv = self.pd2.get_reciprocal_vectors() G2_G = self.pd2.G2_qG[0] C_v = gd.cell_cv.sum(0) / 2 # center of cell self.ngauss_G = np.exp(-1.0 / (4 * self.beta) * G2_G + 1j * np.dot(G_Gv, C_v)) / gd.dv # Calculate potential from gaussian: R_Rv = gd.get_grid_point_coordinates().transpose((1, 2, 3, 0)) r_R = ((R_Rv - C_v)**2).sum(3)**0.5 if (gd.N_c % 2 == 0).all(): r_R[tuple(gd.N_c // 2)] = 1.0 # avoid dividing by zero v_R = erf(self.beta**0.5 * r_R) / r_R if (gd.N_c % 2 == 0).all(): v_R[tuple(gd.N_c // 2)] = (4 * self.beta / pi)**0.5 self.vgauss_G = self.pd2.fft(v_R) # Compare self-interaction to analytic result: assert abs(0.5 * self.pd2.integrate(self.ngauss_G, self.vgauss_G) - (self.beta / 2 / pi)**0.5) < 1e-6
class RPACorrelation: def __init__(self, calc, xc='RPA', filename=None, skip_gamma=False, qsym=True, nlambda=None, nfrequencies=16, frequency_max=800.0, frequency_scale=2.0, frequencies=None, weights=None, world=mpi.world, nblocks=1, wstc=False, txt=sys.stdout): if isinstance(calc, str): calc = GPAW(calc, txt=None, communicator=mpi.serial_comm) self.calc = calc if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w') self.fd = txt self.timer = Timer() if frequencies is None: frequencies, weights = get_gauss_legendre_points( nfrequencies, frequency_max, frequency_scale) user_spec = False else: assert weights is not None user_spec = True self.omega_w = frequencies / Hartree self.weight_w = weights / Hartree if nblocks > 1: assert len(self.omega_w) % nblocks == 0 assert wstc self.wstc = wstc self.nblocks = nblocks self.world = world self.skip_gamma = skip_gamma self.ibzq_qc = None self.weight_q = None self.initialize_q_points(qsym) # Energies for all q-vetors and cutoff energies: self.energy_qi = [] self.filename = filename self.print_initialization(xc, frequency_scale, nlambda, user_spec) def initialize_q_points(self, qsym): kd = self.calc.wfs.kd self.bzq_qc = kd.get_bz_q_points(first=True) if not qsym: self.ibzq_qc = self.bzq_qc self.weight_q = np.ones(len(self.bzq_qc)) / len(self.bzq_qc) else: U_scc = kd.symmetry.op_scc self.ibzq_qc = kd.get_ibz_q_points(self.bzq_qc, U_scc)[0] self.weight_q = kd.q_weights def read(self): lines = open(self.filename).readlines()[1:] n = 0 self.energy_qi = [] nq = len(lines) // len(self.ecut_i) for q_c in self.ibzq_qc[:nq]: self.energy_qi.append([]) for ecut in self.ecut_i: q1, q2, q3, ec, energy = [float(x) for x in lines[n].split()] self.energy_qi[-1].append(energy / Hartree) n += 1 if (abs(q_c - (q1, q2, q3)).max() > 1e-4 or abs(int(ecut * Hartree) - ec) > 0): self.energy_qi = [] return print('Read %d q-points from file: %s' % (nq, self.filename), file=self.fd) print(file=self.fd) def write(self): if self.world.rank == 0 and self.filename: fd = open(self.filename, 'w') print('#%9s %10s %10s %8s %12s' % ('q1', 'q2', 'q3', 'E_cut', 'E_c(q)'), file=fd) for energy_i, q_c in zip(self.energy_qi, self.ibzq_qc): for energy, ecut in zip(energy_i, self.ecut_i): print('%10.4f %10.4f %10.4f %8d %r' % (tuple(q_c) + (ecut * Hartree, energy * Hartree)), file=fd) def calculate(self, ecut, nbands=None, spin=False): """Calculate RPA correlation energy for one or several cutoffs. ecut: float or list of floats Plane-wave cutoff(s). nbands: int Number of bands (defaults to number of plane-waves). spin: bool Separate spin in response funtion. (Only needed for beyond RPA methods that inherit this function). """ p = functools.partial(print, file=self.fd) if isinstance(ecut, (float, int)): ecut = ecut * (1 + 0.5 * np.arange(6))**(-2 / 3) self.ecut_i = np.asarray(np.sort(ecut)) / Hartree ecutmax = max(self.ecut_i) if nbands is None: p('Response function bands : Equal to number of plane waves') else: p('Response function bands : %s' % nbands) p('Plane wave cutoffs (eV) :', end='') for e in self.ecut_i: p(' {0:.3f}'.format(e * Hartree), end='') p() p() if self.filename and os.path.isfile(self.filename): self.read() self.world.barrier() chi0 = Chi0(self.calc, 1j * Hartree * self.omega_w, eta=0.0, intraband=False, hilbert=False, txt=self.fd, timer=self.timer, world=self.world, no_optical_limit=self.wstc, nblocks=self.nblocks) self.blockcomm = chi0.blockcomm wfs = self.calc.wfs if self.wstc: with self.timer('WSTC-init'): p('Using Wigner-Seitz truncated Coulomb potential.') self.wstc = WignerSeitzTruncatedCoulomb( wfs.gd.cell_cv, wfs.kd.N_c, self.fd) nq = len(self.energy_qi) nw = len(self.omega_w) nGmax = max( count_reciprocal_vectors(ecutmax, wfs.gd, q_c) for q_c in self.ibzq_qc[nq:]) mynGmax = (nGmax + self.nblocks - 1) // self.nblocks nx = (1 + spin) * nw * mynGmax * nGmax A1_x = np.empty(nx, complex) if self.nblocks > 1: A2_x = np.empty(nx, complex) else: A2_x = None self.timer.start('RPA') for q_c in self.ibzq_qc[nq:]: if np.allclose(q_c, 0.0) and self.skip_gamma: self.energy_qi.append(len(self.ecut_i) * [0.0]) self.write() p('Not calculating E_c(q) at Gamma') p() continue thisqd = KPointDescriptor([q_c]) pd = PWDescriptor(ecutmax, wfs.gd, complex, thisqd) nG = pd.ngmax mynG = (nG + self.nblocks - 1) // self.nblocks chi0.Ga = self.blockcomm.rank * mynG chi0.Gb = min(chi0.Ga + mynG, nG) shape = (1 + spin, nw, chi0.Gb - chi0.Ga, nG) chi0_swGG = A1_x[:np.prod(shape)].reshape(shape) chi0_swGG[:] = 0.0 if self.wstc or np.allclose(q_c, 0.0): # Wings (x=0,1) and head (G=0) for optical limit and three # directions (v=0,1,2): chi0_swxvG = np.zeros((1 + spin, nw, 2, 3, nG), complex) chi0_swvv = np.zeros((1 + spin, nw, 3, 3), complex) else: chi0_swxvG = None chi0_swvv = None Q_aGii = chi0.initialize_paw_corrections(pd) # First not completely filled band: m1 = chi0.nocc1 p('# %s - %s' % (len(self.energy_qi), ctime().split()[-2])) p('q = [%1.3f %1.3f %1.3f]' % tuple(q_c)) energy_i = [] for ecut in self.ecut_i: if ecut == ecutmax: # Nothing to cut away: cut_G = None m2 = nbands or nG else: cut_G = np.arange(nG)[pd.G2_qG[0] <= 2 * ecut] m2 = len(cut_G) p('E_cut = %d eV / Bands = %d:' % (ecut * Hartree, m2)) self.fd.flush() energy = self.calculate_q(chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, Q_aGii, m1, m2, cut_G, A2_x) energy_i.append(energy) m1 = m2 a = 1 / chi0.kncomm.size if ecut < ecutmax and a != 1.0: # Chi0 will be summed again over chicomm, so we divide # by its size: chi0_swGG *= a if chi0_swxvG is not None: chi0_swxvG *= a chi0_swvv *= a self.energy_qi.append(energy_i) self.write() p() e_i = np.dot(self.weight_q, np.array(self.energy_qi)) p('==========================================================') p() p('Total correlation energy:') for e_cut, e in zip(self.ecut_i, e_i): p('%6.0f: %6.4f eV' % (e_cut * Hartree, e * Hartree)) p() self.energy_qi = [] # important if another calculation is performed if len(e_i) > 1: self.extrapolate(e_i) p('Calculation completed at: ', ctime()) p() self.timer.stop('RPA') self.timer.write(self.fd) self.fd.flush() return e_i * Hartree @timer('chi0(q)') def calculate_q(self, chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, Q_aGii, m1, m2, cut_G, A2_x): chi0_wGG = chi0_swGG[0] if chi0_swxvG is not None: chi0_wxvG = chi0_swxvG[0] chi0_wvv = chi0_swvv[0] else: chi0_wxvG = None chi0_wvv = None chi0._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv, Q_aGii, m1, m2, [0, 1]) print('E_c(q) = ', end='', file=self.fd) chi0_wGG = chi0.redistribute(chi0_wGG, A2_x) if not pd.kd.gamma or self.wstc: e = self.calculate_energy(pd, chi0_wGG, cut_G) print('%.3f eV' % (e * Hartree), file=self.fd) self.fd.flush() else: e = 0.0 for v in range(3): chi0_wGG[:, 0] = chi0_wxvG[:, 0, v] chi0_wGG[:, :, 0] = chi0_wxvG[:, 1, v] chi0_wGG[:, 0, 0] = chi0_wvv[:, v, v] ev = self.calculate_energy(pd, chi0_wGG, cut_G) e += ev print('%.3f' % (ev * Hartree), end='', file=self.fd) if v < 2: print('/', end='', file=self.fd) else: print(' eV', file=self.fd) self.fd.flush() e /= 3 return e @timer('Energy') def calculate_energy(self, pd, chi0_wGG, cut_G): """Evaluate correlation energy from chi0.""" if self.wstc: invG_G = (self.wstc.get_potential(pd) / (4 * pi))**0.5 else: G_G = pd.G2_qG[0]**0.5 # |G+q| if pd.kd.gamma: G_G[0] = 1.0 invG_G = 1.0 / G_G if cut_G is not None: invG_G = invG_G[cut_G] nG = len(invG_G) e_w = [] for chi0_GG in chi0_wGG: if cut_G is not None: chi0_GG = chi0_GG.take(cut_G, 0).take(cut_G, 1) e_GG = (np.eye(nG) - 4 * np.pi * chi0_GG * invG_G * invG_G[:, np.newaxis]) e = np.log(np.linalg.det(e_GG)) + nG - np.trace(e_GG) e_w.append(e.real) E_w = np.zeros_like(self.omega_w) self.blockcomm.all_gather(np.array(e_w), E_w) energy = np.dot(E_w, self.weight_w) / (2 * np.pi) self.E_w = E_w return energy def extrapolate(self, e_i): print('Extrapolated energies:', file=self.fd) ex_i = [] for i in range(len(e_i) - 1): e1, e2 = e_i[i:i + 2] x1, x2 = self.ecut_i[i:i + 2]**-1.5 ex = (e1 * x2 - e2 * x1) / (x2 - x1) ex_i.append(ex) print(' %4.0f -%4.0f: %5.3f eV' % (self.ecut_i[i] * Hartree, self.ecut_i[i + 1] * Hartree, ex * Hartree), file=self.fd) print(file=self.fd) self.fd.flush() return e_i * Hartree def print_initialization(self, xc, frequency_scale, nlambda, user_spec): p = functools.partial(print, file=self.fd) p('----------------------------------------------------------') p('Non-self-consistent %s correlation energy' % xc) p('----------------------------------------------------------') p('Started at: ', ctime()) p() p('Atoms :', self.calc.atoms.get_chemical_formula(mode='hill')) p('Ground state XC functional :', self.calc.hamiltonian.xc.name) p('Valence electrons :', self.calc.wfs.setups.nvalence) p('Number of bands :', self.calc.wfs.bd.nbands) p('Number of spins :', self.calc.wfs.nspins) p('Number of k-points :', len(self.calc.wfs.kd.bzk_kc)) p('Number of irreducible k-points :', len(self.calc.wfs.kd.ibzk_kc)) p('Number of q-points :', len(self.bzq_qc)) p('Number of irreducible q-points :', len(self.ibzq_qc)) p() for q, weight in zip(self.ibzq_qc, self.weight_q): p(' q: [%1.4f %1.4f %1.4f] - weight: %1.3f' % (q[0], q[1], q[2], weight)) p() p('----------------------------------------------------------') p('----------------------------------------------------------') p() if nlambda is None: p('Analytical coupling constant integration') else: p('Numerical coupling constant integration using', nlambda, 'Gauss-Legendre points') p() p('Frequencies') if not user_spec: p(' Gauss-Legendre integration with %s frequency points' % len(self.omega_w)) p(' Transformed from [0,oo] to [0,1] using e^[-aw^(1/B)]') p(' Highest frequency point at %5.1f eV and B=%1.1f' % (self.omega_w[-1] * Hartree, frequency_scale)) else: p(' User specified frequency integration with', len(self.omega_w), 'frequency points') p() p('Parallelization') p(' Total number of CPUs : % s' % self.world.size) p(' G-vector decomposition : % s' % self.nblocks) p(' K-point/band decomposition : % s' % (self.world.size // self.nblocks)) p()