def __init__(self, print_levels=4): Timer.__init__(self, print_levels) from _gpaw import craypat_region_begin, craypat_region_end self.craypat_region_begin = craypat_region_begin self.craypat_region_end = craypat_region_end self.regions = {} self.region_id = 5 # leave room for regions in C
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 __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 __init__(self): Timer.__init__(self) import pytau self.pytau = pytau self.tau_timers = {} pytau.setNode(mpi.rank) self.tau_timers[self.top_level] = pytau.profileTimer(self.top_level) pytau.start(self.tau_timers[self.top_level])
def stop(self, name=None): if name is None: name = self.running[-1] abstime = time.time() t = self.timers[tuple(self.running)] + abstime self.txt.write('T%s << %15.8f %s (%7.5fs) stopped\n' % (self.srank, abstime, name, t)) Timer.stop(self, name)
def __init__(self, out=sys.stdout, name=None, write_as_master_only=True): Timer.__init__(self) if name is None: name = '<%s>' % sys._getframe(1).f_code.co_name self.name = name self.out = out self.alwaysprint = not write_as_master_only self.now = 'temporary now' self.start(self.now)
def start(self, name): Timer.start(self, name) if name in self.regions: id = self.regions[name] else: id = self.region_id self.regions[name] = id self.region_id += 1 self.craypat_region_begin(id, name)
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 __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 __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 __init__(self, cell_cv, response='density', comm=mpi.world, txt='-', timer=None, nblocks=1, eshift=0.0): """Baseclass for Brillouin zone integration and band summation. Simple class to calculate integrals over Brilloun zones and summation of bands. comm: mpi.communicator nblocks: block parallelization """ self.response = response self.comm = comm self.eshift = eshift self.nblocks = nblocks self.vol = abs(np.linalg.det(cell_cv)) if nblocks == 1: self.blockcomm = self.comm.new_communicator([comm.rank]) self.kncomm = comm else: assert comm.size % nblocks == 0, comm.size rank1 = comm.rank // nblocks * nblocks rank2 = rank1 + nblocks self.blockcomm = self.comm.new_communicator(range(rank1, rank2)) ranks = range(comm.rank % nblocks, comm.size, nblocks) self.kncomm = self.comm.new_communicator(ranks) if comm.rank != 0: txt = devnull self.fd = convert_string_to_fd(txt, comm) self.timer = timer or Timer()
def calculate_renormalized_kernel(pd, calc, functional, fd): """Renormalized kernel""" from gpaw.xc.fxc import KernelDens kernel = KernelDens(calc, functional, [pd.kd.bzk_kc[0]], fd, calc.wfs.kd.N_c, None, ecut=pd.ecut * Ha, tag='', timer=Timer()) kernel.calculate_fhxc() r = Reader('fhxc_%s_%s_%s_%s.gpw' % ('', functional, pd.ecut * Ha, 0)) Kxc_sGG = np.array([r.get('fhxc_sGsG')]) v_G = 4 * np.pi / pd.G2_qG[0] Kxc_sGG[0] -= np.diagflat(v_G) if pd.kd.gamma: Kxc_sGG[:, 0, :] = 0.0 Kxc_sGG[:, :, 0] = 0.0 return Kxc_sGG
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 __init__( self, atoms, # XXX do we need atoms at this stage ? *args, name='raman', exext='.alpha', txt='-', verbose=False, comm=world, **kwargs): """ Parameters ---------- atoms: ase Atoms object exext: string Extension for excitation filenames txt: Output stream verbose: Verbosity level of output comm: Communicator, default world """ kwargs['name'] = name self.exname = kwargs.pop('exname', name) super().__init__(atoms, *args, **kwargs) self.exext = exext self.timer = Timer() self.txt = convert_string_to_fd(txt) self.verbose = verbose self.comm = comm
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
def get_xc_kernel(pd, chi0, functional='ALDA', chi0_wGG=None): """Factory function that calls the relevant functions below""" calc = chi0.calc fd = chi0.fd nspins = len(calc.density.nt_sG) assert nspins == 1 if functional[0] == 'A': # Standard adiabatic kernel print('Calculating %s kernel' % functional, file=fd) Kxc_sGG = calculate_Kxc(pd, calc, functional=functional) elif functional[0] == 'r': # Renormalized kernel print('Calculating %s kernel' % functional, file=fd) from gpaw.xc.fxc import KernelDens kernel = KernelDens( calc, functional, [pd.kd.bzk_kc[0]], fd, calc.wfs.kd.N_c, None, ecut=pd.ecut * Ha, tag='', timer=Timer(), ) kernel.calculate_fhxc() r = Reader('fhxc_%s_%s_%s_%s.gpw' % ('', functional, pd.ecut * Ha, 0)) Kxc_sGG = np.array([r.get('fhxc_sGsG')]) v_G = 4 * np.pi / pd.G2_qG[0] Kxc_sGG[0] -= np.diagflat(v_G) if pd.kd.gamma: Kxc_sGG[:, 0, :] = 0.0 Kxc_sGG[:, :, 0] = 0.0 elif functional[:2] == 'LR': print('Calculating LR kernel with alpha = %s' % functional[2:], file=fd) Kxc_sGG = calculate_lr_kernel(pd, calc, alpha=float(functional[2:])) elif functional == 'DM': print('Calculating DM kernel', file=fd) Kxc_sGG = calculate_dm_kernel(pd, calc) elif functional == 'Bootstrap': print('Calculating Bootstrap kernel', file=fd) if chi0.world.rank == 0: chi0_GG = chi0_wGG[0] chi0.world.broadcast(chi0_GG, 0) else: nG = pd.ngmax chi0_GG = np.zeros((nG, nG), complex) chi0.world.broadcast(chi0_GG, 0) Kxc_sGG = calculate_bootstrap_kernel(pd, chi0_GG, fd) else: raise ValueError('%s kernel not implemented' % functional) return Kxc_sGG
def __init__(self, calculator=None, **kwargs): self.timer = Timer() self.set(**kwargs) if isinstance(calculator, str): ExcitationList.__init__(self, None, self.txt) self.filename = calculator else: ExcitationList.__init__(self, calculator, self.txt) if self.filename is not None: return self.read(self.filename) 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(eh_comm)) if calculator is not None and calculator.initialized: 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' : 'domain_only'} " err_txt += 'calculator parameter.' raise NotImplementedError(err_txt) if self.xc == 'GS': self.xc = calculator.hamiltonian.xc.name if calculator.input_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 __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 __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
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 __init__(self, lrtddft, d=0.001, txt=None, parallel=None): """Finite difference calculator for LrTDDFT. parallel: Can be used to parallelize the numerical force calculation over images """ self.timer = Timer() self.atoms = None world = mpi.world if lrtddft is not None: self.lrtddft = lrtddft self.calculator = self.lrtddft.calculator self.atoms = self.calculator.atoms if self.calculator.initialized: world = self.calculator.wfs.world if txt is None: self.txt = self.lrtddft.txt else: self.txt = get_txt(txt, world.rank) prnt('#', self.__class__.__name__, version, file=self.txt) self.d = d self.parallel = { 'world': world, 'mycomm': world, 'ncalcs': 1, 'icalc': 0} if world.size < 2: if parallel > 0: prnt('#', (self.__class__.__name__ + ':'), 'Serial calculation, keyword parallel ignored.', file=self.txt) elif parallel > 0: mycomm, ncalcs, icalc = distribute_cpus(parallel, world) if type(ncalcs) != type(1): # this is ase < r3431 ncalcs = world.size / parallel self.parallel = {'world': world, 'mycomm': mycomm, 'ncalcs': ncalcs, 'icalc': icalc} self.calculator.set(communicator=mycomm)
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)
from __future__ import print_function import sys from ase import Atoms from ase.utils.timing import Timer from gpaw import GPAW from gpaw.test import equal from gpaw.xc.hybrid import HybridXC timer = Timer() loa = Atoms('Be2', [(0, 0, 0), (2.45, 0, 0)], magmoms=[0.5, 0.5], cell=[5.9, 4.8, 5.0]) loa.center() fgl = [False, True] #fgl = [True, False] txt='-' txt='/dev/null' E = {} niter = {} for fg in fgl: if fg: tstr = 'Exx on fine grid' else: tstr = 'Exx on coarse grid'
def start(self, name): Timer.start(self, name) abstime = time.time() t = self.timers[tuple(self.running)] + abstime self.txt.write('T%s >> %15.8f %s (%7.5fs) started\n' % (self.srank, abstime, name, t))
def test_inv_speed(self): full_mat = self.recover() timer = Timer() timer.start('full_numpy') tmp0 = np.linalg.inv(full_mat) timer.stop('full_numpy') timer.start('full_lapack') inverse_general(full_mat) timer.stop('full_lapack') timer.start('sparse_lapack') self.inv_eq() timer.stop('sparse_lapack') timer.start('sparse_lapack_ne') self.inv_ne() timer.stop('sparse_lapack_ne') times = [] methods = ['full_numpy', 'full_lapack', 'sparse_lapack'] for name in methods: time = timer.timers[name,] print(name, time) times.append(time) mintime = np.min(times) self.inv_method = methods[np.argmin(times)] print('mintime', mintime) print('sparse_lapack_ne', timer.timers['sparse_lapack_ne',])
def write(self, out=sys.stdout): Timer.write(self, out) self.hpm_stop(self.top_level)
def start(self, name): Timer.start(self, name) if name in self.compatible: self.hpm_start(name)
def __init__(self, filename=None, timer=None, read_projections=True, **kwargs): """ASE-calculator interface. The following parameters can be used: nbands, xc, kpts, spinpol, gpts, h, charge, symmetry, width, mixer, hund, lmax, fixdensity, convergence, txt, parallel, communicator, dtype, softgauss and stencils. If you don't specify any parameters, you will get: Defaults: neutrally charged, LDA, gamma-point calculation, a reasonable grid-spacing, zero Kelvin electronic temperature, and the number of bands will be equal to the number of atomic orbitals present in the setups. Only occupied bands are used in the convergence decision. The calculation will be spin-polarized if and only if one or more of the atoms have non-zero magnetic moments. Text output will be written to standard output. For a non-gamma point calculation, the electronic temperature will be 0.1 eV (energies are extrapolated to zero Kelvin) and all symmetries will be used to reduce the number of **k**-points.""" PAWTextOutput.__init__(self) self.grid_descriptor_class = GridDescriptor self.input_parameters = InputParameters() if timer is None: self.timer = Timer() else: self.timer = timer self.scf = None self.forces = ForceCalculator(self.timer) self.stress_vv = None self.dipole_v = None self.magmom_av = None self.wfs = EmptyWaveFunctions() self.occupations = None self.density = None self.hamiltonian = None self.atoms = None self.iter = 0 self.initialized = False self.nbands_parallelization_adjustment = None # Somehow avoid this? # Possibly read GPAW keyword arguments from file: if filename is not None and filename.endswith('.gkw'): from gpaw.utilities.kwargs import load parameters = load(filename) parameters.update(kwargs) kwargs = parameters filename = None # XXX if filename is not None: comm = kwargs.get('communicator', mpi.world) reader = gpaw.io.open(filename, 'r', comm) self.atoms = gpaw.io.read_atoms(reader) par = self.input_parameters par.read(reader) # _changed_keywords contains those keywords that have been # changed by set() since last time initialize() was called. self._changed_keywords = set() self.set(**kwargs) # Here in the beginning, effectively every keyword has been changed. self._changed_keywords.update(self.input_parameters) if filename is not None: # Setups are not saved in the file if the setups were not loaded # *from* files in the first place if par.setups is None: if par.idiotproof: raise RuntimeError('Setups not specified in file. Use ' 'idiotproof=False to proceed anyway.') else: par.setups = {None: 'paw'} if par.basis is None: if par.idiotproof: raise RuntimeError('Basis not specified in file. Use ' 'idiotproof=False to proceed anyway.') else: par.basis = {} self.initialize() self.read(reader, read_projections) if self.hamiltonian.xc.type == 'GLLB': self.occupations.calculate(self.wfs) self.print_cell_and_parameters() self.observers = []
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 __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
def get_rpa(self): """Calculate RPA and Hartree-fock part of the A+-B matrices.""" # shorthands kss = self.fullkss finegrid = self.finegrid # calculate omega matrix nij = len(kss) print("RPAhyb", nij, "transitions", file=self.txt) AmB = np.zeros((nij, nij)) ApB = self.ApB # storage place for Coulomb integrals integrals = {} for ij in range(nij): print("RPAhyb kss[" + "%d" % ij + "]=", kss[ij], file=self.txt) timer = Timer() timer.start("init") timer2 = Timer() # smooth density including compensation charges timer2.start("with_compensation_charges 0") rhot_p = kss[ij].with_compensation_charges(finegrid is not 0) timer2.stop() # integrate with 1/|r_1-r_2| timer2.start("poisson") phit_p = np.zeros(rhot_p.shape, rhot_p.dtype) self.poisson.solve(phit_p, rhot_p, charge=None) timer2.stop() timer.stop() t0 = timer.get_time("init") timer.start(ij) if finegrid == 1: rhot = kss[ij].with_compensation_charges() phit = self.gd.zeros() self.restrict(phit_p, phit) else: phit = phit_p rhot = rhot_p for kq in range(ij, nij): if kq != ij: # smooth density including compensation charges timer2.start("kq with_compensation_charges") rhot = kss[kq].with_compensation_charges(finegrid is 2) timer2.stop() pre = self.weight_Kijkq(ij, kq) timer2.start("integrate") I = self.Coulomb_integral_kss(kss[ij], kss[kq], phit, rhot) if kss[ij].spin == kss[kq].spin: name = self.Coulomb_integral_name(kss[ij].i, kss[ij].j, kss[kq].i, kss[kq].j, kss[ij].spin) integrals[name] = I ApB[ij, kq] = pre * I timer2.stop() if ij == kq: epsij = kss[ij].get_energy() / kss[ij].get_weight() AmB[ij, kq] += epsij ApB[ij, kq] += epsij timer.stop() # timer2.write() if ij < (nij - 1): print("RPAhyb estimated time left", self.time_left(timer, t0, ij, nij), file=self.txt) # add HF parts and apply symmetry if hasattr(self.xc, "hybrid"): weight = self.xc.hybrid else: weight = 0.0 for ij in range(nij): print("HF kss[" + "%d" % ij + "]", file=self.txt) timer = Timer() timer.start("init") timer.stop() t0 = timer.get_time("init") timer.start(ij) i = kss[ij].i j = kss[ij].j s = kss[ij].spin for kq in range(ij, nij): if kss[ij].pspin == kss[kq].pspin: k = kss[kq].i q = kss[kq].j ikjq = self.Coulomb_integral_ijkq(i, k, j, q, s, integrals) iqkj = self.Coulomb_integral_ijkq(i, q, k, j, s, integrals) ApB[ij, kq] -= weight * (ikjq + iqkj) AmB[ij, kq] -= weight * (ikjq - iqkj) ApB[kq, ij] = ApB[ij, kq] AmB[kq, ij] = AmB[ij, kq] timer.stop() if ij < (nij - 1): print("HF estimated time left", self.time_left(timer, t0, ij, nij), file=self.txt) return AmB
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])
def __init__(self): Timer.__init__(self) from _gpaw import hpm_start, hpm_stop self.hpm_start = hpm_start self.hpm_stop = hpm_stop hpm_start(self.top_level)
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 """ def __init__(self, calculator=None, **kwargs): self.timer = Timer() self.set(**kwargs) if isinstance(calculator, str): ExcitationList.__init__(self, None, self.txt) self.filename = calculator else: ExcitationList.__init__(self, calculator, self.txt) if self.filename is not None: return self.read(self.filename) 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(eh_comm)) if calculator is not None and calculator.initialized: 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' : 'domain_only'} " err_txt += 'calculator parameter.' raise NotImplementedError(err_txt) if self.xc == 'GS': self.xc = calculator.hamiltonian.xc.name if calculator.input_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): defaults = { '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 } changed = False for key, value in defaults.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 = True 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, int): 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: xc = 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, txt=self.txt) self.name = name def diagonalize(self, istart=None, jend=None, energy_range=None, TDA=False): self.timer.start('diagonalize') self.timer.start('omega') self.Om.diagonalize(istart, jend, energy_range, TDA) self.timer.stop('omega') # 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""" if fh is None: if filename.endswith('.gz'): try: import gzip f = gzip.open(filename) except: f = open(filename, 'r') else: f = open(filename, 'r') self.filename = filename else: f = fh self.filename = None # get my name s = f.readline().replace('\n', '') self.name = s.split()[1] self.xc = f.readline().replace('\n', '').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 self.kss = KSSingles(filehandle=f) 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.Kss(self.kss) # check if already diagonalized p = f.tell() s = f.readline() if s != '# Eigenvalues\n': # go back to previous position f.seek(p) else: # load the eigenvalues n = int(f.readline().split()[0]) for i in range(n): self.append(LrTDDFTExcitation(string=f.readline())) # load the eigenvectors f.readline() for i in range(n): values = f.readline().split() weights = [float(val) for val in values] self[i].f = np.array(weights) self[i].kss = self.kss if fh is None: f.close() # update own variables self.istart = self.Om.fullkss.istart self.jend = self.Om.fullkss.jend 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 mpi.rank == mpi.MASTER: if fh is None: if filename.endswith('.gz'): try: import gzip f = gzip.open(filename, 'wb') except: f = open(filename, 'w') else: f = open(filename, 'w') else: f = fh f.write('# ' + self.name + '\n') xc = self.xc 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()
def stop(self, name=None): Timer.stop(self, name) if name in self.compatible: self.hpm_stop(name)
def stop(self, name=None): Timer.stop(self, name) id = self.regions[name] self.craypat_region_end(id)
def __init__(self, print_levels=1000, comm=mpi.world, txt=sys.stdout): Timer.__init__(self, print_levels) ndigits = 1 + int(math.log10(comm.size)) self.srank = '%0*d' % (ndigits, comm.rank) self.txt = txt
def start(self, name): Timer.start(self, name) self.tau_timers[name] = self.pytau.profileTimer(name) self.pytau.start(self.tau_timers[name])
def stop(self, name=None): Timer.stop(self, name) self.pytau.stop(self.tau_timers[name])
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)
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 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 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 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
def get_rpa(self): """Calculate RPA and Hartree-fock part of the A+-B matrices.""" # shorthands kss = self.fullkss finegrid = self.finegrid yukawa = hasattr(self.xc, 'rsf') and (self.xc.rsf == 'Yukawa') # calculate omega matrix nij = len(kss) print('RPAhyb', nij, 'transitions', file=self.txt) AmB = np.zeros((nij, nij)) ApB = self.ApB # storage place for Coulomb integrals integrals = {} if yukawa: rsf_integrals = {} # setup things for IVOs if self.xc.excitation is not None or self.xc.excited != 0: sin_tri_weight = 1 if self.xc.excitation is not None: ex_type = self.xc.excitation.lower() if ex_type == 'singlet': sin_tri_weight = 2 elif ex_type == 'triplet': sin_tri_weight = 0 h**o = int(self.paw.get_number_of_electrons() // 2) ivo_l = h**o - self.xc.excited - 1 else: ivo_l = None for ij in range(nij): print('RPAhyb kss[' + '%d' % ij + ']=', kss[ij], file=self.txt) timer = Timer() timer.start('init') timer2 = Timer() # smooth density including compensation charges timer2.start('with_compensation_charges 0') rhot_p = kss[ij].with_compensation_charges(finegrid != 0) timer2.stop() # integrate with 1/|r_1-r_2| timer2.start('poisson') phit_p = np.zeros(rhot_p.shape, rhot_p.dtype) self.poisson.solve(phit_p, rhot_p, charge=None) timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) if finegrid == 1: rhot = kss[ij].with_compensation_charges() phit = self.gd.zeros() self.restrict(phit_p, phit) else: phit = phit_p rhot = rhot_p for kq in range(ij, nij): if kq != ij: # smooth density including compensation charges timer2.start('kq with_compensation_charges') rhot = kss[kq].with_compensation_charges(finegrid == 2) timer2.stop() pre = self.weight_Kijkq(ij, kq) timer2.start('integrate') I = self.Coulomb_integral_kss(kss[ij], kss[kq], phit, rhot) if kss[ij].spin == kss[kq].spin: name = self.Coulomb_integral_name(kss[ij].i, kss[ij].j, kss[kq].i, kss[kq].j, kss[ij].spin) integrals[name] = I ApB[ij, kq] = pre * I timer2.stop() if ij == kq: epsij = kss[ij].get_energy() / kss[ij].get_weight() AmB[ij, kq] += epsij ApB[ij, kq] += epsij timer.stop() # timer2.write() if ij < (nij - 1): print('RPAhyb estimated time left', self.time_left(timer, t0, ij, nij), file=self.txt) # add HF parts and apply symmetry if hasattr(self.xc, 'hybrid'): weight = self.xc.hybrid else: weight = 0.0 for ij in range(nij): print('HF kss[' + '%d' % ij + ']', file=self.txt) timer = Timer() timer.start('init') timer.stop() t0 = timer.get_time('init') timer.start(ij) i = kss[ij].i j = kss[ij].j s = kss[ij].spin for kq in range(ij, nij): if kss[ij].pspin == kss[kq].pspin: k = kss[kq].i q = kss[kq].j ikjq = self.Coulomb_integral_ijkq(i, k, j, q, s, integrals) iqkj = self.Coulomb_integral_ijkq(i, q, k, j, s, integrals) if yukawa: # Yukawa integrals might be caches ikjq -= self.Coulomb_integral_ijkq( i, k, j, q, s, rsf_integrals, yukawa) iqkj -= self.Coulomb_integral_ijkq( i, q, k, j, s, rsf_integrals, yukawa) ApB[ij, kq] -= weight * (ikjq + iqkj) AmB[ij, kq] -= weight * (ikjq - iqkj) ApB[kq, ij] = ApB[ij, kq] AmB[kq, ij] = AmB[ij, kq] timer.stop() if ij < (nij - 1): print('HF estimated time left', self.time_left(timer, t0, ij, nij), file=self.txt) if ivo_l is not None: # IVO RPA after Berman, Kaldor, Chem. Phys. 43 (3) 1979 # doi: 10.1016/0301-0104(79)85205-2 l = ivo_l for ij in range(nij): i = kss[ij].i j = kss[ij].j s = kss[ij].spin for kq in range(ij, nij): if kss[kq].i == i and kss[ij].pspin == kss[kq].pspin: k = kss[kq].i q = kss[kq].j jqll = self.Coulomb_integral_ijkq( j, q, l, l, s, integrals) jllq = self.Coulomb_integral_ijkq( j, l, l, q, s, integrals) if yukawa: jqll -= self.Coulomb_integral_ijkq( j, q, l, l, s, rsf_integrals, yukawa) jllq -= self.Coulomb_integral_ijkq( j, l, l, q, s, rsf_integrals, yukawa) jllq *= sin_tri_weight ApB[ij, kq] += weight * (jqll - jllq) AmB[ij, kq] += weight * (jqll - jllq) ApB[kq, ij] = ApB[ij, kq] AmB[kq, ij] = AmB[ij, kq] return AmB
class PAW(PAWTextOutput): """This is the main calculation object for doing a PAW calculation.""" def __init__(self, filename=None, timer=None, read_projections=True, **kwargs): """ASE-calculator interface. The following parameters can be used: nbands, xc, kpts, spinpol, gpts, h, charge, symmetry, width, mixer, hund, lmax, fixdensity, convergence, txt, parallel, communicator, dtype, softgauss and stencils. If you don't specify any parameters, you will get: Defaults: neutrally charged, LDA, gamma-point calculation, a reasonable grid-spacing, zero Kelvin electronic temperature, and the number of bands will be equal to the number of atomic orbitals present in the setups. Only occupied bands are used in the convergence decision. The calculation will be spin-polarized if and only if one or more of the atoms have non-zero magnetic moments. Text output will be written to standard output. For a non-gamma point calculation, the electronic temperature will be 0.1 eV (energies are extrapolated to zero Kelvin) and all symmetries will be used to reduce the number of **k**-points.""" PAWTextOutput.__init__(self) self.grid_descriptor_class = GridDescriptor self.input_parameters = InputParameters() if timer is None: self.timer = Timer() else: self.timer = timer self.scf = None self.forces = ForceCalculator(self.timer) self.stress_vv = None self.dipole_v = None self.magmom_av = None self.wfs = EmptyWaveFunctions() self.occupations = None self.density = None self.hamiltonian = None self.atoms = None self.iter = 0 self.initialized = False self.nbands_parallelization_adjustment = None # Somehow avoid this? # Possibly read GPAW keyword arguments from file: if filename is not None and filename.endswith('.gkw'): from gpaw.utilities.kwargs import load parameters = load(filename) parameters.update(kwargs) kwargs = parameters filename = None # XXX if filename is not None: comm = kwargs.get('communicator', mpi.world) reader = gpaw.io.open(filename, 'r', comm) self.atoms = gpaw.io.read_atoms(reader) par = self.input_parameters par.read(reader) # _changed_keywords contains those keywords that have been # changed by set() since last time initialize() was called. self._changed_keywords = set() self.set(**kwargs) # Here in the beginning, effectively every keyword has been changed. self._changed_keywords.update(self.input_parameters) if filename is not None: # Setups are not saved in the file if the setups were not loaded # *from* files in the first place if par.setups is None: if par.idiotproof: raise RuntimeError('Setups not specified in file. Use ' 'idiotproof=False to proceed anyway.') else: par.setups = {None: 'paw'} if par.basis is None: if par.idiotproof: raise RuntimeError('Basis not specified in file. Use ' 'idiotproof=False to proceed anyway.') else: par.basis = {} self.initialize() self.read(reader, read_projections) if self.hamiltonian.xc.type == 'GLLB': self.occupations.calculate(self.wfs) self.print_cell_and_parameters() self.observers = [] def read(self, reader, read_projections=True): gpaw.io.read(self, reader, read_projections) def set(self, **kwargs): """Change parameters for calculator. Examples:: calc.set(xc='PBE') calc.set(nbands=20, kpts=(4, 1, 1)) """ p = self.input_parameters self._changed_keywords.update(kwargs) if (kwargs.get('h') is not None) and (kwargs.get('gpts') is not None): raise TypeError("""You can't use both "gpts" and "h"!""") if 'h' in kwargs: p['gpts'] = None if 'gpts' in kwargs: p['h'] = None # Special treatment for dictionary parameters: for name in ['convergence', 'parallel']: if kwargs.get(name) is not None: tmp = p[name] for key in kwargs[name]: if key not in tmp: raise KeyError('Unknown subparameter "%s" in ' 'dictionary parameter "%s"' % (key, name)) tmp.update(kwargs[name]) kwargs[name] = tmp self.initialized = False for key in kwargs: if key == 'basis' and str(p['mode']) == 'fd': # umm what about PW? # The second criterion seems buggy, will not touch it. -Ask continue if key == 'eigensolver': self.wfs.set_eigensolver(None) if key in ['fixmom', 'mixer', 'verbose', 'txt', 'hund', 'random', 'eigensolver', 'idiotproof', 'notify']: continue if key in ['convergence', 'fixdensity', 'maxiter']: self.scf = None continue # More drastic changes: self.scf = None self.wfs.set_orthonormalized(False) if key in ['lmax', 'width', 'stencils', 'external', 'xc', 'poissonsolver']: self.hamiltonian = None self.occupations = None elif key in ['occupations']: self.occupations = None elif key in ['charge']: self.hamiltonian = None self.density = None self.wfs = EmptyWaveFunctions() self.occupations = None elif key in ['kpts', 'nbands', 'usesymm', 'symmetry']: self.wfs = EmptyWaveFunctions() self.occupations = None elif key in ['h', 'gpts', 'setups', 'spinpol', 'realspace', 'parallel', 'communicator', 'dtype', 'mode']: self.density = None self.occupations = None self.hamiltonian = None self.wfs = EmptyWaveFunctions() elif key in ['basis']: self.wfs = EmptyWaveFunctions() elif key in ['parsize', 'parsize_bands', 'parstride_bands']: name = {'parsize': 'domain', 'parsize_bands': 'band', 'parstride_bands': 'stridebands'}[key] raise DeprecationWarning( 'Keyword argument has been moved ' + "to the 'parallel' dictionary keyword under '%s'." % name) else: raise TypeError("Unknown keyword argument: '%s'" % key) p.update(kwargs) def calculate(self, atoms=None, converge=False, force_call_to_set_positions=False): """Update PAW calculaton if needed. Returns True/False whether a calculation was performed or not.""" self.timer.start('Initialization') if atoms is None: atoms = self.atoms if self.atoms is None: # First time: self.initialize(atoms) self.set_positions(atoms) 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()): # Drastic changes: self.wfs = EmptyWaveFunctions() self.occupations = None self.density = None self.hamiltonian = None self.scf = None self.initialize(atoms) self.set_positions(atoms) elif not self.initialized: self.initialize(atoms) self.set_positions(atoms) elif (atoms.get_positions() != self.atoms.get_positions()).any(): self.density.reset() self.set_positions(atoms) elif not self.scf.converged: # Do not call scf.check_convergence() here as it overwrites # scf.converged, and setting scf.converged is the only # 'practical' way for a user to force the calculation to proceed self.set_positions(atoms) elif force_call_to_set_positions: self.set_positions(atoms) self.timer.stop('Initialization') if self.scf.converged: return False else: self.print_cell_and_parameters() self.timer.start('SCF-cycle') for iter in self.scf.run(self.wfs, self.hamiltonian, self.density, self.occupations, self.forces): self.iter = iter self.call_observers(iter) self.print_iteration(iter) self.timer.stop('SCF-cycle') if self.scf.converged: self.call_observers(iter, final=True) self.print_converged(iter) elif converge: self.txt.write(oops) raise KohnShamConvergenceError( 'Did not converge! See text output for help.') return True def initialize_positions(self, atoms=None): """Update the positions of the atoms.""" if atoms is None: atoms = self.atoms else: # Save the state of the atoms: self.atoms = atoms.copy() self.check_atoms() spos_ac = atoms.get_scaled_positions() % 1.0 self.wfs.set_positions(spos_ac) self.density.set_positions(spos_ac, self.wfs.rank_a) self.hamiltonian.set_positions(spos_ac, self.wfs.rank_a) 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) self.wfs.initialize(self.density, self.hamiltonian, spos_ac) self.wfs.eigensolver.reset() self.scf.reset() self.forces.reset() self.stress_vv = None self.dipole_v = None self.magmom_av = None self.print_positions() def initialize(self, atoms=None): """Inexpensive initialization.""" if atoms is None: atoms = self.atoms else: # Save the state of the atoms: self.atoms = atoms.copy() par = self.input_parameters world = par.communicator if world is None: world = mpi.world elif hasattr(world, 'new_communicator'): # Check for whether object has correct type already # # Using isinstance() is complicated because of all the # combinations, serial/parallel/debug... pass else: # world should be a list of ranks: world = mpi.world.new_communicator(np.asarray(world)) self.wfs.world = world if 'txt' in self._changed_keywords: self.set_txt(par.txt) self.verbose = par.verbose natoms = len(atoms) cell_cv = atoms.get_cell() / Bohr pbc_c = atoms.get_pbc() Z_a = atoms.get_atomic_numbers() magmom_av = atoms.get_initial_magnetic_moments() self.check_atoms() # 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, str): xc = XC(par.xc) else: xc = par.xc else: xc = self.hamiltonian.xc mode = par.mode if mode == 'fd': mode = FD() elif mode == 'pw': mode = pw.PW() elif mode == 'lcao': mode = LCAO() else: assert hasattr(mode, 'name'), str(mode) if xc.orbital_dependent and mode.name == 'lcao': raise NotImplementedError('LCAO mode does not support ' 'orbital-dependent XC functionals.') if par.realspace is None: realspace = (mode.name != 'pw') else: realspace = par.realspace if mode.name == 'pw': assert not realspace if par.filter is None and mode.name != 'pw': gamma = 1.6 if par.gpts is not None: h = ((np.linalg.inv(cell_cv)**2).sum(0)**-0.5 / par.gpts).max() else: h = (par.h or 0.2) / 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 = par.filter setups = Setups(Z_a, par.setups, par.basis, par.lmax, xc, filter, world) if magmom_av.ndim == 1: collinear = True magmom_av, magmom_a = np.zeros((natoms, 3)), magmom_av magmom_av[:, 2] = magmom_a else: collinear = False magnetic = magmom_av.any() spinpol = par.spinpol if par.hund: if natoms != 1: raise ValueError('hund=True arg only valid for single atoms!') spinpol = True magmom_av[0] = (0, 0, 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!') if collinear: nspins = 1 + int(spinpol) ncomp = 1 else: nspins = 1 ncomp = 2 if par.usesymm != 'default': warnings.warn('Use "symmetry" keyword instead of ' + '"usesymm" keyword') par.symmetry = usesymm2symmetry(par.usesymm) symm = par.symmetry if symm == 'off': symm = {'point_group': False, 'time_reversal': False} bzkpts_kc = kpts2ndarray(par.kpts, self.atoms) kd = KPointDescriptor(bzkpts_kc, nspins, collinear) m_av = magmom_av.round(decimals=3) # round off id_a = zip(setups.id_a, *m_av.T) symmetry = Symmetry(id_a, cell_cv, atoms.pbc, **symm) kd.set_symmetry(atoms, symmetry, comm=world) setups.set_symmetry(symmetry) if par.gpts is not None: 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) symmetry.check_grid(N_c) width = par.width if width is None: if pbc_c.any(): width = 0.1 # eV else: width = 0.0 else: assert par.occupations is None if hasattr(self, 'time') or par.dtype == complex: dtype = complex else: if kd.gamma: dtype = float else: dtype = complex nao = setups.nao nvalence = setups.nvalence - par.charge M_v = magmom_av.sum(0) M = np.dot(M_v, M_v) ** 0.5 nbands = par.nbands orbital_free = any(setup.orbital_free for setup in 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 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)) nbands *= ncomp if par.width is not None: self.text('**NOTE**: please start using ' 'occupations=FermiDirac(width).') if par.fixmom: self.text('**NOTE**: please start using ' 'occupations=FermiDirac(width, fixmagmom=True).') if self.occupations is None: if par.occupations is None: # Create object for occupation numbers: if orbital_free: width = 0.0 # even for PBC self.occupations = occupations.TFOccupations(width, par.fixmom) else: self.occupations = occupations.FermiDirac(width, par.fixmom) else: self.occupations = par.occupations # If occupation numbers are changed, and we have wave functions, # recalculate the occupation numbers if self.wfs is not None and not isinstance( self.wfs, EmptyWaveFunctions): self.occupations.calculate(self.wfs) self.occupations.magmom = M_v[2] cc = par.convergence if mode.name == 'lcao': niter_fixdensity = 0 else: niter_fixdensity = None if self.scf is None: force_crit = cc['forces'] if force_crit is not None: force_crit /= Hartree / Bohr self.scf = SCFLoop( cc['eigenstates'] / Hartree**2 * nvalence, cc['energy'] / Hartree * max(nvalence, 1), cc['density'] * nvalence, par.maxiter, par.fixdensity, niter_fixdensity, force_crit) parsize_kpt = par.parallel['kpt'] parsize_domain = par.parallel['domain'] parsize_bands = par.parallel['band'] if not realspace: pbc_c = np.ones(3, bool) if not self.wfs: if parsize_domain == 'domain only': # XXX this was silly! parsize_domain = world.size parallelization = mpi.Parallelization(world, nspins * kd.nibzkpts) ndomains = None if parsize_domain is not None: ndomains = np.prod(parsize_domain) if mode.name == 'pw': if 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 kd.set_communicator(kpt_comm) parstride_bands = par.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 # I would like to give the following error message, but apparently # there are cases, e.g. gpaw/test/gw_ppa.py, which involve # nbands > nao and are supposed to work that way. #if nbands > nao: # raise ValueError('Number of bands %d adjusted for band ' # 'parallelization %d exceeds number of atomic ' # 'orbitals %d. This problem can be fixed ' # 'by reducing the number of bands a bit.' # % (nbands, band_comm.size, nao)) bd = BandDescriptor(nbands, band_comm, parstride_bands) if (self.density is not None and self.density.gd.comm.size != domain_comm.size): # Domain decomposition has changed, so we need to # reinitialize density and hamiltonian: if par.fixdensity: raise RuntimeError( 'Density reinitialization conflict ' + 'with "fixdensity" - specify domain decomposition.') self.density = None self.hamiltonian = None # Construct grid descriptor for coarse grids for wave functions: gd = self.grid_descriptor_class(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) # do k-point analysis here? XXX args = (gd, nvalence, setups, bd, dtype, world, kd, kptband_comm, self.timer) if par.parallel['sl_auto']: # Choose scalapack parallelization automatically for key, val in par.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 = par.parallel['sl_default'] if mode.name == 'lcao': # Layouts used for general diagonalizer sl_lcao = par.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(collinear, lcaoksl, *args) elif mode.name == 'fd' or mode.name == 'pw': # buffer_size keyword only relevant for fdpw buffer_size = par.parallel['buffer_size'] # Layouts used for diagonalizer sl_diagonalize = par.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 = par.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) try: lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands) except RuntimeError: initksl = None else: # Layouts used for general diagonalizer # (LCAO initialization) sl_lcao = par.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) if hasattr(self, 'time'): assert mode.name == 'fd' from gpaw.tddft import TimeDependentWaveFunctions self.wfs = TimeDependentWaveFunctions( par.stencils[0], diagksl, orthoksl, initksl, gd, nvalence, setups, bd, world, kd, kptband_comm, self.timer) elif mode.name == 'fd': self.wfs = mode(par.stencils[0], diagksl, orthoksl, initksl, *args) else: assert mode.name == 'pw' self.wfs = mode(diagksl, orthoksl, initksl, *args) else: self.wfs = mode(self, *args) else: self.wfs.set_setups(setups) if not self.wfs.eigensolver: # Number of bands to converge: nbands_converge = cc['bands'] 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(par.eigensolver, mode, par.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) if self.density is None: gd = self.wfs.gd if par.stencils[1] != 9: # Construct grid descriptor for fine grids for densities # and potentials: finegd = gd.refine() else: # Special case (use only coarse grid): finegd = gd if realspace: self.density = RealSpaceDensity( gd, finegd, nspins, par.charge + setups.core_charge, collinear, par.stencils[1]) else: self.density = pw.ReciprocalSpaceDensity( gd, finegd, nspins, par.charge + setups.core_charge, collinear) self.density.initialize(setups, self.timer, magmom_av, par.hund) self.density.set_mixer(par.mixer) if self.hamiltonian is None: gd, finegd = self.density.gd, self.density.finegd if realspace: self.hamiltonian = RealSpaceHamiltonian( gd, finegd, nspins, setups, self.timer, xc, world, self.wfs.kptband_comm, par.external, collinear, par.poissonsolver, par.stencils[1]) else: self.hamiltonian = pw.ReciprocalSpaceHamiltonian( gd, finegd, self.density.pd2, self.density.pd3, nspins, setups, self.timer, xc, world, self.wfs.kptband_comm, par.external, collinear) xc.initialize(self.density, self.hamiltonian, self.wfs, self.occupations) self.text() self.print_memory_estimate(self.txt, maxdepth=memory_estimate_depth) self.txt.flush() 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.text) self.txt.flush() self.initialized = True self._changed_keywords.clear() def dry_run(self): # Can be overridden like in gpaw.atom.atompaw self.print_cell_and_parameters() self.print_positions() self.txt.flush() raise SystemExit def linearize_to_xc(self, newxc): """Linearize Hamiltonian to difference XC functional. Used in real time TDDFT to perform calculations with various kernels. """ if isinstance(newxc, str): newxc = XC(newxc) self.txt.write('Linearizing xc-hamiltonian to ' + str(newxc)) newxc.initialize(self.density, self.hamiltonian, self.wfs, self.occupations) self.hamiltonian.linearize_to_xc(newxc, self.density) def restore_state(self): """After restart, calculate fine density and poisson solution. These are not initialized by default. TODO: Is this really the most efficient way? """ spos_ac = self.atoms.get_scaled_positions() % 1.0 self.density.set_positions(spos_ac, self.wfs.rank_a) self.density.interpolate_pseudo_density() self.density.calculate_pseudo_charge() self.hamiltonian.set_positions(spos_ac, self.wfs.rank_a) self.hamiltonian.update(self.density) def attach(self, function, n=1, *args, **kwargs): """Register observer function. Call *function* using *args* and *kwargs* as arguments. If *n* is positive, then *function* will be called every *n* iterations + the final iteration if it would not be otherwise If *n* is negative, then *function* will only be called on iteration *abs(n)*. If *n* is 0, then *function* will only be called on convergence""" try: slf = function.im_self except AttributeError: pass else: if slf is self: # function is a bound method of self. Store the name # of the method and avoid circular reference: function = function.im_func.func_name self.observers.append((function, n, args, kwargs)) def call_observers(self, iter, final=False): """Call all registered callback functions.""" for function, n, args, kwargs in self.observers: call = False # Call every n iterations, including the last if n > 0: if ((iter % n) == 0) != final: call = True # Call only on iteration n elif n < 0 and not final: if iter == abs(n): call = True # Call only on convergence elif n == 0 and final: call = True if call: if isinstance(function, str): function = getattr(self, function) function(*args, **kwargs) def get_reference_energy(self): return self.wfs.setups.Eref * Hartree def write(self, filename, mode='', cmr_params={}, **kwargs): """Write state to file. use mode='all' to write the wave functions. cmr_params is a dictionary that allows you to specify parameters for CMR (Computational Materials Repository). """ self.timer.start('IO') gpaw.io.write(self, filename, mode, cmr_params=cmr_params, **kwargs) self.timer.stop('IO') def get_myu(self, k, s): """Return my u corresponding to a certain kpoint and spin - or None""" # very slow, but we are sure that we have it for u in range(len(self.wfs.kpt_u)): if self.wfs.kpt_u[u].k == k and self.wfs.kpt_u[u].s == s: return u return None def get_homo_lumo(self): """Return H**O and LUMO eigenvalues.""" return self.occupations.get_homo_lumo(self.wfs) * Hartree def estimate_memory(self, mem): """Estimate memory use of this object.""" for name, obj in [('Density', self.density), ('Hamiltonian', self.hamiltonian), ('Wavefunctions', self.wfs), ]: obj.estimate_memory(mem.subnode(name)) def print_memory_estimate(self, txt=None, maxdepth=-1): """Print estimated memory usage for PAW object and components. maxdepth is the maximum nesting level of displayed components. The PAW object must be initialize()'d, but needs not have large arrays allocated.""" # NOTE. This should work with --dry-run=N # # However, the initial overhead estimate is wrong if this method # is called within a real mpirun/gpaw-python context. if txt is None: txt = self.txt txt.write('Memory estimate\n') txt.write('---------------\n') mem_init = maxrss() # initial overhead includes part of Hamiltonian! txt.write('Process memory now: %.2f MiB\n' % (mem_init / 1024.0**2)) mem = MemNode('Calculator', 0) try: self.estimate_memory(mem) except AttributeError as m: txt.write('Attribute error: %r' % m) txt.write('Some object probably lacks estimate_memory() method') txt.write('Memory breakdown may be incomplete') mem.calculate_size() mem.write(txt, maxdepth=maxdepth) def converge_wave_functions(self): """Converge the wave-functions if not present.""" if not self.wfs or not self.scf: self.initialize() else: self.wfs.initialize_wave_functions_from_restart_file() spos_ac = self.atoms.get_scaled_positions() % 1.0 self.wfs.set_positions(spos_ac) no_wave_functions = (self.wfs.kpt_u[0].psit_nG is None) converged = self.scf.check_convergence(self.density, self.wfs.eigensolver, self.wfs, self.hamiltonian, self.forces) if no_wave_functions or not converged: self.wfs.eigensolver.error = np.inf self.scf.converged = False # is the density ok ? error = self.density.mixer.get_charge_sloshing() criterion = (self.input_parameters['convergence']['density'] * self.wfs.nvalence) if error < criterion and not self.hamiltonian.xc.orbital_dependent: self.scf.fix_density() self.calculate() def diagonalize_full_hamiltonian(self, nbands=None, scalapack=None, expert=False): self.wfs.diagonalize_full_hamiltonian(self.hamiltonian, self.atoms, self.occupations, self.txt, nbands, scalapack, expert) def check_atoms(self): """Check that atoms objects are identical on all processors.""" if not mpi.compare_atoms(self.atoms, comm=self.wfs.world): raise RuntimeError('Atoms objects on different processors ' + 'are not identical!')