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 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 start(self, name): Timer.start(self, name) self.tau_timers[name] = self.pytau.profileTimer(name) self.pytau.start(self.tau_timers[name])
class GPAW(Calculator, PAW): """This is the ASE-calculator frontend for doing a PAW calculation.""" implemented_properties = [ 'energy', 'forces', 'stress', 'dipole', 'magmom', 'magmoms' ] default_parameters = { 'mode': 'fd', 'xc': 'LDA', 'occupations': None, 'poissonsolver': None, 'h': None, # Angstrom 'gpts': None, 'kpts': [(0.0, 0.0, 0.0)], 'nbands': None, 'charge': 0, 'setups': {}, 'basis': {}, 'spinpol': None, 'fixdensity': False, 'filter': None, 'mixer': None, 'eigensolver': None, 'background_charge': None, 'external': None, 'random': False, 'hund': False, 'maxiter': 333, 'idiotproof': True, 'symmetry': { 'point_group': True, 'time_reversal': True, 'symmorphic': True, 'tolerance': 1e-7 }, 'convergence': { 'energy': 0.0005, # eV / electron 'density': 1.0e-4, 'eigenstates': 4.0e-8, # eV^2 'bands': 'occupied', 'forces': np.inf }, # eV / Ang 'dtype': None, # Deprecated 'width': None, # Deprecated 'verbose': 0 } default_parallel = { 'kpt': None, 'domain': gpaw.parsize_domain, 'band': gpaw.parsize_bands, 'order': 'kdb', 'stridebands': False, 'augment_grids': gpaw.augment_grids, 'sl_auto': False, 'sl_default': gpaw.sl_default, 'sl_diagonalize': gpaw.sl_diagonalize, 'sl_inverse_cholesky': gpaw.sl_inverse_cholesky, 'sl_lcao': gpaw.sl_lcao, 'sl_lrtddft': gpaw.sl_lrtddft, 'buffer_size': gpaw.buffer_size } def __init__(self, restart=None, ignore_bad_restart_file=False, label=None, atoms=None, timer=None, communicator=None, txt='-', parallel=None, **kwargs): self.parallel = dict(self.default_parallel) if parallel: self.parallel.update(parallel) if timer is None: self.timer = Timer() else: self.timer = timer self.scf = None self.wfs = None self.occupations = None self.density = None self.hamiltonian = None self.observers = [] # XXX move to self.scf self.initialized = False self.world = communicator if self.world is None: self.world = mpi.world elif not hasattr(self.world, 'new_communicator'): self.world = mpi.world.new_communicator(np.asarray(self.world)) self.log = GPAWLogger(world=self.world) self.log.fd = txt self.reader = None Calculator.__init__(self, restart, ignore_bad_restart_file, label, atoms, **kwargs) def __del__(self): self.timer.write(self.log.fd) if self.reader is not None: self.reader.close() def write(self, filename, mode=''): self.log('Writing to {0} (mode={1!r})\n'.format(filename, mode)) writer = Writer(filename, self.world) self._write(writer, mode) writer.close() self.world.barrier() def _write(self, writer, mode): from ase.io.trajectory import write_atoms writer.write(version=1, gpaw_version=gpaw.__version__, ha=Ha, bohr=Bohr) write_atoms(writer.child('atoms'), self.atoms) writer.child('results').write(**self.results) writer.child('parameters').write(**self.todict()) self.density.write(writer.child('density')) self.hamiltonian.write(writer.child('hamiltonian')) self.occupations.write(writer.child('occupations')) self.scf.write(writer.child('scf')) self.wfs.write(writer.child('wave_functions'), mode == 'all') return writer def read(self, filename): from ase.io.trajectory import read_atoms self.log('Reading from {0}'.format(filename)) self.reader = reader = Reader(filename) self.atoms = read_atoms(reader.atoms) res = reader.results self.results = dict((key, res.get(key)) for key in res.keys()) if self.results: self.log('Read {0}'.format(', '.join(sorted(self.results)))) self.log('Reading input parameters:') self.parameters = self.get_default_parameters() dct = {} for key, value in reader.parameters.asdict().items(): if (isinstance(value, dict) and isinstance(self.parameters[key], dict)): self.parameters[key].update(value) else: self.parameters[key] = value dct[key] = self.parameters[key] self.log.print_dict(dct) self.log() self.initialize(reading=True) self.density.read(reader) self.hamiltonian.read(reader) self.occupations.read(reader) self.scf.read(reader) self.wfs.read(reader) # We need to do this in a better way: XXX from gpaw.utilities.partition import AtomPartition atom_partition = AtomPartition(self.wfs.gd.comm, np.zeros(len(self.atoms), dtype=int)) self.wfs.atom_partition = atom_partition self.density.atom_partition = atom_partition self.hamiltonian.atom_partition = atom_partition spos_ac = self.atoms.get_scaled_positions() % 1.0 rank_a = self.density.gd.get_ranks_from_positions(spos_ac) new_atom_partition = AtomPartition(self.density.gd.comm, rank_a) for obj in [self.density, self.hamiltonian]: obj.set_positions_without_ruining_everything( spos_ac, new_atom_partition) self.hamiltonian.xc.read(reader) if self.hamiltonian.xc.name == 'GLLBSC': # XXX GLLB: See lcao/tdgllbsc.py test self.occupations.calculate(self.wfs) return reader def check_state(self, atoms, tol=1e-15): system_changes = Calculator.check_state(self, atoms, tol) if 'positions' not in system_changes: if self.hamiltonian: if self.hamiltonian.vext: if self.hamiltonian.vext.vext_g is None: # QMMM atoms have moved: system_changes.append('positions') return system_changes def calculate(self, atoms=None, properties=['energy'], system_changes=['cell']): """Calculate things.""" Calculator.calculate(self, atoms) atoms = self.atoms if system_changes: self.log('System changes:', ', '.join(system_changes), '\n') if system_changes == ['positions']: # Only positions have changed: self.density.reset() else: # Drastic changes: self.wfs = None self.occupations = None self.density = None self.hamiltonian = None self.scf = None self.initialize(atoms) self.set_positions(atoms) if not self.initialized: self.initialize(atoms) self.set_positions(atoms) if not (self.wfs.positions_set and self.hamiltonian.positions_set): self.set_positions(atoms) if not self.scf.converged: print_cell(self.wfs.gd, self.atoms.pbc, self.log) with self.timer('SCF-cycle'): self.scf.run(self.wfs, self.hamiltonian, self.density, self.occupations, self.log, self.call_observers) self.log('\nConverged after {0} iterations.\n'.format( self.scf.niter)) e_free = self.hamiltonian.e_total_free e_extrapolated = self.hamiltonian.e_total_extrapolated self.results['energy'] = e_extrapolated * Ha self.results['free_energy'] = e_free * Ha if not self.atoms.pbc.all(): dipole_v = self.density.calculate_dipole_moment() * Bohr self.log( 'Dipole moment: ({0:.6f}, {1:.6f}, {2:.6f}) |e|*Ang\n'. format(*dipole_v)) self.results['dipole'] = dipole_v if self.wfs.nspins == 2: magmom = self.occupations.magmom magmom_a = self.density.estimate_magnetic_moments(total=magmom) self.log('Total magnetic moment: %f' % magmom) self.log('Local magnetic moments:') symbols = self.atoms.get_chemical_symbols() for a, mom in enumerate(magmom_a): self.log('{0:4} {1:2} {2:.6f}'.format(a, symbols[a], mom)) self.log() self.results['magmom'] = self.occupations.magmom self.results['magmoms'] = magmom_a self.summary() self.call_observers(self.scf.niter, final=True) if 'forces' in properties: with self.timer('Forces'): F_av = calculate_forces(self.wfs, self.density, self.hamiltonian, self.log) self.results['forces'] = F_av * (Ha / Bohr) if 'stress' in properties: with self.timer('Stress'): try: stress = calculate_stress(self).flat[[0, 4, 8, 5, 2, 1]] except NotImplementedError: # Our ASE Calculator base class will raise # PropertyNotImplementedError for us. pass else: self.results['stress'] = stress * (Ha / Bohr**3) def summary(self): self.hamiltonian.summary(self.occupations.fermilevel, self.log) self.density.summary(self.atoms, self.occupations.magmom, self.log) self.occupations.summary(self.log) self.wfs.summary(self.log) self.log.fd.flush() def set(self, **kwargs): """Change parameters for calculator. Examples:: calc.set(xc='PBE') calc.set(nbands=20, kpts=(4, 1, 1)) """ changed_parameters = Calculator.set(self, **kwargs) for key in ['setups', 'basis']: if key in changed_parameters: dct = changed_parameters[key] if isinstance(dct, dict) and None in dct: dct['default'] = dct.pop(None) warnings.warn('Please use {key}={dct}'.format(key=key, dct=dct)) # We need to handle txt early in order to get logging up and running: if 'txt' in changed_parameters: self.log.fd = changed_parameters.pop('txt') if not changed_parameters: return {} self.initialized = False self.scf = None self.results = {} self.log('Input parameters:') self.log.print_dict(changed_parameters) self.log() for key in changed_parameters: if key in ['eigensolver', 'convergence'] and self.wfs: self.wfs.set_eigensolver(None) if key in [ 'mixer', 'verbose', 'txt', 'hund', 'random', 'eigensolver', 'idiotproof' ]: continue if key in ['convergence', 'fixdensity', 'maxiter']: continue # More drastic changes: if self.wfs: self.wfs.set_orthonormalized(False) if key in ['external', 'xc', 'poissonsolver']: self.hamiltonian = None elif key in ['occupations', 'width']: pass elif key in ['charge', 'background_charge']: self.hamiltonian = None self.density = None self.wfs = None elif key in ['kpts', 'nbands', 'symmetry']: self.wfs = None elif key in ['h', 'gpts', 'setups', 'spinpol', 'dtype', 'mode']: self.density = None self.hamiltonian = None self.wfs = None elif key in ['basis']: self.wfs = None else: raise TypeError('Unknown keyword argument: "%s"' % key) def initialize_positions(self, atoms=None): """Update the positions of the atoms.""" self.log('Initializing position-dependent things.\n') if atoms is None: atoms = self.atoms else: # Save the state of the atoms: self.atoms = atoms.copy() mpi.synchronize_atoms(atoms, self.world) spos_ac = atoms.get_scaled_positions() % 1.0 rank_a = self.wfs.gd.get_ranks_from_positions(spos_ac) atom_partition = AtomPartition(self.wfs.gd.comm, rank_a, name='gd') self.wfs.set_positions(spos_ac, atom_partition) self.density.set_positions(spos_ac, atom_partition) self.hamiltonian.set_positions(spos_ac, atom_partition) return spos_ac def set_positions(self, atoms=None): """Update the positions of the atoms and initialize wave functions.""" spos_ac = self.initialize_positions(atoms) nlcao, nrand = self.wfs.initialize(self.density, self.hamiltonian, spos_ac) if nlcao + nrand: self.log('Creating initial wave functions:') if nlcao: self.log(' ', plural(nlcao, 'band'), 'from LCAO basis set') if nrand: self.log(' ', plural(nrand, 'band'), 'from random numbers') self.log() self.wfs.eigensolver.reset() self.scf.reset() print_positions(self.atoms, self.log) def initialize(self, atoms=None, reading=False): """Inexpensive initialization.""" self.log('Initialize ...\n') if atoms is None: atoms = self.atoms else: # Save the state of the atoms: self.atoms = atoms.copy() par = self.parameters natoms = len(atoms) cell_cv = atoms.get_cell() / Bohr number_of_lattice_vectors = cell_cv.any(axis=1).sum() if number_of_lattice_vectors < 3: raise ValueError( 'GPAW requires 3 lattice vectors. Your system has {0}.'. format(number_of_lattice_vectors)) pbc_c = atoms.get_pbc() assert len(pbc_c) == 3 magmom_a = atoms.get_initial_magnetic_moments() mpi.synchronize_atoms(atoms, self.world) # Generate new xc functional only when it is reset by set # XXX sounds like this should use the _changed_keywords dictionary. if self.hamiltonian is None or self.hamiltonian.xc is None: if isinstance(par.xc, basestring): xc = XC(par.xc) else: xc = par.xc else: xc = self.hamiltonian.xc mode = par.mode if isinstance(mode, basestring): mode = {'name': mode} if isinstance(mode, dict): mode = create_wave_function_mode(**mode) if par.dtype == complex: warnings.warn('Use mode={0}(..., force_complex_dtype=True) ' 'instead of dtype=complex'.format(mode.name.upper())) mode.force_complex_dtype = True del par['dtype'] par.mode = mode if xc.orbital_dependent and mode.name == 'lcao': raise ValueError('LCAO mode does not support ' 'orbital-dependent XC functionals.') realspace = (mode.name != 'pw' and mode.interpolation != 'fft') if not realspace: pbc_c = np.ones(3, bool) self.create_setups(mode, xc) magnetic = magmom_a.any() spinpol = par.spinpol if par.hund: if natoms != 1: raise ValueError('hund=True arg only valid for single atoms!') spinpol = True magmom_a[0] = self.setups[0].get_hunds_rule_moment(par.charge) if spinpol is None: spinpol = magnetic elif magnetic and not spinpol: raise ValueError('Non-zero initial magnetic moment for a ' + 'spin-paired calculation!') nspins = 1 + int(spinpol) if spinpol: self.log('Spin-polarized calculation.') self.log('Magnetic moment: {0:.6f}\n'.format(magmom_a.sum())) else: self.log('Spin-paired calculation\n') if isinstance(par.background_charge, dict): background = create_background_charge(**par.background_charge) else: background = par.background_charge nao = self.setups.nao nvalence = self.setups.nvalence - par.charge if par.background_charge is not None: nvalence += background.charge M = abs(magmom_a.sum()) nbands = par.nbands orbital_free = any(setup.orbital_free for setup in self.setups) if orbital_free: nbands = 1 if isinstance(nbands, basestring): if nbands[-1] == '%': basebands = int(nvalence + M + 0.5) // 2 nbands = int((float(nbands[:-1]) / 100) * basebands) else: raise ValueError('Integer expected: Only use a string ' 'if giving a percentage of occupied bands') if nbands is None: nbands = 0 for setup in self.setups: nbands_from_atom = setup.get_default_nbands() # Any obscure setup errors? if nbands_from_atom < -(-setup.Nv // 2): raise ValueError('Bad setup: This setup requests %d' ' bands but has %d electrons.' % (nbands_from_atom, setup.Nv)) nbands += nbands_from_atom nbands = min(nao, nbands) elif nbands > nao and mode.name == 'lcao': raise ValueError('Too many bands for LCAO calculation: ' '%d bands and only %d atomic orbitals!' % (nbands, nao)) if nvalence < 0: raise ValueError( 'Charge %f is not possible - not enough valence electrons' % par.charge) if nbands <= 0: nbands = int(nvalence + M + 0.5) // 2 + (-nbands) if nvalence > 2 * nbands and not orbital_free: raise ValueError('Too few bands! Electrons: %f, bands: %d' % (nvalence, nbands)) self.create_occupations(magmom_a.sum(), orbital_free) if self.scf is None: self.create_scf(nvalence, mode) self.create_symmetry(magmom_a, cell_cv) if not self.wfs: self.create_wave_functions(mode, realspace, nspins, nbands, nao, nvalence, self.setups, magmom_a, cell_cv, pbc_c) else: self.wfs.set_setups(self.setups) if not self.wfs.eigensolver: self.create_eigensolver(xc, nbands, mode) if self.density is None and not reading: assert not par.fixdensity, 'No density to fix!' olddens = None if (self.density is not None and (self.density.gd.parsize_c != self.wfs.gd.parsize_c).any()): # Domain decomposition has changed, so we need to # reinitialize density and hamiltonian: if par.fixdensity: olddens = self.density self.density = None self.hamiltonian = None if self.density is None: self.create_density(realspace, mode, background) # XXXXXXXXXX if setups change, then setups.core_charge may change. # But that parameter was supplied in Density constructor! # This surely is a bug! self.density.initialize(self.setups, self.timer, magmom_a, par.hund) self.density.set_mixer(par.mixer) if self.density.mixer.driver.name == 'dummy' or par.fixdensity: self.log('No density mixing\n') else: self.log(self.density.mixer, '\n') self.density.fixed = par.fixdensity self.density.log = self.log if olddens is not None: self.density.initialize_from_other_density(olddens, self.wfs.kptband_comm) if self.hamiltonian is None: self.create_hamiltonian(realspace, mode, xc) xc.initialize(self.density, self.hamiltonian, self.wfs, self.occupations) if xc.name == 'GLLBSC' and olddens is not None: xc.heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelp(olddens) self.print_memory_estimate(maxdepth=memory_estimate_depth + 1) print_parallelization_details(self.wfs, self.density, self.log) self.log('Number of atoms:', natoms) self.log('Number of atomic orbitals:', self.wfs.setups.nao) if self.nbands_parallelization_adjustment != 0: self.log( 'Adjusting number of bands by %+d to match parallelization' % self.nbands_parallelization_adjustment) self.log('Number of bands in calculation:', self.wfs.bd.nbands) self.log('Bands to converge: ', end='') n = par.convergence.get('bands', 'occupied') if n == 'occupied': self.log('occupied states only') elif n == 'all': self.log('all') else: self.log('%d lowest bands' % n) self.log('Number of valence electrons:', self.wfs.nvalence) self.log(flush=True) self.timer.print_info(self) if dry_run: self.dry_run() if (realspace and self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT'): self.hamiltonian.poisson.set_density(self.density) self.hamiltonian.poisson.print_messages(self.log) self.log.fd.flush() self.initialized = True self.log('... initialized\n') def create_setups(self, mode, xc): if self.parameters.filter is None and mode.name != 'pw': gamma = 1.6 N_c = self.parameters.get('gpts') if N_c is None: h = (self.parameters.h or 0.2) / Bohr else: icell_vc = np.linalg.inv(self.atoms.cell) h = ((icell_vc**2).sum(0)**-0.5 / N_c).max() / Bohr def filter(rgd, rcut, f_r, l=0): gcut = np.pi / h - 2 / rcut / gamma f_r[:] = rgd.filter(f_r, rcut * gamma, gcut, l) else: filter = self.parameters.filter Z_a = self.atoms.get_atomic_numbers() self.setups = Setups(Z_a, self.parameters.setups, self.parameters.basis, xc, filter, self.world) self.log(self.setups) def create_grid_descriptor(self, N_c, cell_cv, pbc_c, domain_comm, parsize_domain): return GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) def create_occupations(self, magmom, orbital_free): occ = self.parameters.occupations if occ is None: if orbital_free: occ = {'name': 'orbital-free'} else: width = self.parameters.width if width is not None: warnings.warn( 'Please use occupations=FermiDirac({0})'.format(width)) elif self.atoms.pbc.any(): width = 0.1 # eV else: width = 0.0 occ = {'name': 'fermi-dirac', 'width': width} if isinstance(occ, dict): occ = create_occupation_number_object(**occ) if self.parameters.fixdensity: occ.fixed_fermilevel = True if self.occupations: occ.fermilevel = self.occupations.fermilevel self.occupations = occ # If occupation numbers are changed, and we have wave functions, # recalculate the occupation numbers if self.wfs is not None: self.occupations.calculate(self.wfs) self.occupations.magmom = magmom self.log(self.occupations) def create_scf(self, nvalence, mode): if mode.name == 'lcao': niter_fixdensity = 0 else: niter_fixdensity = 2 nv = max(nvalence, 1) cc = self.parameters.convergence self.scf = SCFLoop( cc.get('eigenstates', 4.0e-8) / Ha**2 * nv, cc.get('energy', 0.0005) / Ha * nv, cc.get('density', 1.0e-4) * nv, cc.get('forces', np.inf) / (Ha / Bohr), self.parameters.maxiter, niter_fixdensity, nv) self.log(self.scf) def create_symmetry(self, magmom_a, cell_cv): symm = self.parameters.symmetry if symm == 'off': symm = {'point_group': False, 'time_reversal': False} m_a = magmom_a.round(decimals=3) # round off id_a = list(zip(self.setups.id_a, m_a)) self.symmetry = Symmetry(id_a, cell_cv, self.atoms.pbc, **symm) self.symmetry.analyze(self.atoms.get_scaled_positions()) self.setups.set_symmetry(self.symmetry) def create_eigensolver(self, xc, nbands, mode): # Number of bands to converge: nbands_converge = self.parameters.convergence.get('bands', 'occupied') if nbands_converge == 'all': nbands_converge = nbands elif nbands_converge != 'occupied': assert isinstance(nbands_converge, int) if nbands_converge < 0: nbands_converge += nbands eigensolver = get_eigensolver(self.parameters.eigensolver, mode, self.parameters.convergence) eigensolver.nbands_converge = nbands_converge # XXX Eigensolver class doesn't define an nbands_converge property if isinstance(xc, SIC): eigensolver.blocksize = 1 self.wfs.set_eigensolver(eigensolver) self.log(self.wfs.eigensolver, '\n') def create_density(self, realspace, mode, background): gd = self.wfs.gd big_gd = gd.new_descriptor(comm=self.world) # Check whether grid is too small. 8 is smallest admissible. # (we decide this by how difficult it is to make the tests pass) # (Actually it depends on stencils! But let the user deal with it) N_c = big_gd.get_size_of_global_array(pad=True) too_small = np.any(N_c / big_gd.parsize_c < 8) if self.parallel['augment_grids'] and not too_small: aux_gd = big_gd else: aux_gd = gd redistributor = GridRedistributor(self.world, self.wfs.kptband_comm, gd, aux_gd) # Construct grid descriptor for fine grids for densities # and potentials: finegd = aux_gd.refine() kwargs = dict(gd=gd, finegd=finegd, nspins=self.wfs.nspins, charge=self.parameters.charge + self.wfs.setups.core_charge, redistributor=redistributor, background_charge=background) if realspace: self.density = RealSpaceDensity(stencil=mode.interpolation, **kwargs) else: self.density = pw.ReciprocalSpaceDensity(**kwargs) self.log(self.density, '\n') def create_hamiltonian(self, realspace, mode, xc): dens = self.density kwargs = dict(gd=dens.gd, finegd=dens.finegd, nspins=dens.nspins, setups=dens.setups, timer=self.timer, xc=xc, world=self.world, redistributor=dens.redistributor, vext=self.parameters.external, psolver=self.parameters.poissonsolver) if realspace: self.hamiltonian = RealSpaceHamiltonian(stencil=mode.interpolation, **kwargs) xc.set_grid_descriptor(self.hamiltonian.finegd) # XXX else: self.hamiltonian = pw.ReciprocalSpaceHamiltonian( pd2=dens.pd2, pd3=dens.pd3, realpbc_c=self.atoms.pbc, **kwargs) xc.set_grid_descriptor(dens.xc_redistributor.aux_gd) # XXX self.log(self.hamiltonian, '\n') def create_wave_functions(self, mode, realspace, nspins, nbands, nao, nvalence, setups, magmom_a, cell_cv, pbc_c): par = self.parameters bzkpts_kc = kpts2ndarray(par.kpts, self.atoms) kd = KPointDescriptor(bzkpts_kc, nspins) self.timer.start('Set symmetry') kd.set_symmetry(self.atoms, self.symmetry, comm=self.world) self.timer.stop('Set symmetry') self.log(kd) parallelization = mpi.Parallelization(self.world, nspins * kd.nibzkpts) parsize_kpt = self.parallel['kpt'] parsize_domain = self.parallel['domain'] parsize_bands = self.parallel['band'] ndomains = None if parsize_domain is not None: ndomains = np.prod(parsize_domain) if mode.name == 'pw': if ndomains is not None and ndomains > 1: raise ValueError('Planewave mode does not support ' 'domain decomposition.') ndomains = 1 parallelization.set(kpt=parsize_kpt, domain=ndomains, band=parsize_bands) comms = parallelization.build_communicators() domain_comm = comms['d'] kpt_comm = comms['k'] band_comm = comms['b'] kptband_comm = comms['D'] domainband_comm = comms['K'] self.comms = comms if par.gpts is not None: if par.h is not None: raise ValueError("""You can't use both "gpts" and "h"!""") N_c = np.array(par.gpts) else: h = par.h if h is not None: h /= Bohr N_c = get_number_of_grid_points(cell_cv, h, mode, realspace, kd.symmetry) self.symmetry.check_grid(N_c) kd.set_communicator(kpt_comm) parstride_bands = self.parallel['stridebands'] # Unfortunately we need to remember that we adjusted the # number of bands so we can print a warning if it differs # from the number specified by the user. (The number can # be inferred from the input parameters, but it's tricky # because we allow negative numbers) self.nbands_parallelization_adjustment = -nbands % band_comm.size nbands += self.nbands_parallelization_adjustment bd = BandDescriptor(nbands, band_comm, parstride_bands) # Construct grid descriptor for coarse grids for wave functions: gd = self.create_grid_descriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) if hasattr(self, 'time') or mode.force_complex_dtype: dtype = complex else: if kd.gamma: dtype = float else: dtype = complex wfs_kwargs = dict(gd=gd, nvalence=nvalence, setups=setups, bd=bd, dtype=dtype, world=self.world, kd=kd, kptband_comm=kptband_comm, timer=self.timer) if self.parallel['sl_auto']: # Choose scalapack parallelization automatically for key, val in self.parallel.items(): if (key.startswith('sl_') and key != 'sl_auto' and val is not None): raise ValueError("Cannot use 'sl_auto' together " "with '%s'" % key) max_scalapack_cpus = bd.comm.size * gd.comm.size nprow = max_scalapack_cpus npcol = 1 # Get a sort of reasonable number of columns/rows while npcol < nprow and nprow % 2 == 0: npcol *= 2 nprow //= 2 assert npcol * nprow == max_scalapack_cpus # ScaLAPACK creates trouble if there aren't at least a few # whole blocks; choose block size so there will always be # several blocks. This will crash for small test systems, # but so will ScaLAPACK in any case blocksize = min(-(-nbands // 4), 64) sl_default = (nprow, npcol, blocksize) else: sl_default = self.parallel['sl_default'] if mode.name == 'lcao': # Layouts used for general diagonalizer sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, bd, domainband_comm, dtype, nao=nao, timer=self.timer) self.wfs = mode(lcaoksl, **wfs_kwargs) elif mode.name == 'fd' or mode.name == 'pw': # buffer_size keyword only relevant for fdpw buffer_size = self.parallel['buffer_size'] # Layouts used for diagonalizer sl_diagonalize = self.parallel['sl_diagonalize'] if sl_diagonalize is None: sl_diagonalize = sl_default diagksl = get_KohnSham_layouts( sl_diagonalize, 'fd', # XXX # choice of key 'fd' not so nice gd, bd, domainband_comm, dtype, buffer_size=buffer_size, timer=self.timer) # Layouts used for orthonormalizer sl_inverse_cholesky = self.parallel['sl_inverse_cholesky'] if sl_inverse_cholesky is None: sl_inverse_cholesky = sl_default if sl_inverse_cholesky != sl_diagonalize: message = 'sl_inverse_cholesky != sl_diagonalize ' \ 'is not implemented.' raise NotImplementedError(message) orthoksl = get_KohnSham_layouts(sl_inverse_cholesky, 'fd', gd, bd, domainband_comm, dtype, buffer_size=buffer_size, timer=self.timer) # Use (at most) all available LCAO for initialization lcaonbands = min(nbands, nao // band_comm.size * band_comm.size) try: lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands) except RuntimeError: initksl = None else: # Layouts used for general diagonalizer # (LCAO initialization) sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default initksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, lcaobd, domainband_comm, dtype, nao=nao, timer=self.timer) self.wfs = mode(diagksl, orthoksl, initksl, **wfs_kwargs) else: self.wfs = mode(self, **wfs_kwargs) self.log(self.wfs, '\n') def dry_run(self): # Can be overridden like in gpaw.atom.atompaw print_cell(self.wfs.gd, self.atoms.pbc, self.log) print_positions(self.atoms, self.log) self.log.fd.flush() raise SystemExit
class LrTDDFT(ExcitationList): """Linear Response TDDFT excitation class Input parameters: calculator: the calculator object after a ground state calculation nspins: number of spins considered in the calculation Note: Valid only for unpolarised ground state calculation eps: Minimal occupation difference for a transition (default 0.001) istart: First occupied state to consider jend: Last unoccupied state to consider xc: Exchange-Correlation approximation in the Kernel derivative_level: 0: use Exc, 1: use vxc, 2: use fxc if available filename: read from a file """ default_parameters = { 'nspins': None, 'eps': 0.001, 'istart': 0, 'jend': sys.maxsize, 'energy_range': None, 'xc': 'GS', 'derivative_level': 1, 'numscale': 0.00001, 'txt': None, 'filename': None, 'finegrid': 2, 'force_ApmB': False, # for tests 'eh_comm': None} # parallelization over eh-pairs def __init__(self, calculator=None, **kwargs): self.timer = Timer() self.diagonalized = False changed = 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: 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: 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.name if calculator.parameters.mode != 'lcao': calculator.converge_wave_functions() if calculator.density.nct_G is None: spos_ac = calculator.initialize_positions() calculator.wfs.initialize(calculator.density, calculator.hamiltonian, spos_ac) self.update(calculator) def set(self, **kwargs): """Change parameters.""" changed = [] for key, value in LrTDDFT.default_parameters.items(): if hasattr(self, key): value = getattr(self, key) # do not overwrite setattr(self, key, kwargs.pop(key, value)) if value != getattr(self, key): changed.append(key) for key in kwargs: raise KeyError('Unknown key ' + key) return changed def set_calculator(self, calculator): self.calculator = calculator # self.force_ApmB = parameters['force_ApmB'] self.force_ApmB = None # XXX def analyse(self, what=None, out=None, min=0.1): """Print info about the transitions. Parameters: 1. what: I list of excitation indicees, None means all 2. out : I where to send the output, None means sys.stdout 3. min : I minimal contribution to list (0<min<1) """ if what is None: what = range(len(self)) elif isinstance(what, numbers.Integral): what = [what] if out is None: out = sys.stdout for i in what: print(str(i) + ':', self[i].analyse(min=min), file=out) def update(self, calculator=None, **kwargs): changed = self.set(**kwargs) if calculator is not None: changed = True self.set_calculator(calculator) if not changed: return self.forced_update() def forced_update(self): """Recalc yourself.""" if not self.force_ApmB: Om = OmegaMatrix name = 'LrTDDFT' if self.xc: 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, **kwargs): self.set(**kwargs) self.timer.start('diagonalize') self.timer.start('omega') self.Om.diagonalize(self.istart, self.jend, self.energy_range) self.timer.stop('omega') self.diagonalized = True # remove old stuff self.timer.start('clean') while len(self): self.pop() self.timer.stop('clean') print('LrTDDFT digonalized:', file=self.txt) self.timer.start('build') for j in range(len(self.Om.kss)): self.append(LrTDDFTExcitation(self.Om, j)) print(' ', str(self[-1]), file=self.txt) self.timer.stop('build') self.timer.stop('diagonalize') def get_Om(self): return self.Om def read(self, filename=None, fh=None): """Read myself from a file""" if fh is None: if filename.endswith('.gz'): try: import gzip f = gzip.open(filename, 'rt') except: f = open(filename, 'r') else: f = open(filename, 'r') self.filename = filename else: f = fh self.filename = None # 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: self.diagonalized = True # load the eigenvalues n = int(f.readline().split()[0]) for i in range(n): self.append(LrTDDFTExcitation(string=f.readline())) # load the eigenvectors 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() def singlets_triplets(self): """Split yourself into a singlet and triplet object""" slr = LrTDDFT(None, nspins=self.nspins, eps=self.eps, istart=self.istart, jend=self.jend, xc=self.xc, derivative_level=self.derivative_level, numscale=self.numscale) tlr = LrTDDFT(None, nspins=self.nspins, eps=self.eps, istart=self.istart, jend=self.jend, xc=self.xc, derivative_level=self.derivative_level, numscale=self.numscale) slr.Om, tlr.Om = self.Om.singlets_triplets() for lr in [slr, tlr]: lr.kss = lr.Om.fullkss return slr, tlr def single_pole_approximation(self, i, j): """Return the excitation according to the single pole approximation. See e.g.: Grabo et al, Theochem 501 (2000) 353-367 """ for ij, kss in enumerate(self.kss): if kss.i == i and kss.j == j: return sqrt(self.Om.full[ij][ij]) * Hartree return self.Om.full[ij][ij] / kss.energy * Hartree def __str__(self): string = ExcitationList.__str__(self) string += '# derived from:\n' string += self.Om.kss.__str__() return string def write(self, filename=None, fh=None): """Write current state to a file. 'filename' is the filename. If the filename ends in .gz, the file is automatically saved in compressed gzip format. 'fh' is a filehandle. This can be used to write into already opened files. """ if self.calculator is None: rank = mpi.world.rank else: rank = self.calculator.wfs.world.rank if rank == 0: if fh is None: if filename.endswith('.gz'): try: import gzip f = gzip.open(filename, 'wt') except: f = open(filename, 'w') else: f = open(filename, 'w') else: f = fh f.write('# ' + self.name + '\n') 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() mpi.world.barrier() def __getitem__(self, i): if not self.diagonalized: self.diagonalize() return list.__getitem__(self, i) def __iter__(self): if not self.diagonalized: self.diagonalize() return list.__iter__(self) def __len__(self): if not self.diagonalized: self.diagonalize() return list.__len__(self)
def 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
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()
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 start(self, name): Timer.start(self, name) if name in self.compatible: self.hpm_start(name)
class ResonantRaman(Vibrations): """Class for calculating vibrational modes and resonant Raman intensities using finite difference. atoms: Atoms object Excitations: Class to calculate the excitations. The class object is initialized as:: Excitations(atoms.get_calculator()) or by reading form a file as:: Excitations('filename', **exkwargs) The file is written by calling the method Excitations.write('filename'). """ def __init__( self, atoms, Excitations, indices=None, gsname='rraman', # name for ground state calculations exname=None, # name for excited state calculations delta=0.01, nfree=2, directions=None, exkwargs={}, # kwargs to be passed to Excitations exext='.ex.gz', # extension for Excitation names txt='-', verbose=False): assert (nfree == 2) Vibrations.__init__(self, atoms, indices, gsname, delta, nfree) self.name = gsname + '-d%.3f' % delta if exname is None: exname = gsname self.exname = exname + '-d%.3f' % delta self.exext = exext if directions is None: self.directions = np.array([0, 1, 2]) else: self.directions = np.array(directions) self.exobj = Excitations self.exkwargs = exkwargs self.timer = Timer() self.txt = get_txt(txt, rank) self.verbose = verbose def log(self, message, pre='# ', end='\n'): if self.verbose: self.txt.write(pre + message + end) self.txt.flush() def calculate(self, filename, fd): """Call ground and excited state calculation""" self.timer.start('Ground state') forces = self.atoms.get_forces() if rank == 0: pickle.dump(forces, fd) fd.close() self.timer.stop('Ground state') self.timer.start('Excitations') basename, _ = os.path.splitext(filename) excitations = self.exobj(self.atoms.get_calculator()) excitations.write(basename + self.exext) self.timer.stop('Excitations') def get_intensity_tensor(self, omega, gamma=0.1): if not hasattr(self, 'modes'): self.read() if not hasattr(self, 'ex0'): eu = units.Hartree def get_me_tensor(exname, n, form='v'): def outer(ex): me = ex.get_dipole_me(form=form) return np.outer(me, me.conj()) self.log('reading ' + exname) ex_p = self.exobj(exname, **self.exkwargs) if len(ex_p) != n: raise RuntimeError( ('excitations {0} of wrong length: {1} != {2}' + ' exkwargs={3}').format(exname, len(ex_p), n, self.exkwargs)) m_ccp = np.empty((3, 3, len(ex_p)), dtype=complex) for p, ex in enumerate(ex_p): m_ccp[:, :, p] = outer(ex) return m_ccp self.timer.start('reading excitations') self.log('reading ' + self.exname + '.eq' + self.exext) ex_p = self.exobj(self.exname + '.eq' + self.exext, **self.exkwargs) n = len(ex_p) self.ex0 = np.array([ex.energy * eu for ex in ex_p]) self.exminus = [] self.explus = [] for a in self.indices: for i in 'xyz': name = '%s.%d%s' % (self.exname, a, i) self.exminus.append( get_me_tensor(name + '-' + self.exext, n)) self.explus.append( get_me_tensor(name + '+' + self.exext, n)) self.timer.stop('reading excitations') self.timer.start('amplitudes') self.timer.start('init') ndof = 3 * len(self.indices) amplitudes = np.zeros((ndof, 3, 3), dtype=complex) pre = 1. / (2 * self.delta) self.timer.stop('init') def kappa(me_ccp, e_p, omega, gamma, form='v'): """Kappa tensor after Profeta and Mauri PRB 63 (2001) 245415""" result = (me_ccp / (e_p - omega - 1j * gamma) + me_ccp.conj() / (e_p + omega + 1j * gamma)) return result.sum(2) r = 0 for a in self.indices: for i in 'xyz': amplitudes[r] = pre * ( kappa(self.explus[r], self.ex0, omega, gamma) - kappa(self.exminus[r], self.ex0, omega, gamma)) r += 1 self.timer.stop('amplitudes') # map to modes am = np.dot(amplitudes.T, self.modes.T).T return omega**4 * (am * am.conj()).real def get_intensities(self, omega, gamma=0.1): return self.get_intensity_tensor(omega, gamma).sum(axis=1).sum(axis=1) def get_spectrum(self, omega, gamma=0.1, start=200.0, end=4000.0, npts=None, width=4.0, type='Gaussian', method='standard', direction='central', intensity_unit='????', normalize=False): """Get resonant Raman spectrum. The method returns wavenumbers in cm^-1 with corresponding absolute infrared intensity. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1. normalize=True ensures the integral over the peaks to give the intensity. """ self.type = type.lower() assert self.type in ['gaussian', 'lorentzian'] if not npts: npts = int((end - start) / width * 10 + 1) frequencies = self.get_frequencies(method, direction).real intensities = self.get_intensities(omega, gamma) prefactor = 1 if type == 'lorentzian': intensities = intensities * width * np.pi / 2. if normalize: prefactor = 2. / width / np.pi else: sigma = width / 2. / np.sqrt(2. * np.log(2.)) if normalize: prefactor = 1. / sigma / np.sqrt(2 * np.pi) # Make array with spectrum data spectrum = np.empty(npts) energies = np.linspace(start, end, npts) for i, energy in enumerate(energies): energies[i] = energy if type == 'lorentzian': spectrum[i] = (intensities * 0.5 * width / np.pi / ( (frequencies - energy)**2 + 0.25 * width**2)).sum() else: spectrum[i] = ( intensities * np.exp(-(frequencies - energy)**2 / 2. / sigma**2)).sum() return [energies, prefactor * spectrum] def write_spectrum(self, omega, gamma, out='resonant-raman-spectra.dat', start=200, end=4000, npts=None, width=10, type='Gaussian', method='standard', direction='central'): """Write out spectrum to file. First column is the wavenumber in cm^-1, the second column the absolute infrared intensities, and the third column the absorbance scaled so that data runs from 1 to 0. Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1.""" energies, spectrum = self.get_spectrum(omega, gamma, start, end, npts, width, type, method, direction) # Write out spectrum in file. First column is absolute intensities. outdata = np.empty([len(energies), 3]) outdata.T[0] = energies outdata.T[1] = spectrum fd = open(out, 'w') fd.write('# Resonant Raman spectrum\n') fd.write('# omega={0:g} eV, gamma={1:g} eV\n'.format(omega, gamma)) fd.write('# %s folded, width=%g cm^-1\n' % (type.title(), width)) fd.write('# [cm^-1] [a.u.]\n') for row in outdata: fd.write('%.3f %15.5g\n' % (row[0], row[1])) fd.close() def summary(self, omega, gamma=0.1, method='standard', direction='central', intensity_unit='(D/A)2/amu', log=sys.stdout): """Print summary for given omega [eV]""" hnu = self.get_energies(method, direction) s = 0.01 * units._e / units._c / units._hplanck intensities = self.get_intensities(omega, gamma) if isinstance(log, str): log = paropen(log, 'a') parprint('-------------------------------------', file=log) parprint(' excitation at ' + str(omega) + ' eV', file=log) parprint(' gamma ' + str(gamma) + ' eV\n', file=log) parprint(' Mode Frequency Intensity', file=log) parprint(' # meV cm^-1 [a.u.]', file=log) parprint('-------------------------------------', file=log) for n, e in enumerate(hnu): if e.imag != 0: c = 'i' e = e.imag else: c = ' ' e = e.real parprint('%3d %6.1f%s %7.1f%s %9.3g' % (n, 1000 * e, c, s * e, c, intensities[n]), file=log) parprint('-------------------------------------', file=log) parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy(), file=log) def __del__(self): self.timer.write(self.txt)
class GPAW(PAW, Calculator): """This is the ASE-calculator frontend for doing a PAW calculation.""" implemented_properties = [ 'energy', 'forces', 'stress', 'dipole', 'magmom', 'magmoms' ] default_parameters = { 'mode': 'fd', 'xc': 'LDA', 'occupations': None, 'poissonsolver': None, 'h': None, # Angstrom 'gpts': None, 'kpts': [(0.0, 0.0, 0.0)], 'nbands': None, 'charge': 0, 'setups': {}, 'basis': {}, 'spinpol': None, 'fixdensity': False, 'filter': None, 'mixer': None, 'eigensolver': None, 'background_charge': None, 'experimental': { 'reuse_wfs_method': 'paw', 'niter_fixdensity': 0, 'magmoms': None, 'soc': None, 'kpt_refine': None }, 'external': None, 'random': False, 'hund': False, 'maxiter': 333, 'idiotproof': True, 'symmetry': { 'point_group': True, 'time_reversal': True, 'symmorphic': True, 'tolerance': 1e-7, 'do_not_symmetrize_the_density': False }, 'convergence': { 'energy': 0.0005, # eV / electron 'density': 1.0e-4, 'eigenstates': 4.0e-8, # eV^2 'bands': 'occupied', 'forces': np.inf }, # eV / Ang 'dtype': None, # Deprecated 'width': None, # Deprecated 'verbose': 0 } default_parallel = { 'kpt': None, 'domain': gpaw.parsize_domain, 'band': gpaw.parsize_bands, 'order': 'kdb', 'stridebands': False, 'augment_grids': gpaw.augment_grids, 'sl_auto': False, 'sl_default': gpaw.sl_default, 'sl_diagonalize': gpaw.sl_diagonalize, 'sl_inverse_cholesky': gpaw.sl_inverse_cholesky, 'sl_lcao': gpaw.sl_lcao, 'sl_lrtddft': gpaw.sl_lrtddft, 'use_elpa': False, 'elpasolver': '2stage', 'buffer_size': gpaw.buffer_size } def __init__(self, restart=None, ignore_bad_restart_file=False, label=None, atoms=None, timer=None, communicator=None, txt='-', parallel=None, **kwargs): self.parallel = dict(self.default_parallel) if parallel: for key in parallel: if key not in self.default_parallel: allowed = ', '.join(list(self.default_parallel.keys())) raise TypeError('Unexpected keyword "{}" in "parallel" ' 'dictionary. Must be one of: {}'.format( key, allowed)) self.parallel.update(parallel) if timer is None: self.timer = Timer() else: self.timer = timer self.scf = None self.wfs = None self.occupations = None self.density = None self.hamiltonian = None self.spos_ac = None # XXX store this in some better way. self.observers = [] # XXX move to self.scf self.initialized = False self.world = communicator if self.world is None: self.world = mpi.world elif not hasattr(self.world, 'new_communicator'): self.world = mpi.world.new_communicator(np.asarray(self.world)) self.log = GPAWLogger(world=self.world) self.log.fd = txt self.reader = None Calculator.__init__(self, restart, ignore_bad_restart_file, label, atoms, **kwargs) def __del__(self): # Write timings and close reader if necessary. # If we crashed in the constructor (e.g. a bad keyword), we may not # have the normally expected attributes: if hasattr(self, 'timer'): self.timer.write(self.log.fd) if hasattr(self, 'reader') and self.reader is not None: self.reader.close() def write(self, filename, mode=''): self.log('Writing to {} (mode={!r})\n'.format(filename, mode)) writer = Writer(filename, self.world) self._write(writer, mode) writer.close() self.world.barrier() def _write(self, writer, mode): from ase.io.trajectory import write_atoms writer.write(version=1, gpaw_version=gpaw.__version__, ha=Ha, bohr=Bohr) write_atoms(writer.child('atoms'), self.atoms) writer.child('results').write(**self.results) writer.child('parameters').write(**self.todict()) self.density.write(writer.child('density')) self.hamiltonian.write(writer.child('hamiltonian')) self.occupations.write(writer.child('occupations')) self.scf.write(writer.child('scf')) self.wfs.write(writer.child('wave_functions'), mode == 'all') return writer def _set_atoms(self, atoms): check_atoms_too_close(atoms) self.atoms = atoms # GPAW works in terms of the scaled positions. We want to # extract the scaled positions in only one place, and that is # here. No other place may recalculate them, or we might end up # with rounding errors and inconsistencies. self.spos_ac = atoms.get_scaled_positions() % 1.0 def read(self, filename): from ase.io.trajectory import read_atoms self.log('Reading from {}'.format(filename)) self.reader = reader = Reader(filename) atoms = read_atoms(reader.atoms) self._set_atoms(atoms) res = reader.results self.results = dict((key, res.get(key)) for key in res.keys()) if self.results: self.log('Read {}'.format(', '.join(sorted(self.results)))) self.log('Reading input parameters:') # XXX param self.parameters = self.get_default_parameters() dct = {} for key, value in reader.parameters.asdict().items(): if (isinstance(value, dict) and isinstance(self.parameters[key], dict)): self.parameters[key].update(value) else: self.parameters[key] = value dct[key] = self.parameters[key] self.log.print_dict(dct) self.log() self.initialize(reading=True) self.density.read(reader) self.hamiltonian.read(reader) self.occupations.read(reader) self.scf.read(reader) self.wfs.read(reader) # We need to do this in a better way: XXX from gpaw.utilities.partition import AtomPartition atom_partition = AtomPartition(self.wfs.gd.comm, np.zeros(len(self.atoms), dtype=int)) self.wfs.atom_partition = atom_partition self.density.atom_partition = atom_partition self.hamiltonian.atom_partition = atom_partition rank_a = self.density.gd.get_ranks_from_positions(self.spos_ac) new_atom_partition = AtomPartition(self.density.gd.comm, rank_a) for obj in [self.density, self.hamiltonian]: obj.set_positions_without_ruining_everything( self.spos_ac, new_atom_partition) self.hamiltonian.xc.read(reader) if self.hamiltonian.xc.name == 'GLLBSC': # XXX GLLB: See test/lcaotddft/gllbsc.py self.occupations.calculate(self.wfs) return reader def check_state(self, atoms, tol=1e-15): system_changes = Calculator.check_state(self, atoms, tol) if 'positions' not in system_changes: if self.hamiltonian: if self.hamiltonian.vext: if self.hamiltonian.vext.vext_g is None: # QMMM atoms have moved: system_changes.append('positions') return system_changes def calculate(self, atoms=None, properties=['energy'], system_changes=['cell']): """Calculate things.""" Calculator.calculate(self, atoms) atoms = self.atoms if system_changes: self.log('System changes:', ', '.join(system_changes), '\n') if system_changes == ['positions']: # Only positions have changed: self.density.reset() else: # Drastic changes: self.wfs = None self.occupations = None self.density = None self.hamiltonian = None self.scf = None self.initialize(atoms) self.set_positions(atoms) if not self.initialized: self.initialize(atoms) self.set_positions(atoms) if not (self.wfs.positions_set and self.hamiltonian.positions_set): self.set_positions(atoms) if not self.scf.converged: print_cell(self.wfs.gd, self.atoms.pbc, self.log) with self.timer('SCF-cycle'): self.scf.run(self.wfs, self.hamiltonian, self.density, self.occupations, self.log, self.call_observers) self.log('\nConverged after {} iterations.\n'.format( self.scf.niter)) e_free = self.hamiltonian.e_total_free e_extrapolated = self.hamiltonian.e_total_extrapolated self.results['energy'] = e_extrapolated * Ha self.results['free_energy'] = e_free * Ha dipole_v = self.density.calculate_dipole_moment() * Bohr self.log( 'Dipole moment: ({:.6f}, {:.6f}, {:.6f}) |e|*Ang\n'.format( *dipole_v)) self.results['dipole'] = dipole_v if self.wfs.nspins == 2 or not self.density.collinear: totmom_v, magmom_av = self.density.estimate_magnetic_moments() self.log( 'Total magnetic moment: ({:.6f}, {:.6f}, {:.6f})'.format( *totmom_v)) self.log('Local magnetic moments:') symbols = self.atoms.get_chemical_symbols() for a, mom_v in enumerate(magmom_av): self.log('{:4} {:2} ({:9.6f}, {:9.6f}, {:9.6f})'.format( a, symbols[a], *mom_v)) self.log() self.results['magmom'] = self.occupations.magmom self.results['magmoms'] = magmom_av[:, 2].copy() self.summary() self.call_observers(self.scf.niter, final=True) if 'forces' in properties: with self.timer('Forces'): F_av = calculate_forces(self.wfs, self.density, self.hamiltonian, self.log) self.results['forces'] = F_av * (Ha / Bohr) if 'stress' in properties: with self.timer('Stress'): try: stress = calculate_stress(self).flat[[0, 4, 8, 5, 2, 1]] except NotImplementedError: # Our ASE Calculator base class will raise # PropertyNotImplementedError for us. pass else: self.results['stress'] = stress * (Ha / Bohr**3) def summary(self): efermi = self.occupations.fermilevel self.hamiltonian.summary(efermi, self.log) self.density.summary(self.atoms, self.occupations.magmom, self.log) self.occupations.summary(self.log) self.wfs.summary(self.log) try: bandgap(self, output=self.log.fd, efermi=efermi * Ha) except ValueError: pass self.log.fd.flush() def set(self, **kwargs): """Change parameters for calculator. Examples:: calc.set(xc='PBE') calc.set(nbands=20, kpts=(4, 1, 1)) """ # Verify that keys are consistent with default ones. for key in kwargs: if key != 'txt' and key not in self.default_parameters: raise TypeError('Unknown GPAW parameter: {}'.format(key)) if key in ['convergence', 'symmetry', 'experimental' ] and isinstance(kwargs[key], dict): # For values that are dictionaries, verify subkeys, too. default_dict = self.default_parameters[key] for subkey in kwargs[key]: if subkey not in default_dict: allowed = ', '.join(list(default_dict.keys())) raise TypeError('Unknown subkeyword "{}" of keyword ' '"{}". Must be one of: {}'.format( subkey, key, allowed)) changed_parameters = Calculator.set(self, **kwargs) for key in ['setups', 'basis']: if key in changed_parameters: dct = changed_parameters[key] if isinstance(dct, dict) and None in dct: dct['default'] = dct.pop(None) warnings.warn('Please use {key}={dct}'.format(key=key, dct=dct)) # We need to handle txt early in order to get logging up and running: if 'txt' in changed_parameters: self.log.fd = changed_parameters.pop('txt') if not changed_parameters: return {} self.initialized = False self.scf = None self.results = {} self.log('Input parameters:') self.log.print_dict(changed_parameters) self.log() for key in changed_parameters: if key in ['eigensolver', 'convergence'] and self.wfs: self.wfs.set_eigensolver(None) if key in [ 'mixer', 'verbose', 'txt', 'hund', 'random', 'eigensolver', 'idiotproof' ]: continue if key in ['convergence', 'fixdensity', 'maxiter']: continue # Check nested arguments if key in ['experimental']: changed_parameters2 = changed_parameters[key] for key2 in changed_parameters2: if key2 in ['kpt_refine', 'magmoms', 'soc']: self.wfs = None elif key2 in ['reuse_wfs_method', 'niter_fixdensity']: continue else: raise TypeError('Unknown keyword argument:', key2) continue # More drastic changes: if self.wfs: self.wfs.set_orthonormalized(False) if key in ['external', 'xc', 'poissonsolver']: self.hamiltonian = None elif key in ['occupations', 'width']: pass elif key in ['charge', 'background_charge']: self.hamiltonian = None self.density = None self.wfs = None elif key in ['kpts', 'nbands', 'symmetry']: self.wfs = None elif key in ['h', 'gpts', 'setups', 'spinpol', 'dtype', 'mode']: self.density = None self.hamiltonian = None self.wfs = None elif key in ['basis']: self.wfs = None else: raise TypeError('Unknown keyword argument: "%s"' % key) def initialize_positions(self, atoms=None): """Update the positions of the atoms.""" self.log('Initializing position-dependent things.\n') if atoms is None: atoms = self.atoms else: atoms = atoms.copy() self._set_atoms(atoms) mpi.synchronize_atoms(atoms, self.world) rank_a = self.wfs.gd.get_ranks_from_positions(self.spos_ac) atom_partition = AtomPartition(self.wfs.gd.comm, rank_a, name='gd') self.wfs.set_positions(self.spos_ac, atom_partition) self.density.set_positions(self.spos_ac, atom_partition) self.hamiltonian.set_positions(self.spos_ac, atom_partition) def set_positions(self, atoms=None): """Update the positions of the atoms and initialize wave functions.""" self.initialize_positions(atoms) nlcao, nrand = self.wfs.initialize(self.density, self.hamiltonian, self.spos_ac) if nlcao + nrand: self.log('Creating initial wave functions:') if nlcao: self.log(' ', plural(nlcao, 'band'), 'from LCAO basis set') if nrand: self.log(' ', plural(nrand, 'band'), 'from random numbers') self.log() self.wfs.eigensolver.reset() self.scf.reset() print_positions(self.atoms, self.log, self.density.magmom_av) def initialize(self, atoms=None, reading=False): """Inexpensive initialization.""" self.log('Initialize ...\n') if atoms is None: atoms = self.atoms else: atoms = atoms.copy() self._set_atoms(atoms) par = self.parameters natoms = len(atoms) cell_cv = atoms.get_cell() / Bohr number_of_lattice_vectors = cell_cv.any(axis=1).sum() if number_of_lattice_vectors < 3: raise ValueError( 'GPAW requires 3 lattice vectors. Your system has {}.'.format( number_of_lattice_vectors)) pbc_c = atoms.get_pbc() assert len(pbc_c) == 3 magmom_a = atoms.get_initial_magnetic_moments() if par.experimental.get('magmoms') is not None: magmom_av = np.array(par.experimental['magmoms'], float) collinear = False else: magmom_av = np.zeros((natoms, 3)) magmom_av[:, 2] = magmom_a collinear = True mpi.synchronize_atoms(atoms, self.world) # Generate new xc functional only when it is reset by set # XXX sounds like this should use the _changed_keywords dictionary. if self.hamiltonian is None or self.hamiltonian.xc is None: if isinstance(par.xc, (basestring, dict)): xc = XC(par.xc, collinear=collinear, atoms=atoms) else: xc = par.xc else: xc = self.hamiltonian.xc mode = par.mode if isinstance(mode, basestring): mode = {'name': mode} if isinstance(mode, dict): mode = create_wave_function_mode(**mode) if par.dtype == complex: warnings.warn('Use mode={}(..., force_complex_dtype=True) ' 'instead of dtype=complex'.format(mode.name.upper())) mode.force_complex_dtype = True del par['dtype'] par.mode = mode if xc.orbital_dependent and mode.name == 'lcao': raise ValueError('LCAO mode does not support ' 'orbital-dependent XC functionals.') realspace = (mode.name != 'pw' and mode.interpolation != 'fft') if not realspace: pbc_c = np.ones(3, bool) self.create_setups(mode, xc) if par.hund: if natoms != 1: raise ValueError('hund=True arg only valid for single atoms!') spinpol = True magmom_av[0, 2] = self.setups[0].get_hunds_rule_moment(par.charge) if collinear: magnetic = magmom_av.any() spinpol = par.spinpol if spinpol is None: spinpol = magnetic elif magnetic and not spinpol: raise ValueError('Non-zero initial magnetic moment for a ' + 'spin-paired calculation!') nspins = 1 + int(spinpol) if spinpol: self.log('Spin-polarized calculation.') self.log('Magnetic moment: {:.6f}\n'.format(magmom_av.sum())) else: self.log('Spin-paired calculation\n') else: nspins = 1 self.log('Non-collinear calculation.') self.log('Magnetic moment: ({:.6f}, {:.6f}, {:.6f})\n'.format( *magmom_av.sum(0))) if isinstance(par.background_charge, dict): background = create_background_charge(**par.background_charge) else: background = par.background_charge nao = self.setups.nao nvalence = self.setups.nvalence - par.charge if par.background_charge is not None: nvalence += background.charge M = np.linalg.norm(magmom_av.sum(0)) nbands = par.nbands orbital_free = any(setup.orbital_free for setup in self.setups) if orbital_free: nbands = 1 if isinstance(nbands, basestring): if nbands == 'nao': nbands = nao elif nbands[-1] == '%': basebands = (nvalence + M) / 2 nbands = int(np.ceil(float(nbands[:-1]) / 100 * basebands)) else: raise ValueError('Integer expected: Only use a string ' 'if giving a percentage of occupied bands') if nbands is None: # Number of bound partial waves: nbandsmax = sum(setup.get_default_nbands() for setup in self.setups) nbands = int(np.ceil((1.2 * (nvalence + M) / 2))) + 4 if nbands > nbandsmax: nbands = nbandsmax if mode.name == 'lcao' and nbands > nao: nbands = nao elif nbands <= 0: nbands = max(1, int(nvalence + M + 0.5) // 2 + (-nbands)) if nbands > nao and mode.name == 'lcao': raise ValueError('Too many bands for LCAO calculation: ' '%d bands and only %d atomic orbitals!' % (nbands, nao)) if nvalence < 0: raise ValueError( 'Charge %f is not possible - not enough valence electrons' % par.charge) if nvalence > 2 * nbands and not orbital_free: raise ValueError('Too few bands! Electrons: %f, bands: %d' % (nvalence, nbands)) self.create_occupations(magmom_av[:, 2].sum(), orbital_free) if self.scf is None: self.create_scf(nvalence, mode) self.create_symmetry(magmom_av, cell_cv) if not collinear: nbands *= 2 if not self.wfs: self.create_wave_functions(mode, realspace, nspins, collinear, nbands, nao, nvalence, self.setups, cell_cv, pbc_c) else: self.wfs.set_setups(self.setups) if not self.wfs.eigensolver: self.create_eigensolver(xc, nbands, mode) if self.density is None and not reading: assert not par.fixdensity, 'No density to fix!' olddens = None if (self.density is not None and (self.density.gd.parsize_c != self.wfs.gd.parsize_c).any()): # Domain decomposition has changed, so we need to # reinitialize density and hamiltonian: if par.fixdensity: olddens = self.density self.density = None self.hamiltonian = None if self.density is None: self.create_density(realspace, mode, background) # XXXXXXXXXX if setups change, then setups.core_charge may change. # But that parameter was supplied in Density constructor! # This surely is a bug! self.density.initialize(self.setups, self.timer, magmom_av, par.hund) self.density.set_mixer(par.mixer) if self.density.mixer.driver.name == 'dummy' or par.fixdensity: self.log('No density mixing\n') else: self.log(self.density.mixer, '\n') self.density.fixed = par.fixdensity self.density.log = self.log if olddens is not None: self.density.initialize_from_other_density(olddens, self.wfs.kptband_comm) if self.hamiltonian is None: self.create_hamiltonian(realspace, mode, xc) xc.initialize(self.density, self.hamiltonian, self.wfs, self.occupations) description = xc.get_description() if description is not None: self.log('XC parameters: {}\n'.format('\n '.join( description.splitlines()))) if xc.name == 'GLLBSC' and olddens is not None: xc.heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelp(olddens) self.print_memory_estimate(maxdepth=memory_estimate_depth + 1) print_parallelization_details(self.wfs, self.hamiltonian, self.log) self.log('Number of atoms:', natoms) self.log('Number of atomic orbitals:', self.wfs.setups.nao) self.log('Number of bands in calculation:', self.wfs.bd.nbands) self.log('Bands to converge: ', end='') n = par.convergence.get('bands', 'occupied') if n == 'occupied': self.log('occupied states only') elif n == 'all': self.log('all') else: self.log('%d lowest bands' % n) self.log('Number of valence electrons:', self.wfs.nvalence) self.log(flush=True) self.timer.print_info(self) if dry_run: self.dry_run() if (realspace and self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT'): self.hamiltonian.poisson.set_density(self.density) self.hamiltonian.poisson.print_messages(self.log) self.log.fd.flush() self.initialized = True self.log('... initialized\n') def create_setups(self, mode, xc): if self.parameters.filter is None and mode.name != 'pw': gamma = 1.6 N_c = self.parameters.get('gpts') if N_c is None: h = (self.parameters.h or 0.2) / Bohr else: icell_vc = np.linalg.inv(self.atoms.cell) h = ((icell_vc**2).sum(0)**-0.5 / N_c).max() / Bohr def filter(rgd, rcut, f_r, l=0): gcut = np.pi / h - 2 / rcut / gamma ftmp = rgd.filter(f_r, rcut * gamma, gcut, l) f_r[:] = ftmp[:len(f_r)] else: filter = self.parameters.filter Z_a = self.atoms.get_atomic_numbers() self.setups = Setups(Z_a, self.parameters.setups, self.parameters.basis, xc, filter, self.world) self.log(self.setups) def create_grid_descriptor(self, N_c, cell_cv, pbc_c, domain_comm, parsize_domain): return GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) def create_occupations(self, magmom, orbital_free): occ = self.parameters.occupations if occ is None: if orbital_free: occ = {'name': 'orbital-free'} else: width = self.parameters.width if width is not None: warnings.warn( 'Please use occupations=FermiDirac({})'.format(width)) elif self.atoms.pbc.any(): width = 0.1 # eV else: width = 0.0 occ = {'name': 'fermi-dirac', 'width': width} if isinstance(occ, dict): occ = create_occupation_number_object(**occ) if self.parameters.fixdensity: occ.fixed_fermilevel = True if self.occupations: occ.fermilevel = self.occupations.fermilevel self.occupations = occ # If occupation numbers are changed, and we have wave functions, # recalculate the occupation numbers if self.wfs is not None: self.occupations.calculate(self.wfs) self.occupations.magmom = magmom self.log(self.occupations) def create_scf(self, nvalence, mode): # if mode.name == 'lcao': # niter_fixdensity = 0 # else: # niter_fixdensity = 2 nv = max(nvalence, 1) cc = self.parameters.convergence self.scf = SCFLoop( cc.get('eigenstates', 4.0e-8) / Ha**2 * nv, cc.get('energy', 0.0005) / Ha * nv, cc.get('density', 1.0e-4) * nv, cc.get('forces', np.inf) / (Ha / Bohr), self.parameters.maxiter, # XXX make sure niter_fixdensity value is *always* set from default # Subdictionary defaults seem to not be set when user provides # e.g. {}. We should change that so it works like the ordinary # parameters. self.parameters.experimental.get('niter_fixdensity', 0), nv) self.log(self.scf) def create_symmetry(self, magmom_av, cell_cv): symm = self.parameters.symmetry if symm == 'off': symm = {'point_group': False, 'time_reversal': False} m_av = magmom_av.round(decimals=3) # round off id_a = [id + tuple(m_v) for id, m_v in zip(self.setups.id_a, m_av)] self.symmetry = Symmetry(id_a, cell_cv, self.atoms.pbc, **symm) self.symmetry.analyze(self.spos_ac) self.setups.set_symmetry(self.symmetry) def create_eigensolver(self, xc, nbands, mode): # Number of bands to converge: nbands_converge = self.parameters.convergence.get('bands', 'occupied') if nbands_converge == 'all': nbands_converge = nbands elif nbands_converge != 'occupied': assert isinstance(nbands_converge, int) if nbands_converge < 0: nbands_converge += nbands eigensolver = get_eigensolver(self.parameters.eigensolver, mode, self.parameters.convergence) eigensolver.nbands_converge = nbands_converge # XXX Eigensolver class doesn't define an nbands_converge property if isinstance(xc, SIC): eigensolver.blocksize = 1 self.wfs.set_eigensolver(eigensolver) self.log('Eigensolver\n ', self.wfs.eigensolver, '\n') def create_density(self, realspace, mode, background): gd = self.wfs.gd big_gd = gd.new_descriptor(comm=self.world) # Check whether grid is too small. 8 is smallest admissible. # (we decide this by how difficult it is to make the tests pass) # (Actually it depends on stencils! But let the user deal with it) N_c = big_gd.get_size_of_global_array(pad=True) too_small = np.any(N_c / big_gd.parsize_c < 8) if (self.parallel['augment_grids'] and not too_small and mode.name != 'pw'): aux_gd = big_gd else: aux_gd = gd redistributor = GridRedistributor(self.world, self.wfs.kptband_comm, gd, aux_gd) # Construct grid descriptor for fine grids for densities # and potentials: finegd = aux_gd.refine() kwargs = dict(gd=gd, finegd=finegd, nspins=self.wfs.nspins, collinear=self.wfs.collinear, charge=self.parameters.charge + self.wfs.setups.core_charge, redistributor=redistributor, background_charge=background) if realspace: self.density = RealSpaceDensity(stencil=mode.interpolation, **kwargs) else: self.density = pw.ReciprocalSpaceDensity(**kwargs) self.log(self.density, '\n') def create_hamiltonian(self, realspace, mode, xc): dens = self.density kwargs = dict(gd=dens.gd, finegd=dens.finegd, nspins=dens.nspins, collinear=dens.collinear, setups=dens.setups, timer=self.timer, xc=xc, world=self.world, redistributor=dens.redistributor, vext=self.parameters.external, psolver=self.parameters.poissonsolver) if realspace: self.hamiltonian = RealSpaceHamiltonian(stencil=mode.interpolation, **kwargs) xc.set_grid_descriptor(self.hamiltonian.finegd) else: # This code will work if dens.redistributor uses # ordinary density.gd as aux_gd gd = dens.finegd xc_redist = None if self.parallel['augment_grids']: from gpaw.grid_descriptor import BadGridError try: aux_gd = gd.new_descriptor(comm=self.world) except BadGridError as err: import warnings warnings.warn('Ignoring augment_grids: {}'.format(err)) else: bcast_comm = dens.redistributor.broadcast_comm xc_redist = GridRedistributor(self.world, bcast_comm, gd, aux_gd) self.hamiltonian = pw.ReciprocalSpaceHamiltonian( pd2=dens.pd2, pd3=dens.pd3, realpbc_c=self.atoms.pbc, xc_redistributor=xc_redist, **kwargs) xc.set_grid_descriptor(self.hamiltonian.xc_gd) self.hamiltonian.soc = self.parameters.experimental.get('soc') self.log(self.hamiltonian, '\n') def create_kpoint_descriptor(self, nspins): par = self.parameters bzkpts_kc = kpts2ndarray(par.kpts, self.atoms) kpt_refine = par.experimental.get('kpt_refine') if kpt_refine is None: kd = KPointDescriptor(bzkpts_kc, nspins) self.timer.start('Set symmetry') kd.set_symmetry(self.atoms, self.symmetry, comm=self.world) self.timer.stop('Set symmetry') else: self.timer.start('Set k-point refinement') kd = create_kpoint_descriptor_with_refinement(kpt_refine, bzkpts_kc, nspins, self.atoms, self.symmetry, comm=self.world, timer=self.timer) self.timer.stop('Set k-point refinement') # Update quantities which might have changed, if symmetry # was changed self.symmetry = kd.symmetry self.setups.set_symmetry(kd.symmetry) self.log(kd) return kd def create_wave_functions(self, mode, realspace, nspins, collinear, nbands, nao, nvalence, setups, cell_cv, pbc_c): par = self.parameters kd = self.create_kpoint_descriptor(nspins) parallelization = mpi.Parallelization(self.world, nspins * kd.nibzkpts) parsize_kpt = self.parallel['kpt'] parsize_domain = self.parallel['domain'] parsize_bands = self.parallel['band'] ndomains = None if parsize_domain is not None: ndomains = np.prod(parsize_domain) parallelization.set(kpt=parsize_kpt, domain=ndomains, band=parsize_bands) comms = parallelization.build_communicators() domain_comm = comms['d'] kpt_comm = comms['k'] band_comm = comms['b'] kptband_comm = comms['D'] domainband_comm = comms['K'] self.comms = comms if par.gpts is not None: if par.h is not None: raise ValueError("""You can't use both "gpts" and "h"!""") N_c = np.array(par.gpts) else: h = par.h if h is not None: h /= Bohr N_c = get_number_of_grid_points(cell_cv, h, mode, realspace, kd.symmetry) self.symmetry.check_grid(N_c) kd.set_communicator(kpt_comm) parstride_bands = self.parallel['stridebands'] bd = BandDescriptor(nbands, band_comm, parstride_bands) # Construct grid descriptor for coarse grids for wave functions: gd = self.create_grid_descriptor(N_c, cell_cv, pbc_c, domain_comm, parsize_domain) if hasattr(self, 'time') or mode.force_complex_dtype or not collinear: dtype = complex else: if kd.gamma: dtype = float else: dtype = complex wfs_kwargs = dict(gd=gd, nvalence=nvalence, setups=setups, bd=bd, dtype=dtype, world=self.world, kd=kd, kptband_comm=kptband_comm, timer=self.timer) if self.parallel['sl_auto']: # Choose scalapack parallelization automatically for key, val in self.parallel.items(): if (key.startswith('sl_') and key != 'sl_auto' and val is not None): raise ValueError("Cannot use 'sl_auto' together " "with '%s'" % key) max_scalapack_cpus = bd.comm.size * gd.comm.size sl_default = suggest_blocking(nbands, max_scalapack_cpus) else: sl_default = self.parallel['sl_default'] if mode.name == 'lcao': assert collinear # Layouts used for general diagonalizer sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default elpasolver = None if self.parallel['use_elpa']: elpasolver = self.parallel['elpasolver'] lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, bd, domainband_comm, dtype, nao=nao, timer=self.timer, elpasolver=elpasolver) self.wfs = mode(lcaoksl, **wfs_kwargs) elif mode.name == 'fd' or mode.name == 'pw': # Use (at most) all available LCAO for initialization lcaonbands = min(nbands, nao) try: lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands) except RuntimeError: initksl = None else: # Layouts used for general diagonalizer # (LCAO initialization) sl_lcao = self.parallel['sl_lcao'] if sl_lcao is None: sl_lcao = sl_default initksl = get_KohnSham_layouts(sl_lcao, 'lcao', gd, lcaobd, domainband_comm, dtype, nao=nao, timer=self.timer) reuse_wfs_method = par.experimental.get('reuse_wfs_method', 'paw') sl = (domainband_comm, ) + (self.parallel['sl_diagonalize'] or sl_default or (1, 1, None)) self.wfs = mode(sl, initksl, reuse_wfs_method=reuse_wfs_method, collinear=collinear, **wfs_kwargs) else: self.wfs = mode(self, collinear=collinear, **wfs_kwargs) self.log(self.wfs, '\n') def dry_run(self): # Can be overridden like in gpaw.atom.atompaw print_cell(self.wfs.gd, self.atoms.pbc, self.log) print_positions(self.atoms, self.log, self.density.magmom_av) self.log.fd.flush() # Write timing info now before the interpreter shuts down: self.__del__() # Disable timing output during shut-down: del self.timer raise SystemExit
class 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!')
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)
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' timer.start(tstr) calc = GPAW(h=0.3, eigensolver='rmm-diis', xc='PBE', nbands=4, convergence={'eigenstates': 1e-4}, charge=-1) loa.set_calculator(calc) E[fg] = loa.get_potential_energy() calc.set(xc=HybridXC('PBE0', finegrid=fg)) E[fg] = loa.get_potential_energy() niter[fg] = calc.get_number_of_iterations() timer.stop(tstr) timer.write(sys.stdout)
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)
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
class RPACorrelation: def __init__(self, calc, xc='RPA', filename=None, skip_gamma=False, qsym=True, nlambda=None, nfrequencies=16, frequency_max=800.0, frequency_scale=2.0, frequencies=None, weights=None, truncation=None, world=mpi.world, nblocks=1, txt=sys.stdout): """Creates the RPACorrelation object calc: str or calculator object The string should refer to the .gpw file contaning KS orbitals xc: str Exchange-correlation kernel. This is only different from RPA when this object is constructed from a different module - e.g. fxc.py filename: str txt output skip_gamme: bool If True, skip q = [0,0,0] from the calculation qsym: bool Use symmetry to reduce q-points nlambda: int Number of lambda points. Only used for numerical coupling constant integration involved when called from fxc.py nfrequencies: int Number of frequency points used in the Gauss-Legendre integration frequency_max: float Largest frequency point in Gauss-Legendre integration frequency_scale: float Determines density of frequency points at low frequencies. A slight increase to e.g. 2.5 or 3.0 improves convergence wth respect to frequency points for metals frequencies: list List of frequancies for user-specified frequency integration weights: list list of weights (integration measure) for a user specified frequency grid. Must be specified and have the same length as frequencies if frequencies is not None truncation: str Coulomb truncation scheme. Can be either wigner-seitz, 2D, 1D, or 0D world: communicator nblocks: int Number of parallelization blocks. Frequency parallelization can be specified by setting nblocks=nfrequencies and is useful for memory consuming calculations txt: str txt file for saving and loading contributions to the correlation energy from different q-points """ if isinstance(calc, str): calc = GPAW(calc, txt=None, communicator=mpi.serial_comm) self.calc = calc if world.rank != 0: txt = devnull elif isinstance(txt, str): txt = open(txt, 'w', 1) self.fd = txt self.timer = Timer() if frequencies is None: frequencies, weights = get_gauss_legendre_points(nfrequencies, frequency_max, frequency_scale) user_spec = False else: assert weights is not None user_spec = True self.omega_w = frequencies / Hartree self.weight_w = weights / Hartree if nblocks > 1: assert len(self.omega_w) % nblocks == 0 self.nblocks = nblocks self.world = world self.truncation = truncation self.skip_gamma = skip_gamma self.ibzq_qc = None self.weight_q = None self.initialize_q_points(qsym) # Energies for all q-vetors and cutoff energies: self.energy_qi = [] self.filename = filename self.print_initialization(xc, frequency_scale, nlambda, user_spec) def initialize_q_points(self, qsym): kd = self.calc.wfs.kd self.bzq_qc = kd.get_bz_q_points(first=True) if not qsym: self.ibzq_qc = self.bzq_qc self.weight_q = np.ones(len(self.bzq_qc)) / len(self.bzq_qc) else: U_scc = kd.symmetry.op_scc self.ibzq_qc = kd.get_ibz_q_points(self.bzq_qc, U_scc)[0] self.weight_q = kd.q_weights def read(self): lines = open(self.filename).readlines()[1:] n = 0 self.energy_qi = [] nq = len(lines) // len(self.ecut_i) for q_c in self.ibzq_qc[:nq]: self.energy_qi.append([]) for ecut in self.ecut_i: q1, q2, q3, ec, energy = [float(x) for x in lines[n].split()] self.energy_qi[-1].append(energy / Hartree) n += 1 if (abs(q_c - (q1, q2, q3)).max() > 1e-4 or abs(int(ecut * Hartree) - ec) > 0): self.energy_qi = [] return print('Read %d q-points from file: %s' % (nq, self.filename), file=self.fd) print(file=self.fd) def write(self): if self.world.rank == 0 and self.filename: fd = open(self.filename, 'w') print('#%9s %10s %10s %8s %12s' % ('q1', 'q2', 'q3', 'E_cut', 'E_c(q)'), file=fd) for energy_i, q_c in zip(self.energy_qi, self.ibzq_qc): for energy, ecut in zip(energy_i, self.ecut_i): print('%10.4f %10.4f %10.4f %8d %r' % (tuple(q_c) + (ecut * Hartree, energy * Hartree)), file=fd) def calculate(self, ecut, nbands=None, spin=False): """Calculate RPA correlation energy for one or several cutoffs. ecut: float or list of floats Plane-wave cutoff(s). nbands: int Number of bands (defaults to number of plane-waves). spin: bool Separate spin in response function. (Only needed for beyond RPA methods that inherit this function). """ p = functools.partial(print, file=self.fd) if isinstance(ecut, (float, int)): ecut = ecut * (1 + 0.5 * np.arange(6))**(-2 / 3) self.ecut_i = np.asarray(np.sort(ecut)) / Hartree ecutmax = max(self.ecut_i) if nbands is None: p('Response function bands : Equal to number of plane waves') else: p('Response function bands : %s' % nbands) p('Plane wave cutoffs (eV) :', end='') for e in self.ecut_i: p(' {0:.3f}'.format(e * Hartree), end='') p() if self.truncation is not None: p('Using %s Coulomb truncation' % self.truncation) p() if self.filename and os.path.isfile(self.filename): self.read() self.world.barrier() chi0 = Chi0(self.calc, 1j * Hartree * self.omega_w, eta=0.0, intraband=False, hilbert=False, txt='chi0.txt', timer=self.timer, world=self.world, nblocks=self.nblocks) self.blockcomm = chi0.blockcomm wfs = self.calc.wfs if self.truncation == 'wigner-seitz': self.wstc = WignerSeitzTruncatedCoulomb(wfs.gd.cell_cv, wfs.kd.N_c, self.fd) else: self.wstc = None nq = len(self.energy_qi) nw = len(self.omega_w) nGmax = max(count_reciprocal_vectors(ecutmax, wfs.gd, q_c) for q_c in self.ibzq_qc[nq:]) mynGmax = (nGmax + self.nblocks - 1) // self.nblocks nx = (1 + spin) * nw * mynGmax * nGmax A1_x = np.empty(nx, complex) if self.nblocks > 1: A2_x = np.empty(nx, complex) else: A2_x = None self.timer.start('RPA') for q_c in self.ibzq_qc[nq:]: if np.allclose(q_c, 0.0) and self.skip_gamma: self.energy_qi.append(len(self.ecut_i) * [0.0]) self.write() p('Not calculating E_c(q) at Gamma') p() continue thisqd = KPointDescriptor([q_c]) pd = PWDescriptor(ecutmax, wfs.gd, complex, thisqd) nG = pd.ngmax mynG = (nG + self.nblocks - 1) // self.nblocks chi0.Ga = self.blockcomm.rank * mynG chi0.Gb = min(chi0.Ga + mynG, nG) shape = (1 + spin, nw, chi0.Gb - chi0.Ga, nG) chi0_swGG = A1_x[:np.prod(shape)].reshape(shape) chi0_swGG[:] = 0.0 if np.allclose(q_c, 0.0): chi0_swxvG = np.zeros((1 + spin, nw, 2, 3, nG), complex) chi0_swvv = np.zeros((1 + spin, nw, 3, 3), complex) else: chi0_swxvG = None chi0_swvv = None # First not completely filled band: m1 = chi0.nocc1 p('# %s - %s' % (len(self.energy_qi), ctime().split()[-2])) p('q = [%1.3f %1.3f %1.3f]' % tuple(q_c)) energy_i = [] for ecut in self.ecut_i: if ecut == ecutmax: # Nothing to cut away: cut_G = None m2 = nbands or nG else: cut_G = np.arange(nG)[pd.G2_qG[0] <= 2 * ecut] m2 = len(cut_G) p('E_cut = %d eV / Bands = %d:' % (ecut * Hartree, m2)) self.fd.flush() energy = self.calculate_q(chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, m1, m2, cut_G, A2_x) energy_i.append(energy) m1 = m2 a = 1 / chi0.kncomm.size if ecut < ecutmax and a != 1.0: # Chi0 will be summed again over chicomm, so we divide # by its size: chi0_swGG *= a if chi0_swxvG is not None: chi0_swxvG *= a chi0_swvv *= a self.energy_qi.append(energy_i) self.write() p() e_i = np.dot(self.weight_q, np.array(self.energy_qi)) p('==========================================================') p() p('Total correlation energy:') for e_cut, e in zip(self.ecut_i, e_i): p('%6.0f: %6.4f eV' % (e_cut * Hartree, e * Hartree)) p() self.energy_qi = [] # important if another calculation is performed if len(e_i) > 1: self.extrapolate(e_i) p('Calculation completed at: ', ctime()) p() self.timer.stop('RPA') self.timer.write(self.fd) self.fd.flush() return e_i * Hartree @timer('chi0(q)') def calculate_q(self, chi0, pd, chi0_swGG, chi0_swxvG, chi0_swvv, m1, m2, cut_G, A2_x): chi0_wGG = chi0_swGG[0] if chi0_swxvG is not None: chi0_wxvG = chi0_swxvG[0] chi0_wvv = chi0_swvv[0] else: chi0_wxvG = None chi0_wvv = None chi0._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv, m1, m2, [0, 1]) print('E_c(q) = ', end='', file=self.fd) chi0_wGG = chi0.redistribute(chi0_wGG, A2_x) if not pd.kd.gamma: e = self.calculate_energy(pd, chi0_wGG, cut_G) print('%.3f eV' % (e * Hartree), file=self.fd) self.fd.flush() else: from ase.dft import monkhorst_pack kd = self.calc.wfs.kd N = 4 N_c = np.array([N, N, N]) if self.truncation is not None: N_c[kd.N_c == 1] = 1 q_qc = monkhorst_pack(N_c) / kd.N_c q_qc *= 1.0e-6 U_scc = kd.symmetry.op_scc q_qc = kd.get_ibz_q_points(q_qc, U_scc)[0] weight_q = kd.q_weights q_qv = 2 * np.pi * np.dot(q_qc, pd.gd.icell_cv) nw = len(self.omega_w) mynw = nw // self.nblocks w1 = self.blockcomm.rank * mynw w2 = w1 + mynw a_qw = np.sum(np.dot(chi0_wvv[w1:w2], q_qv.T) * q_qv.T, axis=1).T a0_qwG = np.dot(q_qv, chi0_wxvG[w1:w2, 0]) a1_qwG = np.dot(q_qv, chi0_wxvG[w1:w2, 1]) e = 0 for iq in range(len(q_qv)): chi0_wGG[:, 0] = a0_qwG[iq] chi0_wGG[:, :, 0] = a1_qwG[iq] chi0_wGG[:, 0, 0] = a_qw[iq] ev = self.calculate_energy(pd, chi0_wGG, cut_G, q_v=q_qv[iq]) e += ev * weight_q[iq] print('%.3f eV' % (e * Hartree), file=self.fd) self.fd.flush() return e @timer('Energy') def calculate_energy(self, pd, chi0_wGG, cut_G, q_v=None): """Evaluate correlation energy from chi0.""" sqrV_G = get_coulomb_kernel(pd, self.calc.wfs.kd.N_c, q_v=q_v, truncation=self.truncation, wstc=self.wstc)**0.5 if cut_G is not None: sqrV_G = sqrV_G[cut_G] nG = len(sqrV_G) e_w = [] for chi0_GG in chi0_wGG: if cut_G is not None: chi0_GG = chi0_GG.take(cut_G, 0).take(cut_G, 1) e_GG = np.eye(nG) - chi0_GG * sqrV_G * sqrV_G[:, np.newaxis] e = np.log(np.linalg.det(e_GG)) + nG - np.trace(e_GG) e_w.append(e.real) E_w = np.zeros_like(self.omega_w) self.blockcomm.all_gather(np.array(e_w), E_w) energy = np.dot(E_w, self.weight_w) / (2 * np.pi) self.E_w = E_w return energy def extrapolate(self, e_i): print('Extrapolated energies:', file=self.fd) ex_i = [] for i in range(len(e_i) - 1): e1, e2 = e_i[i:i + 2] x1, x2 = self.ecut_i[i:i + 2]**-1.5 ex = (e1 * x2 - e2 * x1) / (x2 - x1) ex_i.append(ex) print(' %4.0f -%4.0f: %5.3f eV' % (self.ecut_i[i] * Hartree, self.ecut_i[i + 1] * Hartree, ex * Hartree), file=self.fd) print(file=self.fd) self.fd.flush() return e_i * Hartree def print_initialization(self, xc, frequency_scale, nlambda, user_spec): p = functools.partial(print, file=self.fd) p('----------------------------------------------------------') p('Non-self-consistent %s correlation energy' % xc) p('----------------------------------------------------------') p('Started at: ', ctime()) p() p('Atoms :', self.calc.atoms.get_chemical_formula(mode='hill')) p('Ground state XC functional :', self.calc.hamiltonian.xc.name) p('Valence electrons :', self.calc.wfs.setups.nvalence) p('Number of bands :', self.calc.wfs.bd.nbands) p('Number of spins :', self.calc.wfs.nspins) p('Number of k-points :', len(self.calc.wfs.kd.bzk_kc)) p('Number of irreducible k-points :', len(self.calc.wfs.kd.ibzk_kc)) p('Number of q-points :', len(self.bzq_qc)) p('Number of irreducible q-points :', len(self.ibzq_qc)) p() for q, weight in zip(self.ibzq_qc, self.weight_q): p(' q: [%1.4f %1.4f %1.4f] - weight: %1.3f' % (q[0], q[1], q[2], weight)) p() p('----------------------------------------------------------') p('----------------------------------------------------------') p() if nlambda is None: p('Analytical coupling constant integration') else: p('Numerical coupling constant integration using', nlambda, 'Gauss-Legendre points') p() p('Frequencies') if not user_spec: p(' Gauss-Legendre integration with %s frequency points' % len(self.omega_w)) p(' Transformed from [0,oo] to [0,1] using e^[-aw^(1/B)]') p(' Highest frequency point at %5.1f eV and B=%1.1f' % (self.omega_w[-1] * Hartree, frequency_scale)) else: p(' User specified frequency integration with', len(self.omega_w), 'frequency points') p() p('Parallelization') p(' Total number of CPUs : % s' % self.world.size) p(' G-vector decomposition : % s' % self.nblocks) p(' K-point/band decomposition : % s' % (self.world.size // self.nblocks)) p()
class 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 HybridXC(HybridXCBase): orbital_dependent = True def __init__(self, name, hybrid=None, xc=None, alpha=None, gamma_point=1, method='standard', bandstructure=False, logfilename='-', bands=None, fcut=1e-10, molecule=False, qstride=1, world=None): """Mix standard functionals with exact exchange. name: str Name of functional: EXX, PBE0, HSE03, HSE06 hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. method: str Use 'standard' standard formula and 'acdf for adiabatic-connection dissipation fluctuation formula. alpha: float XXX describe gamma_point: bool 0: Skip k2-k1=0 interactions. 1: Use the alpha method. 2: Integrate the gamma point. bandstructure: bool Calculate bandstructure instead of just the total energy. bands: list of int List of bands to calculate bandstructure for. Default is all bands. molecule: bool Decouple electrostatic interactions between periodically repeated images. fcut: float Threshold for empty band. """ self.alpha = alpha self.fcut = fcut self.gamma_point = gamma_point self.method = method self.bandstructure = bandstructure self.bands = bands self.fd = logfilename self.write_timing_information = True HybridXCBase.__init__(self, name, hybrid, xc) # EXX energies: self.exx = None # total self.evv = None # valence-valence (pseudo part) self.evvacdf = None # valence-valence (pseudo part) self.devv = None # valence-valence (PAW correction) self.evc = None # valence-core self.ecc = None # core-core self.exx_skn = None # bandstructure self.qlatest = None if world is None: world = mpi.world self.world = world self.molecule = molecule if isinstance(qstride, int): qstride = [qstride] * 3 self.qstride_c = np.asarray(qstride) self.timer = Timer() def log(self, *args, **kwargs): prnt(file=self.fd, *args, **kwargs) self.fd.flush() def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, dens, ham, wfs, occupations): assert wfs.bd.comm.size == 1 self.xc.initialize(dens, ham, wfs, occupations) self.dens = dens self.wfs = wfs # Make a k-point descriptor that is not distributed # (self.kd.comm is serial_comm): self.kd = wfs.kd.copy() self.fd = logfile(self.fd, self.world.rank) wfs.initialize_wave_functions_from_restart_file() def set_positions(self, spos_ac): self.spos_ac = spos_ac def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx * self.hybrid def calculate_exx(self): """Non-selfconsistent calculation.""" self.timer.start('EXX') self.timer.start('Initialization') kd = self.kd wfs = self.wfs if fftw.FFTPlan is fftw.NumpyFFTPlan: self.log('NOT USING FFTW !!') self.log('Spins:', self.wfs.nspins) W = max(1, self.wfs.kd.comm.size // self.wfs.nspins) # Are the k-points distributed? kparallel = (W > 1) # Find number of occupied bands: self.nocc_sk = np.zeros((self.wfs.nspins, kd.nibzkpts), int) for kpt in self.wfs.kpt_u: for n, f in enumerate(kpt.f_n): if abs(f) < self.fcut: self.nocc_sk[kpt.s, kpt.k] = n break else: self.nocc_sk[kpt.s, kpt.k] = self.wfs.bd.nbands self.wfs.kd.comm.sum(self.nocc_sk) noccmin = self.nocc_sk.min() noccmax = self.nocc_sk.max() self.log('Number of occupied bands (min, max): %d, %d' % (noccmin, noccmax)) self.log('Number of valence electrons:', self.wfs.setups.nvalence) if self.bandstructure: self.log('Calculating eigenvalue shifts.') # allocate array for eigenvalue shifts: self.exx_skn = np.zeros((self.wfs.nspins, kd.nibzkpts, self.wfs.bd.nbands)) if self.bands is None: noccmax = self.wfs.bd.nbands else: noccmax = max(max(self.bands) + 1, noccmax) N_c = self.kd.N_c vol = wfs.gd.dv * wfs.gd.N_c.prod() if self.alpha is None: alpha = 6 * vol**(2 / 3.0) / pi**2 else: alpha = self.alpha if self.gamma_point == 1: if alpha == 0.0: qvol = (2*np.pi)**3 / vol / N_c.prod() self.gamma = 4*np.pi * (3*qvol / (4*np.pi))**(1/3.) / qvol else: self.gamma = self.calculate_gamma(vol, alpha) else: kcell_cv = wfs.gd.cell_cv.copy() kcell_cv[0] *= N_c[0] kcell_cv[1] *= N_c[1] kcell_cv[2] *= N_c[2] self.gamma = madelung(kcell_cv) * vol * N_c.prod() / (4 * np.pi) self.log('Value of alpha parameter: %.3f Bohr^2' % alpha) self.log('Value of gamma parameter: %.3f Bohr^2' % self.gamma) # Construct all possible q=k2-k1 vectors: Nq_c = (N_c - 1) // self.qstride_c i_qc = np.indices(Nq_c * 2 + 1, float).transpose( (1, 2, 3, 0)).reshape((-1, 3)) self.bzq_qc = (i_qc - Nq_c) / N_c * self.qstride_c self.q0 = ((Nq_c * 2 + 1).prod() - 1) // 2 # index of q=(0,0,0) assert not self.bzq_qc[self.q0].any() # Count number of pairs for each q-vector: self.npairs_q = np.zeros(len(self.bzq_qc), int) for s in range(kd.nspins): for k1 in range(kd.nibzkpts): for k2 in range(kd.nibzkpts): for K2, q, n1_n, n2 in self.indices(s, k1, k2): self.npairs_q[q] += len(n1_n) self.npairs0 = self.npairs_q.sum() # total number of pairs self.log('Number of pairs:', self.npairs0) # Distribute q-vectors to Q processors: Q = self.world.size // self.wfs.kd.comm.size myrank = self.world.rank // self.wfs.kd.comm.size rank = 0 N = 0 myq = [] nq = 0 for q, n in enumerate(self.npairs_q): if n > 0: nq += 1 if rank == myrank: myq.append(q) N += n if N >= (rank + 1.0) * self.npairs0 / Q: rank += 1 assert len(myq) > 0, 'Too few q-vectors for too many processes!' self.bzq_qc = self.bzq_qc[myq] try: self.q0 = myq.index(self.q0) except ValueError: self.q0 = None self.log('%d x %d x %d k-points' % tuple(self.kd.N_c)) self.log('Distributing %d IBZ k-points over %d process(es).' % (kd.nibzkpts, self.wfs.kd.comm.size)) self.log('Distributing %d q-vectors over %d process(es).' % (nq, Q)) # q-point descriptor for my q-vectors: qd = KPointDescriptor(self.bzq_qc) # Plane-wave descriptor for all wave-functions: self.pd = PWDescriptor(wfs.pd.ecut, wfs.gd, dtype=wfs.pd.dtype, kd=kd) # Plane-wave descriptor pair-densities: self.pd2 = PWDescriptor(self.dens.pd2.ecut, self.dens.gd, dtype=wfs.dtype, kd=qd) self.log('Cutoff energies:') self.log(' Wave functions: %10.3f eV' % (self.pd.ecut * Hartree)) self.log(' Density: %10.3f eV' % (self.pd2.ecut * Hartree)) # Calculate 1/|G+q|^2 with special treatment of |G+q|=0: G2_qG = self.pd2.G2_qG if self.q0 is None: if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] else: self.iG2_qG = [(1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG] else: G2_qG[self.q0][0] = 117.0 # avoid division by zero if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] self.iG2_qG[self.q0][0] = self.gamma else: self.iG2_qG = [(1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG] self.iG2_qG[self.q0][0] = 1 / (4 * self.omega**2) G2_qG[self.q0][0] = 0.0 # restore correct value # Compensation charges: self.ghat = PWLFC([setup.ghat_l for setup in wfs.setups], self.pd2) self.ghat.set_positions(self.spos_ac) if self.molecule: self.initialize_gaussian() self.log('Value of beta parameter: %.3f 1/Bohr^2' % self.beta) self.timer.stop('Initialization') # Ready ... set ... go: self.t0 = time() self.npairs = 0 self.evv = 0.0 self.evvacdf = 0.0 for s in range(self.wfs.nspins): kpt1_q = [KPoint(self.wfs, noccmax).initialize(kpt) for kpt in self.wfs.kpt_u if kpt.s == s] kpt2_q = kpt1_q[:] if len(kpt1_q) == 0: # No s-spins on this CPU: continue # Send and receive ranks: srank = self.wfs.kd.get_rank_and_index( s, (kpt1_q[0].k - 1) % kd.nibzkpts)[0] rrank = self.wfs.kd.get_rank_and_index( s, (kpt1_q[-1].k + 1) % kd.nibzkpts)[0] # Shift k-points kd.nibzkpts - 1 times: for i in range(kd.nibzkpts): if i < kd.nibzkpts - 1: if kparallel: kpt = kpt2_q[-1].next(self.wfs) kpt.start_receiving(rrank) kpt2_q[0].start_sending(srank) else: kpt = kpt2_q[0] self.timer.start('Calculate') for kpt1, kpt2 in zip(kpt1_q, kpt2_q): # Loop over all k-points that k2 can be mapped to: for K2, q, n1_n, n2 in self.indices(s, kpt1.k, kpt2.k): self.apply(K2, q, kpt1, kpt2, n1_n, n2) self.timer.stop('Calculate') if i < kd.nibzkpts - 1: self.timer.start('Wait') if kparallel: kpt.wait() kpt2_q[0].wait() self.timer.stop('Wait') kpt2_q.pop(0) kpt2_q.append(kpt) self.evv = self.world.sum(self.evv) self.evvacdf = self.world.sum(self.evvacdf) self.calculate_exx_paw_correction() if self.method == 'standard': self.exx = self.evv + self.devv + self.evc + self.ecc elif self.method == 'acdf': self.exx = self.evvacdf + self.devv + self.evc + self.ecc else: 1 / 0 self.log('Exact exchange energy:') for txt, e in [ ('core-core', self.ecc), ('valence-core', self.evc), ('valence-valence (pseudo, acdf)', self.evvacdf), ('valence-valence (pseudo, standard)', self.evv), ('valence-valence (correction)', self.devv), ('total (%s)' % self.method, self.exx)]: self.log(' %-36s %14.6f eV' % (txt + ':', e * Hartree)) self.log('Total time: %10.3f seconds' % (time() - self.t0)) self.npairs = self.world.sum(self.npairs) assert self.npairs == self.npairs0 self.timer.stop('EXX') self.timer.write(self.fd) def calculate_gamma(self, vol, alpha): if self.molecule: return 0.0 N_c = self.kd.N_c offset_c = (N_c + 1) % 2 * 0.5 / N_c bzq_qc = monkhorst_pack(N_c) + offset_c qd = KPointDescriptor(bzq_qc) pd = PWDescriptor(self.wfs.pd.ecut, self.wfs.gd, kd=qd) gamma = (vol / (2 * pi)**2 * sqrt(pi / alpha) * self.kd.nbzkpts) for G2_G in pd.G2_qG: if G2_G[0] < 1e-7: G2_G = G2_G[1:] gamma -= np.dot(np.exp(-alpha * G2_G), G2_G**-1) return gamma / self.qstride_c.prod() def indices(self, s, k1, k2): """Generator for (K2, q, n1, n2) indices for (k1, k2) pair. s: int Spin index. k1: int Index of k-point in the IBZ. k2: int Index of k-point in the IBZ. Returns (K, q, n1_n, n2), where K then index of the k-point in the BZ that k2 is mapped to, q is the index of the q-vector between K and k1, and n1_n is a list of bands that should be combined with band n2.""" for K, k in enumerate(self.kd.bz2ibz_k): if k == k2: for K, q, n1_n, n2 in self._indices(s, k1, k2, K): yield K, q, n1_n, n2 def _indices(self, s, k1, k2, K2): k1_c = self.kd.ibzk_kc[k1] k2_c = self.kd.bzk_kc[K2] q_c = k2_c - k1_c q = abs(self.bzq_qc - q_c).sum(1).argmin() if abs(self.bzq_qc[q] - q_c).sum() > 1e-7: return if self.gamma_point == 0 and q == self.q0: return nocc1 = self.nocc_sk[s, k1] nocc2 = self.nocc_sk[s, k2] # Is k2 in the IBZ? is_ibz2 = (self.kd.ibz2bz_k[k2] == K2) for n2 in range(self.wfs.bd.nbands): # Find range of n1's (from n1a to n1b-1): if is_ibz2: # We get this combination twice, so let's only do half: if k1 >= k2: n1a = n2 else: n1a = n2 + 1 else: n1a = 0 n1b = self.wfs.bd.nbands if self.bandstructure: if n2 >= nocc2: n1b = min(n1b, nocc1) else: if n2 >= nocc2: break n1b = min(n1b, nocc1) if self.bands is not None: assert self.bandstructure n1_n = [] for n1 in range(n1a, n1b): if (n1 in self.bands and n2 < nocc2 or is_ibz2 and n2 in self.bands and n1 < nocc1): n1_n.append(n1) n1_n = np.array(n1_n) else: n1_n = np.arange(n1a, n1b) if len(n1_n) == 0: continue yield K2, q, n1_n, n2 def apply(self, K2, q, kpt1, kpt2, n1_n, n2): k20_c = self.kd.ibzk_kc[kpt2.k] k2_c = self.kd.bzk_kc[K2] if k2_c.any(): self.timer.start('Initialize plane waves') eik2r_R = self.wfs.gd.plane_wave(k2_c) eik20r_R = self.wfs.gd.plane_wave(k20_c) self.timer.stop('Initialize plane waves') else: eik2r_R = 1.0 eik20r_R = 1.0 w1 = self.kd.weight_k[kpt1.k] w2 = self.kd.weight_k[kpt2.k] # Is k2 in the 1. BZ? is_ibz2 = (self.kd.ibz2bz_k[kpt2.k] == K2) e_n = self.calculate_interaction(n1_n, n2, kpt1, kpt2, q, K2, eik20r_R, eik2r_R, is_ibz2) e_n *= 1.0 / self.kd.nbzkpts / self.wfs.nspins * self.qstride_c.prod() if q == self.q0: e_n[n1_n == n2] *= 0.5 f1_n = kpt1.f_n[n1_n] eps1_n = kpt1.eps_n[n1_n] f2 = kpt2.f_n[n2] eps2 = kpt2.eps_n[n2] s_n = np.sign(eps2 - eps1_n) evv = (f1_n * f2 * e_n).sum() evvacdf = 0.5 * (f1_n * (1 - s_n) * e_n + f2 * (1 + s_n) * e_n).sum() self.evv += evv * w1 self.evvacdf += evvacdf * w1 if is_ibz2: self.evv += evv * w2 self.evvacdf += evvacdf * w2 if self.bandstructure: x = self.wfs.nspins self.exx_skn[kpt1.s, kpt1.k, n1_n] += x * f2 * e_n if is_ibz2: self.exx_skn[kpt2.s, kpt2.k, n2] += x * np.dot(f1_n, e_n) def calculate_interaction(self, n1_n, n2, kpt1, kpt2, q, k, eik20r_R, eik2r_R, is_ibz2): """Calculate Coulomb interactions. For all n1 in the n1_n list, calculate interaction with n2.""" # number of plane waves: ng1 = self.wfs.ng_k[kpt1.k] ng2 = self.wfs.ng_k[kpt2.k] # Transform to real space and apply symmetry operation: self.timer.start('IFFT1') if is_ibz2: u2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) else: psit2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) * eik20r_R self.timer.start('Symmetry transform') u2_R = self.kd.transform_wave_function(psit2_R, k) / eik2r_R self.timer.stop() self.timer.stop() # Calculate pair densities: nt_nG = self.pd2.zeros(len(n1_n), q=q) for n1, nt_G in zip(n1_n, nt_nG): self.timer.start('IFFT2') u1_R = self.pd.ifft(kpt1.psit_nG[n1, :ng1], kpt1.k) self.timer.stop() nt_R = u1_R.conj() * u2_R self.timer.start('FFT') nt_G[:] = self.pd2.fft(nt_R, q) self.timer.stop() s = self.kd.sym_k[k] time_reversal = self.kd.time_reversal_k[k] k2_c = self.kd.ibzk_kc[kpt2.k] self.timer.start('Compensation charges') Q_anL = {} # coefficients for shape functions for a, P1_ni in kpt1.P_ani.items(): P1_ni = P1_ni[n1_n] if is_ibz2: P2_i = kpt2.P_ani[a][n2] else: b = self.kd.symmetry.a_sa[s, a] S_c = (np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s]) - self.spos_ac[b]) assert abs(S_c.round() - S_c).max() < 1e-5 if self.ghat.dtype == complex: x = np.exp(2j * pi * np.dot(k2_c, S_c)) else: x = 1.0 P2_i = np.dot(self.wfs.setups[a].R_sii[s], kpt2.P_ani[b][n2]) * x if time_reversal: P2_i = P2_i.conj() D_np = [] for P1_i in P1_ni: D_ii = np.outer(P1_i.conj(), P2_i) D_np.append(pack(D_ii)) Q_anL[a] = np.dot(D_np, self.wfs.setups[a].Delta_pL) self.timer.start('Expand') if q != self.qlatest: self.f_IG = self.ghat.expand(q) self.qlatest = q self.timer.stop('Expand') # Add compensation charges: self.ghat.add(nt_nG, Q_anL, q, self.f_IG) self.timer.stop('Compensation charges') if self.molecule and n2 in n1_n: nn = (n1_n == n2).nonzero()[0][0] nt_nG[nn] -= self.ngauss_G else: nn = None iG2_G = self.iG2_qG[q] # Calculate energies: e_n = np.empty(len(n1_n)) for n, nt_G in enumerate(nt_nG): e_n[n] = -4 * pi * np.real(self.pd2.integrate(nt_G, nt_G * iG2_G)) self.npairs += 1 if nn is not None: e_n[nn] -= 2 * (self.pd2.integrate(nt_nG[nn], self.vgauss_G) + (self.beta / 2 / pi)**0.5) if self.write_timing_information: t = (time() - self.t0) / len(n1_n) self.log('Time for first pair-density: %10.3f seconds' % t) self.log('Estimated total time: %10.3f seconds' % (t * self.npairs0 / self.world.size)) self.write_timing_information = False return e_n def calculate_exx_paw_correction(self): self.timer.start('PAW correction') self.devv = 0.0 self.evc = 0.0 self.ecc = 0.0 deg = 2 // self.wfs.nspins # spin degeneracy for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.devv -= D_ii[i1, i2] * A / deg self.evc -= np.dot(D_p, setup.X_p) self.ecc += setup.ExxC if not self.bandstructure: self.timer.stop('PAW correction') return Q = self.world.size // self.wfs.kd.comm.size self.exx_skn *= Q for kpt in self.wfs.kpt_u: for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) P_ni = kpt.P_ani[a] for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.exx_skn[kpt.s, kpt.k] -= \ (A * P_ni[:, i1].conj() * P_ni[:, i2]).real p12 = packed_index(i1, i2, ni) self.exx_skn[kpt.s, kpt.k] -= \ (P_ni[:, i1].conj() * setup.X_p[p12] * P_ni[:, i2]).real / self.wfs.nspins self.world.sum(self.exx_skn) self.exx_skn *= self.hybrid / Q self.timer.stop('PAW correction') def initialize_gaussian(self): """Calculate gaussian compensation charge and its potential. Used to decouple electrostatic interactions between periodically repeated images for molecular calculations. Charge containing one electron:: (beta/pi)^(3/2)*exp(-beta*r^2), its Fourier transform:: exp(-G^2/(4*beta)), and its potential:: erf(beta^0.5*r)/r. """ gd = self.wfs.gd # Set exponent of exp-function to -19 on the boundary: self.beta = 4 * 19 * (gd.icell_cv**2).sum(1).max() # Calculate gaussian: G_Gv = self.pd2.get_reciprocal_vectors() G2_G = self.pd2.G2_qG[0] C_v = gd.cell_cv.sum(0) / 2 # center of cell self.ngauss_G = np.exp(-1.0 / (4 * self.beta) * G2_G + 1j * np.dot(G_Gv, C_v)) / gd.dv # Calculate potential from gaussian: R_Rv = gd.get_grid_point_coordinates().transpose((1, 2, 3, 0)) r_R = ((R_Rv - C_v)**2).sum(3)**0.5 if (gd.N_c % 2 == 0).all(): r_R[tuple(gd.N_c // 2)] = 1.0 # avoid dividing by zero v_R = erf(self.beta**0.5 * r_R) / r_R if (gd.N_c % 2 == 0).all(): v_R[tuple(gd.N_c // 2)] = (4 * self.beta / pi)**0.5 self.vgauss_G = self.pd2.fft(v_R) # Compare self-interaction to analytic result: assert abs(0.5 * self.pd2.integrate(self.ngauss_G, self.vgauss_G) - (self.beta / 2 / pi)**0.5) < 1e-6
class HybridXC(HybridXCBase): orbital_dependent = True def __init__(self, name, hybrid=None, xc=None, alpha=None, gamma_point=1, method='standard', bandstructure=False, logfilename='-', bands=None, fcut=1e-10, molecule=False, qstride=1, world=None): """Mix standard functionals with exact exchange. name: str Name of functional: EXX, PBE0, HSE03, HSE06 hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. method: str Use 'standard' standard formula and 'acdf for adiabatic-connection dissipation fluctuation formula. alpha: float XXX describe gamma_point: bool 0: Skip k2-k1=0 interactions. 1: Use the alpha method. 2: Integrate the gamma point. bandstructure: bool Calculate bandstructure instead of just the total energy. bands: list of int List of bands to calculate bandstructure for. Default is all bands. molecule: bool Decouple electrostatic interactions between periodically repeated images. fcut: float Threshold for empty band. """ self.alpha = alpha self.fcut = fcut self.gamma_point = gamma_point self.method = method self.bandstructure = bandstructure self.bands = bands self.fd = logfilename self.write_timing_information = True HybridXCBase.__init__(self, name, hybrid, xc) # EXX energies: self.exx = None # total self.evv = None # valence-valence (pseudo part) self.evvacdf = None # valence-valence (pseudo part) self.devv = None # valence-valence (PAW correction) self.evc = None # valence-core self.ecc = None # core-core self.exx_skn = None # bandstructure self.qlatest = None if world is None: world = mpi.world self.world = world self.molecule = molecule if isinstance(qstride, int): qstride = [qstride] * 3 self.qstride_c = np.asarray(qstride) self.timer = Timer() def log(self, *args, **kwargs): prnt(file=self.fd, *args, **kwargs) self.fd.flush() def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, dens, ham, wfs, occupations): assert wfs.bd.comm.size == 1 self.xc.initialize(dens, ham, wfs, occupations) self.dens = dens self.wfs = wfs # Make a k-point descriptor that is not distributed # (self.kd.comm is serial_comm): self.kd = wfs.kd.copy() self.fd = logfile(self.fd, self.world.rank) wfs.initialize_wave_functions_from_restart_file() def set_positions(self, spos_ac): self.spos_ac = spos_ac def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx * self.hybrid def calculate_exx(self): """Non-selfconsistent calculation.""" self.timer.start('EXX') self.timer.start('Initialization') kd = self.kd wfs = self.wfs if fftw.FFTPlan is fftw.NumpyFFTPlan: self.log('NOT USING FFTW !!') self.log('Spins:', self.wfs.nspins) W = max(1, self.wfs.kd.comm.size // self.wfs.nspins) # Are the k-points distributed? kparallel = (W > 1) # Find number of occupied bands: self.nocc_sk = np.zeros((self.wfs.nspins, kd.nibzkpts), int) for kpt in self.wfs.kpt_u: for n, f in enumerate(kpt.f_n): if abs(f) < self.fcut: self.nocc_sk[kpt.s, kpt.k] = n break else: self.nocc_sk[kpt.s, kpt.k] = self.wfs.bd.nbands self.wfs.kd.comm.sum(self.nocc_sk) noccmin = self.nocc_sk.min() noccmax = self.nocc_sk.max() self.log('Number of occupied bands (min, max): %d, %d' % (noccmin, noccmax)) self.log('Number of valence electrons:', self.wfs.setups.nvalence) if self.bandstructure: self.log('Calculating eigenvalue shifts.') # allocate array for eigenvalue shifts: self.exx_skn = np.zeros( (self.wfs.nspins, kd.nibzkpts, self.wfs.bd.nbands)) if self.bands is None: noccmax = self.wfs.bd.nbands else: noccmax = max(max(self.bands) + 1, noccmax) N_c = self.kd.N_c vol = wfs.gd.dv * wfs.gd.N_c.prod() if self.alpha is None: alpha = 6 * vol**(2 / 3.0) / pi**2 else: alpha = self.alpha if self.gamma_point == 1: if alpha == 0.0: qvol = (2 * np.pi)**3 / vol / N_c.prod() self.gamma = 4 * np.pi * (3 * qvol / (4 * np.pi))**(1 / 3.) / qvol else: self.gamma = self.calculate_gamma(vol, alpha) else: kcell_cv = wfs.gd.cell_cv.copy() kcell_cv[0] *= N_c[0] kcell_cv[1] *= N_c[1] kcell_cv[2] *= N_c[2] self.gamma = madelung(kcell_cv) * vol * N_c.prod() / (4 * np.pi) self.log('Value of alpha parameter: %.3f Bohr^2' % alpha) self.log('Value of gamma parameter: %.3f Bohr^2' % self.gamma) # Construct all possible q=k2-k1 vectors: Nq_c = (N_c - 1) // self.qstride_c i_qc = np.indices(Nq_c * 2 + 1, float).transpose((1, 2, 3, 0)).reshape( (-1, 3)) self.bzq_qc = (i_qc - Nq_c) / N_c * self.qstride_c self.q0 = ((Nq_c * 2 + 1).prod() - 1) // 2 # index of q=(0,0,0) assert not self.bzq_qc[self.q0].any() # Count number of pairs for each q-vector: self.npairs_q = np.zeros(len(self.bzq_qc), int) for s in range(kd.nspins): for k1 in range(kd.nibzkpts): for k2 in range(kd.nibzkpts): for K2, q, n1_n, n2 in self.indices(s, k1, k2): self.npairs_q[q] += len(n1_n) self.npairs0 = self.npairs_q.sum() # total number of pairs self.log('Number of pairs:', self.npairs0) # Distribute q-vectors to Q processors: Q = self.world.size // self.wfs.kd.comm.size myrank = self.world.rank // self.wfs.kd.comm.size rank = 0 N = 0 myq = [] nq = 0 for q, n in enumerate(self.npairs_q): if n > 0: nq += 1 if rank == myrank: myq.append(q) N += n if N >= (rank + 1.0) * self.npairs0 / Q: rank += 1 assert len(myq) > 0, 'Too few q-vectors for too many processes!' self.bzq_qc = self.bzq_qc[myq] try: self.q0 = myq.index(self.q0) except ValueError: self.q0 = None self.log('%d x %d x %d k-points' % tuple(self.kd.N_c)) self.log('Distributing %d IBZ k-points over %d process(es).' % (kd.nibzkpts, self.wfs.kd.comm.size)) self.log('Distributing %d q-vectors over %d process(es).' % (nq, Q)) # q-point descriptor for my q-vectors: qd = KPointDescriptor(self.bzq_qc) # Plane-wave descriptor for all wave-functions: self.pd = PWDescriptor(wfs.pd.ecut, wfs.gd, dtype=wfs.pd.dtype, kd=kd) # Plane-wave descriptor pair-densities: self.pd2 = PWDescriptor(self.dens.pd2.ecut, self.dens.gd, dtype=wfs.dtype, kd=qd) self.log('Cutoff energies:') self.log(' Wave functions: %10.3f eV' % (self.pd.ecut * Hartree)) self.log(' Density: %10.3f eV' % (self.pd2.ecut * Hartree)) # Calculate 1/|G+q|^2 with special treatment of |G+q|=0: G2_qG = self.pd2.G2_qG if self.q0 is None: if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] else: self.iG2_qG = [ (1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG ] else: G2_qG[self.q0][0] = 117.0 # avoid division by zero if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] self.iG2_qG[self.q0][0] = self.gamma else: self.iG2_qG = [ (1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG ] self.iG2_qG[self.q0][0] = 1 / (4 * self.omega**2) G2_qG[self.q0][0] = 0.0 # restore correct value # Compensation charges: self.ghat = PWLFC([setup.ghat_l for setup in wfs.setups], self.pd2) self.ghat.set_positions(self.spos_ac) if self.molecule: self.initialize_gaussian() self.log('Value of beta parameter: %.3f 1/Bohr^2' % self.beta) self.timer.stop('Initialization') # Ready ... set ... go: self.t0 = time() self.npairs = 0 self.evv = 0.0 self.evvacdf = 0.0 for s in range(self.wfs.nspins): kpt1_q = [ KPoint(self.wfs, noccmax).initialize(kpt) for kpt in self.wfs.kpt_u if kpt.s == s ] kpt2_q = kpt1_q[:] if len(kpt1_q) == 0: # No s-spins on this CPU: continue # Send and receive ranks: srank = self.wfs.kd.get_rank_and_index(s, (kpt1_q[0].k - 1) % kd.nibzkpts)[0] rrank = self.wfs.kd.get_rank_and_index(s, (kpt1_q[-1].k + 1) % kd.nibzkpts)[0] # Shift k-points kd.nibzkpts - 1 times: for i in range(kd.nibzkpts): if i < kd.nibzkpts - 1: if kparallel: kpt = kpt2_q[-1].next(self.wfs) kpt.start_receiving(rrank) kpt2_q[0].start_sending(srank) else: kpt = kpt2_q[0] self.timer.start('Calculate') for kpt1, kpt2 in zip(kpt1_q, kpt2_q): # Loop over all k-points that k2 can be mapped to: for K2, q, n1_n, n2 in self.indices(s, kpt1.k, kpt2.k): self.apply(K2, q, kpt1, kpt2, n1_n, n2) self.timer.stop('Calculate') if i < kd.nibzkpts - 1: self.timer.start('Wait') if kparallel: kpt.wait() kpt2_q[0].wait() self.timer.stop('Wait') kpt2_q.pop(0) kpt2_q.append(kpt) self.evv = self.world.sum(self.evv) self.evvacdf = self.world.sum(self.evvacdf) self.calculate_exx_paw_correction() if self.method == 'standard': self.exx = self.evv + self.devv + self.evc + self.ecc elif self.method == 'acdf': self.exx = self.evvacdf + self.devv + self.evc + self.ecc else: 1 / 0 self.log('Exact exchange energy:') for txt, e in [('core-core', self.ecc), ('valence-core', self.evc), ('valence-valence (pseudo, acdf)', self.evvacdf), ('valence-valence (pseudo, standard)', self.evv), ('valence-valence (correction)', self.devv), ('total (%s)' % self.method, self.exx)]: self.log(' %-36s %14.6f eV' % (txt + ':', e * Hartree)) self.log('Total time: %10.3f seconds' % (time() - self.t0)) self.npairs = self.world.sum(self.npairs) assert self.npairs == self.npairs0 self.timer.stop('EXX') self.timer.write(self.fd) def calculate_gamma(self, vol, alpha): if self.molecule: return 0.0 N_c = self.kd.N_c offset_c = (N_c + 1) % 2 * 0.5 / N_c bzq_qc = monkhorst_pack(N_c) + offset_c qd = KPointDescriptor(bzq_qc) pd = PWDescriptor(self.wfs.pd.ecut, self.wfs.gd, kd=qd) gamma = (vol / (2 * pi)**2 * sqrt(pi / alpha) * self.kd.nbzkpts) for G2_G in pd.G2_qG: if G2_G[0] < 1e-7: G2_G = G2_G[1:] gamma -= np.dot(np.exp(-alpha * G2_G), G2_G**-1) return gamma / self.qstride_c.prod() def indices(self, s, k1, k2): """Generator for (K2, q, n1, n2) indices for (k1, k2) pair. s: int Spin index. k1: int Index of k-point in the IBZ. k2: int Index of k-point in the IBZ. Returns (K, q, n1_n, n2), where K then index of the k-point in the BZ that k2 is mapped to, q is the index of the q-vector between K and k1, and n1_n is a list of bands that should be combined with band n2.""" for K, k in enumerate(self.kd.bz2ibz_k): if k == k2: for K, q, n1_n, n2 in self._indices(s, k1, k2, K): yield K, q, n1_n, n2 def _indices(self, s, k1, k2, K2): k1_c = self.kd.ibzk_kc[k1] k2_c = self.kd.bzk_kc[K2] q_c = k2_c - k1_c q = abs(self.bzq_qc - q_c).sum(1).argmin() if abs(self.bzq_qc[q] - q_c).sum() > 1e-7: return if self.gamma_point == 0 and q == self.q0: return nocc1 = self.nocc_sk[s, k1] nocc2 = self.nocc_sk[s, k2] # Is k2 in the IBZ? is_ibz2 = (self.kd.ibz2bz_k[k2] == K2) for n2 in range(self.wfs.bd.nbands): # Find range of n1's (from n1a to n1b-1): if is_ibz2: # We get this combination twice, so let's only do half: if k1 >= k2: n1a = n2 else: n1a = n2 + 1 else: n1a = 0 n1b = self.wfs.bd.nbands if self.bandstructure: if n2 >= nocc2: n1b = min(n1b, nocc1) else: if n2 >= nocc2: break n1b = min(n1b, nocc1) if self.bands is not None: assert self.bandstructure n1_n = [] for n1 in range(n1a, n1b): if (n1 in self.bands and n2 < nocc2 or is_ibz2 and n2 in self.bands and n1 < nocc1): n1_n.append(n1) n1_n = np.array(n1_n) else: n1_n = np.arange(n1a, n1b) if len(n1_n) == 0: continue yield K2, q, n1_n, n2 def apply(self, K2, q, kpt1, kpt2, n1_n, n2): k20_c = self.kd.ibzk_kc[kpt2.k] k2_c = self.kd.bzk_kc[K2] if k2_c.any(): self.timer.start('Initialize plane waves') eik2r_R = self.wfs.gd.plane_wave(k2_c) eik20r_R = self.wfs.gd.plane_wave(k20_c) self.timer.stop('Initialize plane waves') else: eik2r_R = 1.0 eik20r_R = 1.0 w1 = self.kd.weight_k[kpt1.k] w2 = self.kd.weight_k[kpt2.k] # Is k2 in the 1. BZ? is_ibz2 = (self.kd.ibz2bz_k[kpt2.k] == K2) e_n = self.calculate_interaction(n1_n, n2, kpt1, kpt2, q, K2, eik20r_R, eik2r_R, is_ibz2) e_n *= 1.0 / self.kd.nbzkpts / self.wfs.nspins * self.qstride_c.prod() if q == self.q0: e_n[n1_n == n2] *= 0.5 f1_n = kpt1.f_n[n1_n] eps1_n = kpt1.eps_n[n1_n] f2 = kpt2.f_n[n2] eps2 = kpt2.eps_n[n2] s_n = np.sign(eps2 - eps1_n) evv = (f1_n * f2 * e_n).sum() evvacdf = 0.5 * (f1_n * (1 - s_n) * e_n + f2 * (1 + s_n) * e_n).sum() self.evv += evv * w1 self.evvacdf += evvacdf * w1 if is_ibz2: self.evv += evv * w2 self.evvacdf += evvacdf * w2 if self.bandstructure: x = self.wfs.nspins self.exx_skn[kpt1.s, kpt1.k, n1_n] += x * f2 * e_n if is_ibz2: self.exx_skn[kpt2.s, kpt2.k, n2] += x * np.dot(f1_n, e_n) def calculate_interaction(self, n1_n, n2, kpt1, kpt2, q, k, eik20r_R, eik2r_R, is_ibz2): """Calculate Coulomb interactions. For all n1 in the n1_n list, calculate interaction with n2.""" # number of plane waves: ng1 = self.wfs.ng_k[kpt1.k] ng2 = self.wfs.ng_k[kpt2.k] # Transform to real space and apply symmetry operation: self.timer.start('IFFT1') if is_ibz2: u2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) else: psit2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) * eik20r_R self.timer.start('Symmetry transform') u2_R = self.kd.transform_wave_function(psit2_R, k) / eik2r_R self.timer.stop() self.timer.stop() # Calculate pair densities: nt_nG = self.pd2.zeros(len(n1_n), q=q) for n1, nt_G in zip(n1_n, nt_nG): self.timer.start('IFFT2') u1_R = self.pd.ifft(kpt1.psit_nG[n1, :ng1], kpt1.k) self.timer.stop() nt_R = u1_R.conj() * u2_R self.timer.start('FFT') nt_G[:] = self.pd2.fft(nt_R, q) self.timer.stop() s = self.kd.sym_k[k] time_reversal = self.kd.time_reversal_k[k] k2_c = self.kd.ibzk_kc[kpt2.k] self.timer.start('Compensation charges') Q_anL = {} # coefficients for shape functions for a, P1_ni in kpt1.P_ani.items(): P1_ni = P1_ni[n1_n] if is_ibz2: P2_i = kpt2.P_ani[a][n2] else: b = self.kd.symmetry.a_sa[s, a] S_c = (np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s]) - self.spos_ac[b]) assert abs(S_c.round() - S_c).max() < 1e-5 if self.ghat.dtype == complex: x = np.exp(2j * pi * np.dot(k2_c, S_c)) else: x = 1.0 P2_i = np.dot(self.wfs.setups[a].R_sii[s], kpt2.P_ani[b][n2]) * x if time_reversal: P2_i = P2_i.conj() D_np = [] for P1_i in P1_ni: D_ii = np.outer(P1_i.conj(), P2_i) D_np.append(pack(D_ii)) Q_anL[a] = np.dot(D_np, self.wfs.setups[a].Delta_pL) self.timer.start('Expand') if q != self.qlatest: self.f_IG = self.ghat.expand(q) self.qlatest = q self.timer.stop('Expand') # Add compensation charges: self.ghat.add(nt_nG, Q_anL, q, self.f_IG) self.timer.stop('Compensation charges') if self.molecule and n2 in n1_n: nn = (n1_n == n2).nonzero()[0][0] nt_nG[nn] -= self.ngauss_G else: nn = None iG2_G = self.iG2_qG[q] # Calculate energies: e_n = np.empty(len(n1_n)) for n, nt_G in enumerate(nt_nG): e_n[n] = -4 * pi * np.real(self.pd2.integrate(nt_G, nt_G * iG2_G)) self.npairs += 1 if nn is not None: e_n[nn] -= 2 * (self.pd2.integrate(nt_nG[nn], self.vgauss_G) + (self.beta / 2 / pi)**0.5) if self.write_timing_information: t = (time() - self.t0) / len(n1_n) self.log('Time for first pair-density: %10.3f seconds' % t) self.log('Estimated total time: %10.3f seconds' % (t * self.npairs0 / self.world.size)) self.write_timing_information = False return e_n def calculate_exx_paw_correction(self): self.timer.start('PAW correction') self.devv = 0.0 self.evc = 0.0 self.ecc = 0.0 deg = 2 // self.wfs.nspins # spin degeneracy for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.devv -= D_ii[i1, i2] * A / deg self.evc -= np.dot(D_p, setup.X_p) self.ecc += setup.ExxC if not self.bandstructure: self.timer.stop('PAW correction') return Q = self.world.size // self.wfs.kd.comm.size self.exx_skn *= Q for kpt in self.wfs.kpt_u: for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) P_ni = kpt.P_ani[a] for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.exx_skn[kpt.s, kpt.k] -= \ (A * P_ni[:, i1].conj() * P_ni[:, i2]).real p12 = packed_index(i1, i2, ni) self.exx_skn[kpt.s, kpt.k] -= \ (P_ni[:, i1].conj() * setup.X_p[p12] * P_ni[:, i2]).real / self.wfs.nspins self.world.sum(self.exx_skn) self.exx_skn *= self.hybrid / Q self.timer.stop('PAW correction') def initialize_gaussian(self): """Calculate gaussian compensation charge and its potential. Used to decouple electrostatic interactions between periodically repeated images for molecular calculations. Charge containing one electron:: (beta/pi)^(3/2)*exp(-beta*r^2), its Fourier transform:: exp(-G^2/(4*beta)), and its potential:: erf(beta^0.5*r)/r. """ gd = self.wfs.gd # Set exponent of exp-function to -19 on the boundary: self.beta = 4 * 19 * (gd.icell_cv**2).sum(1).max() # Calculate gaussian: G_Gv = self.pd2.get_reciprocal_vectors() G2_G = self.pd2.G2_qG[0] C_v = gd.cell_cv.sum(0) / 2 # center of cell self.ngauss_G = np.exp(-1.0 / (4 * self.beta) * G2_G + 1j * np.dot(G_Gv, C_v)) / gd.dv # Calculate potential from gaussian: R_Rv = gd.get_grid_point_coordinates().transpose((1, 2, 3, 0)) r_R = ((R_Rv - C_v)**2).sum(3)**0.5 if (gd.N_c % 2 == 0).all(): r_R[tuple(gd.N_c // 2)] = 1.0 # avoid dividing by zero v_R = erf(self.beta**0.5 * r_R) / r_R if (gd.N_c % 2 == 0).all(): v_R[tuple(gd.N_c // 2)] = (4 * self.beta / pi)**0.5 self.vgauss_G = self.pd2.fft(v_R) # Compare self-interaction to analytic result: assert abs(0.5 * self.pd2.integrate(self.ngauss_G, self.vgauss_G) - (self.beta / 2 / pi)**0.5) < 1e-6
class 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()
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)
def get_xc(self): """Add xc part of the coupling matrix""" # shorthands paw = self.paw wfs = paw.wfs gd = paw.density.finegd comm = gd.comm eh_comm = self.eh_comm fg = self.finegrid is 2 kss = self.fullkss nij = len(kss) Om_xc = self.Om # initialize densities # nt_sg is the smooth density on the fine grid with spin index if kss.nvspins == 2: # spin polarised ground state calc. nt_sg = paw.density.nt_sg else: # spin unpolarised ground state calc. if kss.npspins == 2: # construct spin polarised densities nt_sg = np.array([.5 * paw.density.nt_sg[0], .5 * paw.density.nt_sg[0]]) else: nt_sg = paw.density.nt_sg # check if D_sp have been changed before D_asp = self.paw.density.D_asp for a, D_sp in D_asp.items(): if len(D_sp) != kss.npspins: if len(D_sp) == 1: D_asp[a] = np.array([0.5 * D_sp[0], 0.5 * D_sp[0]]) else: D_asp[a] = np.array([D_sp[0] + D_sp[1]]) # restrict the density if needed if fg: nt_s = nt_sg else: nt_s = self.gd.zeros(nt_sg.shape[0]) for s in range(nt_sg.shape[0]): self.restrict(nt_sg[s], nt_s[s]) gd = paw.density.gd # initialize vxc or fxc if self.derivativeLevel == 0: raise NotImplementedError if kss.npspins == 2: v_g = nt_sg[0].copy() else: v_g = nt_sg.copy() elif self.derivativeLevel == 1: pass elif self.derivativeLevel == 2: fxc_sg = np.zeros(nt_sg.shape) self.xc.calculate_fxc(gd, nt_sg, fxc_sg) else: raise ValueError('derivativeLevel can only be 0,1,2') # self.paw.my_nuclei = [] ns = self.numscale xc = self.xc print('XC', nij, 'transitions', file=self.txt) for ij in range(eh_comm.rank, nij, eh_comm.size): print('XC kss[' + '%d' % ij + ']', file=self.txt) timer = Timer() timer.start('init') timer2 = Timer() if self.derivativeLevel >= 1: # vxc is available # We use the numerical two point formula for calculating # the integral over fxc*n_ij. The results are # vvt_s smooth integral # nucleus.I_sp atom based correction matrices (pack2) # stored on each nucleus timer2.start('init v grids') vp_s = np.zeros(nt_s.shape, nt_s.dtype.char) vm_s = np.zeros(nt_s.shape, nt_s.dtype.char) if kss.npspins == 2: # spin polarised nv_s = nt_s.copy() nv_s[kss[ij].pspin] += ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vp_s) nv_s = nt_s.copy() nv_s[kss[ij].pspin] -= ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vm_s) else: # spin unpolarised nv = nt_s + ns * kss[ij].get(fg) xc.calculate(gd, nv, vp_s) nv = nt_s - ns * kss[ij].get(fg) xc.calculate(gd, nv, vm_s) vvt_s = (0.5 / ns) * (vp_s - vm_s) timer2.stop() # initialize the correction matrices timer2.start('init v corrections') I_asp = {} for a, P_ni in wfs.kpt_u[kss[ij].spin].P_ani.items(): # create the modified density matrix Pi_i = P_ni[kss[ij].i] Pj_i = P_ni[kss[ij].j] P_ii = np.outer(Pi_i, Pj_i) # we need the symmetric form, hence we can pack P_p = pack(P_ii) D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] -= ns * P_p setup = wfs.setups[a] I_sp = np.zeros_like(D_sp) self.xc.calculate_paw_correction(setup, D_sp, I_sp) I_sp *= -1.0 D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] += ns * P_p self.xc.calculate_paw_correction(setup, D_sp, I_sp) I_sp /= 2.0 * ns I_asp[a] = I_sp timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) for kq in range(ij, nij): weight = self.weight_Kijkq(ij, kq) if self.derivativeLevel == 0: # only Exc is available if kss.npspins == 2: # spin polarised nv_g = nt_sg.copy() nv_g[kss[ij].pspin] += kss[ij].get(fg) nv_g[kss[kq].pspin] += kss[kq].get(fg) Excpp = xc.get_energy_and_potential( nv_g[0], v_g, nv_g[1], v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] += kss[ij].get(fg) nv_g[kss[kq].pspin] -= kss[kq].get(fg) Excpm = xc.get_energy_and_potential( nv_g[0], v_g, nv_g[1], v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -=\ kss[ij].get(fg) nv_g[kss[kq].pspin] +=\ kss[kq].get(fg) Excmp = xc.get_energy_and_potential( nv_g[0], v_g, nv_g[1], v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -= \ kss[ij].get(fg) nv_g[kss[kq].pspin] -=\ kss[kq].get(fg) Excpp = xc.get_energy_and_potential( nv_g[0], v_g, nv_g[1], v_g) else: # spin unpolarised nv_g = nt_sg + ns * kss[ij].get(fg)\ + ns * kss[kq].get(fg) Excpp = xc.get_energy_and_potential(nv_g, v_g) nv_g = nt_sg + ns * kss[ij].get(fg)\ - ns * kss[kq].get(fg) Excpm = xc.get_energy_and_potential(nv_g, v_g) nv_g = nt_sg - ns * kss[ij].get(fg)\ + ns * kss[kq].get(fg) Excmp = xc.get_energy_and_potential(nv_g, v_g) nv_g = nt_sg - ns * kss[ij].get(fg)\ - ns * kss[kq].get(fg) Excmm = xc.get_energy_and_potential(nv_g, v_g) Om_xc[ij, kq] += weight *\ 0.25 * \ (Excpp - Excmp - Excpm + Excmm) / (ns * ns) elif self.derivativeLevel == 1: # vxc is available timer2.start('integrate') Om_xc[ij, kq] += weight *\ self.gd.integrate(kss[kq].get(fg) * vvt_s[kss[kq].pspin]) timer2.stop() timer2.start('integrate corrections') Exc = 0. for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): # create the modified density matrix Pk_i = P_ni[kss[kq].i] Pq_i = P_ni[kss[kq].j] P_ii = np.outer(Pk_i, Pq_i) # we need the symmetric form, hence we can pack # use pack as I_sp used pack2 P_p = pack(P_ii) Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) Om_xc[ij, kq] += weight * self.gd.comm.sum(Exc) timer2.stop() elif self.derivativeLevel == 2: # fxc is available if kss.npspins == 2: # spin polarised Om_xc[ij, kq] += weight *\ gd.integrate(kss[ij].get(fg) * kss[kq].get(fg) * fxc_sg[kss[ij].pspin, kss[kq].pspin]) else: # spin unpolarised Om_xc[ij, kq] += weight *\ gd.integrate(kss[ij].get(fg) * kss[kq].get(fg) * fxc_sg) # XXX still numeric derivatives for local terms timer2.start('integrate corrections') Exc = 0. for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): # create the modified density matrix Pk_i = P_ni[kss[kq].i] Pq_i = P_ni[kss[kq].j] P_ii = np.outer(Pk_i, Pq_i) # we need the symmetric form, hence we can pack # use pack as I_sp used pack2 P_p = pack(P_ii) Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) Om_xc[ij, kq] += weight * self.gd.comm.sum(Exc) timer2.stop() if ij != kq: Om_xc[kq, ij] = Om_xc[ij, kq] timer.stop() # timer2.write() if ij < (nij - 1): print('XC estimated time left', self.time_left(timer, t0, ij, nij), file=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()
def get_rpa(self): """calculate RPA part of the omega matrix""" # shorthands kss = self.fullkss finegrid = self.finegrid wfs = self.paw.wfs eh_comm = self.eh_comm # calculate omega matrix nij = len(kss) print('RPA', nij, 'transitions', file=self.txt) Om = self.Om for ij in range(eh_comm.rank, nij, eh_comm.size): print('RPA 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.char) 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() # print "shapes 0=",phit.shape,rhot.shape 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 = 2 * sqrt(kss[ij].get_energy() * kss[kq].get_energy() * kss[ij].get_weight() * kss[kq].get_weight()) I = self.Coulomb_integral_kss(kss[ij], kss[kq], rhot, phit, timer2) Om[ij, kq] = pre * I if ij == kq: Om[ij, kq] += kss[ij].get_energy() ** 2 else: Om[kq, ij] = Om[ij, kq] timer.stop() # timer2.write() if ij < (nij - 1): t = timer.get_time(ij) # time for nij-ij calculations t = .5 * t * \ (nij - ij) # estimated time for n*(n+1)/2, n=nij-(ij+1) print('RPA estimated time left', self.timestring(t0 * (nij - ij - 1) + t), file=self.txt)
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!')