def __init__(self, filename, td_potential=None, propagator='SICN', propagator_kwargs=None, solver='CSCG', tolerance=1e-8, **kwargs): """Create TDDFT-object. Parameters: ----------- filename: string File containing ground state or time-dependent state to propagate td_potential: class, optional Function class for the time-dependent potential. Must have a method 'strength(time)' which returns the strength of the linear potential to each direction as a vector of three floats. propagator: {'SICN','ETRSCN','ECN','SITE','SIKE4','SIKE5','SIKE6'} Name of the time propagator for the Kohn-Sham wavefunctions solver: {'CSCG','BiCGStab'} Name of the iterative linear equations solver for time propagation tolerance: float Tolerance for the linear solver The following parameters can be used: `txt`, `parallel`, `communicator` `mixer` and `dtype`. The internal parameters `mixer` and `dtype` are strictly used to specify a dummy mixer and complex type respectively. """ # Set initial time self.time = 0.0 # Set initial kick strength self.kick_strength = np.array([0.0, 0.0, 0.0], dtype=float) # Set initial value of iteration counter self.niter = 0 # Override default `mixer` and `dtype` given in InputParameters kwargs.setdefault('mixer', DummyMixer()) kwargs.setdefault('dtype', complex) # Parallelization dictionary should also default to strided bands parallel = kwargs.setdefault('parallel', {}) parallel.setdefault('stridebands', True) # Initialize paw-object without density mixing # NB: TDDFT restart files contain additional information which # will override the initial settings for time/kick/niter. GPAW.__init__(self, filename, **kwargs) assert isinstance(self.wfs, TimeDependentWaveFunctions) assert isinstance(self.wfs.overlap, TimeDependentOverlap) # Prepare for dipole moment file handle self.dm_file = None # Initialize wavefunctions and density # (necessary after restarting from file) self.set_positions() # Don't be too strict self.density.charge_eps = 1e-5 wfs = self.wfs self.rank = wfs.world.rank self.text('') self.text('') self.text('------------------------------------------') self.text(' Time-propagation TDDFT ') self.text('------------------------------------------') self.text('') self.text('Charge epsilon: ', self.density.charge_eps) # Time-dependent variables and operators self.td_potential = td_potential self.td_hamiltonian = TimeDependentHamiltonian(self.wfs, self.atoms, self.hamiltonian, td_potential) self.td_overlap = self.wfs.overlap #TODO remove this property self.td_density = TimeDependentDensity(self) # Solver for linear equations self.text('Solver: ', solver) if solver == 'BiCGStab': self.solver = BiCGStab(gd=wfs.gd, timer=self.timer, tolerance=tolerance) elif solver == 'CSCG': self.solver = CSCG(gd=wfs.gd, timer=self.timer, tolerance=tolerance) else: raise RuntimeError('Solver %s not supported.' % solver) # Preconditioner # No preconditioner as none good found self.text('Preconditioner: ', 'None') self.preconditioner = None #TODO! check out SSOR preconditioning #self.preconditioner = InverseOverlapPreconditioner(self.overlap) #self.preconditioner = KineticEnergyPreconditioner(wfs.gd, self.td_hamiltonian.hamiltonian.kin, np.complex) # Time propagator self.text('Propagator: ', propagator) if propagator_kwargs is None: propagator_kwargs = {} if propagator == 'ECN': self.propagator = ExplicitCrankNicolson(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SICN': self.propagator = SemiImplicitCrankNicolson(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'EFSICN': self.propagator = EhrenfestPAWSICN(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'EFSICN_HGH': self.propagator = EhrenfestHGHSICN(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'ETRSCN': self.propagator = EnforcedTimeReversalSymmetryCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SITE': self.propagator = SemiImplicitTaylorExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SIKE': self.propagator = SemiImplicitKrylovExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator.startswith('SITE') or propagator.startswith('SIKE'): raise DeprecationWarning('Use propagator_kwargs to specify degree.') else: raise RuntimeError('Time propagator %s not supported.' % propagator) if self.rank == 0: if wfs.kpt_comm.size > 1: if wfs.nspins == 2: self.text('Parallelization Over Spin') if wfs.gd.comm.size > 1: self.text('Using Domain Decomposition: %d x %d x %d' % tuple(wfs.gd.parsize_c)) if wfs.band_comm.size > 1: self.text('Parallelization Over bands on %d Processors' % wfs.band_comm.size) self.text('States per processor = ', wfs.bd.mynbands) self.hpsit = None self.eps_tmp = None self.mblas = MultiBlas(wfs.gd)
class TDDFT(GPAW): """Time-dependent density functional theory calculation based on GPAW. This class is the core class of the time-dependent density functional theory implementation and is the only class which a user has to use. """ def __init__(self, filename, td_potential=None, propagator='SICN', propagator_kwargs=None, solver='CSCG', tolerance=1e-8, **kwargs): """Create TDDFT-object. Parameters: ----------- filename: string File containing ground state or time-dependent state to propagate td_potential: class, optional Function class for the time-dependent potential. Must have a method 'strength(time)' which returns the strength of the linear potential to each direction as a vector of three floats. propagator: {'SICN','ETRSCN','ECN','SITE','SIKE4','SIKE5','SIKE6'} Name of the time propagator for the Kohn-Sham wavefunctions solver: {'CSCG','BiCGStab'} Name of the iterative linear equations solver for time propagation tolerance: float Tolerance for the linear solver The following parameters can be used: `txt`, `parallel`, `communicator` `mixer` and `dtype`. The internal parameters `mixer` and `dtype` are strictly used to specify a dummy mixer and complex type respectively. """ # Set initial time self.time = 0.0 # Set initial kick strength self.kick_strength = np.array([0.0, 0.0, 0.0], dtype=float) # Set initial value of iteration counter self.niter = 0 # Override default `mixer` and `dtype` given in InputParameters kwargs.setdefault('mixer', DummyMixer()) kwargs.setdefault('dtype', complex) # Parallelization dictionary should also default to strided bands parallel = kwargs.setdefault('parallel', {}) parallel.setdefault('stridebands', True) # Initialize paw-object without density mixing # NB: TDDFT restart files contain additional information which # will override the initial settings for time/kick/niter. GPAW.__init__(self, filename, **kwargs) assert isinstance(self.wfs, TimeDependentWaveFunctions) assert isinstance(self.wfs.overlap, TimeDependentOverlap) # Prepare for dipole moment file handle self.dm_file = None # Initialize wavefunctions and density # (necessary after restarting from file) self.set_positions() # Don't be too strict self.density.charge_eps = 1e-5 wfs = self.wfs self.rank = wfs.world.rank self.text('') self.text('') self.text('------------------------------------------') self.text(' Time-propagation TDDFT ') self.text('------------------------------------------') self.text('') self.text('Charge epsilon: ', self.density.charge_eps) # Time-dependent variables and operators self.td_potential = td_potential self.td_hamiltonian = TimeDependentHamiltonian(self.wfs, self.atoms, self.hamiltonian, td_potential) self.td_overlap = self.wfs.overlap #TODO remove this property self.td_density = TimeDependentDensity(self) # Solver for linear equations self.text('Solver: ', solver) if solver == 'BiCGStab': self.solver = BiCGStab(gd=wfs.gd, timer=self.timer, tolerance=tolerance) elif solver == 'CSCG': self.solver = CSCG(gd=wfs.gd, timer=self.timer, tolerance=tolerance) else: raise RuntimeError('Solver %s not supported.' % solver) # Preconditioner # No preconditioner as none good found self.text('Preconditioner: ', 'None') self.preconditioner = None #TODO! check out SSOR preconditioning #self.preconditioner = InverseOverlapPreconditioner(self.overlap) #self.preconditioner = KineticEnergyPreconditioner(wfs.gd, self.td_hamiltonian.hamiltonian.kin, np.complex) # Time propagator self.text('Propagator: ', propagator) if propagator_kwargs is None: propagator_kwargs = {} if propagator == 'ECN': self.propagator = ExplicitCrankNicolson(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SICN': self.propagator = SemiImplicitCrankNicolson(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'EFSICN': self.propagator = EhrenfestPAWSICN(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'EFSICN_HGH': self.propagator = EhrenfestHGHSICN(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'ETRSCN': self.propagator = EnforcedTimeReversalSymmetryCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SITE': self.propagator = SemiImplicitTaylorExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SIKE': self.propagator = SemiImplicitKrylovExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator.startswith('SITE') or propagator.startswith('SIKE'): raise DeprecationWarning('Use propagator_kwargs to specify degree.') else: raise RuntimeError('Time propagator %s not supported.' % propagator) if self.rank == 0: if wfs.kpt_comm.size > 1: if wfs.nspins == 2: self.text('Parallelization Over Spin') if wfs.gd.comm.size > 1: self.text('Using Domain Decomposition: %d x %d x %d' % tuple(wfs.gd.parsize_c)) if wfs.band_comm.size > 1: self.text('Parallelization Over bands on %d Processors' % wfs.band_comm.size) self.text('States per processor = ', wfs.bd.mynbands) self.hpsit = None self.eps_tmp = None self.mblas = MultiBlas(wfs.gd) def set(self, **kwargs): p = self.input_parameters # Special treatment for dictionary parameters: for name in ['parallel']: if kwargs.get(name) is not None: tmp = p[name] for key in kwargs[name]: if not key in tmp: raise KeyError('Unknown subparameter "%s" in ' 'dictionary parameter "%s"' % (key, name)) tmp.update(kwargs[name]) kwargs[name] = tmp for key in kwargs: # Only whitelisted arguments can be changed after initialization if self.initialized and key not in ['txt']: raise TypeError("Keyword argument '%s' is immutable." % key) if key in ['txt', 'parallel', 'communicator','poissonsolver']: continue elif key == 'mixer': if not isinstance(kwargs[key], DummyMixer): raise ValueError("Mixer must be of type DummyMixer.") elif key == 'dtype': if kwargs[key] is not complex: raise ValueError("TDDFT calculation must be complex.") 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 read(self, reader): assert reader.has_array('PseudoWaveFunctions') GPAW.read(self, reader) def propagate(self, time_step, iterations, dipole_moment_file=None, restart_file=None, dump_interval=100): """Propagates wavefunctions. Parameters ---------- time_step: float Time step in attoseconds (10^-18 s), e.g., 4.0 or 8.0 iterations: integer Iterations, e.g., 20 000 as / 4.0 as = 5000 dipole_moment_file: string, optional Name of the data file where to the time-dependent dipole moment is saved restart_file: string, optional Name of the restart file dump_interval: integer After how many iterations restart data is dumped """ if self.rank == 0: self.text() self.text('Starting time: %7.2f as' % (self.time * autime_to_attosec)) self.text('Time step: %7.2f as' % time_step) header = """\ Simulation Total log10 Iterations: Time time Energy Norm Propagator""" self.text() self.text(header) # Convert to atomic units time_step = time_step * attosec_to_autime if dipole_moment_file is not None: self.initialize_dipole_moment_file(dipole_moment_file) niterpropagator = 0 maxiter = self.niter + iterations self.timer.start('Propagate') while self.niter < maxiter: norm = self.density.finegd.integrate(self.density.rhot_g) # Write dipole moment at every iteration if dipole_moment_file is not None: self.update_dipole_moment_file(norm) # print output (energy etc.) every 10th iteration if self.niter % 10 == 0: self.get_td_energy() T = time.localtime() if self.rank == 0: iter_text = 'iter: %3d %02d:%02d:%02d %11.2f' \ ' %13.6f %9.1f %10d' self.text(iter_text % (self.niter, T[3], T[4], T[5], self.time * autime_to_attosec, self.Etot, log(abs(norm)+1e-16)/log(10), niterpropagator)) self.txt.flush() # Propagate the Kohn-Shame wavefunctions a single timestep niterpropagator = self.propagator.propagate(self.time, time_step) self.time += time_step self.niter += 1 # Call registered callback functions self.call_observers(self.niter) # Write restart data if restart_file is not None and self.niter % dump_interval == 0: self.write(restart_file, 'all') if self.rank == 0: print 'Wrote restart file.' print self.niter, ' iterations done. Current time is ', \ self.time * autime_to_attosec, ' as.' self.timer.stop('Propagate') # Write final results and close dipole moment file if dipole_moment_file is not None: #TODO final iteration is propagated, but nothing is updated #norm = self.density.finegd.integrate(self.density.rhot_g) #self.finalize_dipole_moment_file(norm) self.finalize_dipole_moment_file() # Call registered callback functions self.call_observers(self.niter, final=True) if restart_file is not None: self.write(restart_file, 'all') def initialize_dipole_moment_file(self, dipole_moment_file): if self.rank == 0: if self.dm_file is not None and not self.dm_file.closed: raise RuntimeError('Dipole moment file is already open') if self.time == 0.0: mode = 'w' else: # We probably continue from restart mode = 'a' self.dm_file = file(dipole_moment_file, mode) # If the dipole moment file is empty, add a header if self.dm_file.tell() == 0: header = '# Kick = [%22.12le, %22.12le, %22.12le]\n' \ % (self.kick_strength[0], self.kick_strength[1], \ self.kick_strength[2]) header += '# %15s %15s %22s %22s %22s\n' \ % ('time', 'norm', 'dmx', 'dmy', 'dmz') self.dm_file.write(header) self.dm_file.flush() def update_dipole_moment_file(self, norm): dm = self.density.finegd.calculate_dipole_moment(self.density.rhot_g) if self.rank == 0: line = '%20.8lf %20.8le %22.12le %22.12le %22.12le\n' \ % (self.time, norm, dm[0], dm[1], dm[2]) self.dm_file.write(line) self.dm_file.flush() def finalize_dipole_moment_file(self, norm=None): if norm is not None: self.update_dipole_moment_file(norm) if self.rank == 0: self.dm_file.close() self.dm_file = None def get_td_energy(self): """Calculate the time-dependent total energy""" self.td_overlap.update(self.wfs) self.td_density.update() self.td_hamiltonian.update(self.td_density.get_density(), self.time) kpt_u = self.wfs.kpt_u if self.hpsit is None: self.hpsit = self.wfs.gd.zeros(len(kpt_u[0].psit_nG), dtype=complex) if self.eps_tmp is None: self.eps_tmp = np.zeros(len(kpt_u[0].eps_n), dtype=complex) # self.Eband = sum_i <psi_i|H|psi_j> for kpt in kpt_u: self.td_hamiltonian.apply(kpt, kpt.psit_nG, self.hpsit, calculate_P_ani=False) self.mblas.multi_zdotc(self.eps_tmp, kpt.psit_nG, self.hpsit, len(kpt_u[0].psit_nG)) self.eps_tmp *= self.wfs.gd.dv kpt.eps_n[:] = self.eps_tmp.real self.occupations.calculate_band_energy(self.wfs) H = self.td_hamiltonian.hamiltonian # Nonlocal self.Enlxc = 0.0#xcfunc.get_non_local_energy() self.Enlkin = H.xc.get_kinetic_energy_correction() # PAW self.Ekin = H.Ekin0 + self.occupations.e_band + self.Enlkin self.Epot = H.Epot self.Eext = H.Eext self.Ebar = H.Ebar self.Exc = H.Exc + self.Enlxc self.Etot = self.Ekin + self.Epot + self.Ebar + self.Exc return self.Etot def set_absorbing_boundary(self, absorbing_boundary): self.td_hamiltonian.set_absorbing_boundary(absorbing_boundary) # exp(ip.r) psi def absorption_kick(self, kick_strength): """Delta absorption kick for photoabsorption spectrum. Parameters ---------- kick_strength: [float, float, float] Strength of the kick, e.g., [0.0, 0.0, 1e-3] """ if self.rank == 0: self.text('Delta kick = ', kick_strength) self.kick_strength = np.array(kick_strength) abs_kick_hamiltonian = AbsorptionKickHamiltonian(self.wfs, self.atoms, np.array(kick_strength, float)) abs_kick = AbsorptionKick(self.wfs, abs_kick_hamiltonian, self.td_overlap, self.solver, self.preconditioner, self.wfs.gd, self.timer) abs_kick.kick() def __del__(self): """Destructor""" GPAW.__del__(self)
def __init__(self, filename, td_potential=None, propagator='SICN', calculate_energy=True, propagator_kwargs=None, solver='CSCG', tolerance=1e-8, **kwargs): """Create TDDFT-object. Parameters: filename: string File containing ground state or time-dependent state to propagate td_potential: class, optional Function class for the time-dependent potential. Must have a method 'strength(time)' which returns the strength of the linear potential to each direction as a vector of three floats. propagator: {'SICN','ETRSCN','ECN','SITE','SIKE4','SIKE5','SIKE6'} Name of the time propagator for the Kohn-Sham wavefunctions solver: {'CSCG','BiCGStab'} Name of the iterative linear equations solver for time propagation tolerance: float Tolerance for the linear solver The following parameters can be used: `txt`, `parallel`, `communicator` `mixer` and `dtype`. The internal parameters `mixer` and `dtype` are strictly used to specify a dummy mixer and complex type respectively. """ # Set initial time self.time = 0.0 # Set initial kick strength self.kick_strength = np.array([0.0, 0.0, 0.0], dtype=float) # Set initial value of iteration counter self.niter = 0 # Parallelization dictionary should default to strided bands self.default_parallel = GPAW.default_parallel.copy() self.default_parallel['stridebands'] = True self.default_parameters = GPAW.default_parameters.copy() self.default_parameters['mixer'] = DummyMixer() # NB: TDDFT restart files contain additional information which # will override the initial settings for time/kick/niter. GPAW.__init__(self, filename, **kwargs) assert isinstance(self.wfs, TimeDependentWaveFunctions) assert isinstance(self.wfs.overlap, TimeDependentOverlap) # Prepare for dipole moment file handle self.dm_file = None # Initialize wavefunctions and density # (necessary after restarting from file) if not self.initialized: self.initialize() self.set_positions() # Don't be too strict self.density.charge_eps = 1e-5 wfs = self.wfs self.rank = wfs.world.rank self.text = self.log self.text('') self.text('') self.text('------------------------------------------') self.text(' Time-propagation TDDFT ') self.text('------------------------------------------') self.text('') self.text('Charge epsilon: ', self.density.charge_eps) # Time-dependent variables and operators self.td_potential = td_potential self.td_hamiltonian = TimeDependentHamiltonian(self.wfs, self.spos_ac, self.hamiltonian, td_potential) self.td_overlap = self.wfs.overlap # TODO remove this property self.td_density = TimeDependentDensity(self) # Solver for linear equations self.text('Solver: ', solver) if solver == 'BiCGStab': self.solver = BiCGStab(gd=wfs.gd, timer=self.timer, tolerance=tolerance) elif solver == 'CSCG': self.solver = CSCG(gd=wfs.gd, timer=self.timer, tolerance=tolerance) else: raise RuntimeError('Solver %s not supported.' % solver) # Preconditioner # No preconditioner as none good found self.text('Preconditioner: ', 'None') self.preconditioner = None # TODO! check out SSOR preconditioning # self.preconditioner = InverseOverlapPreconditioner(self.overlap) # self.preconditioner = KineticEnergyPreconditioner( # wfs.gd, self.td_hamiltonian.hamiltonian.kin, np.complex) # Time propagator self.text('Propagator: ', propagator) if propagator_kwargs is None: propagator_kwargs = {} if propagator == 'ECN': self.propagator = ExplicitCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SICN': self.propagator = SemiImplicitCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'EFSICN': self.propagator = EhrenfestPAWSICN(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'EFSICN_HGH': self.propagator = EhrenfestHGHSICN(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'ETRSCN': self.propagator = EnforcedTimeReversalSymmetryCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SITE': self.propagator = SemiImplicitTaylorExponential( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SIKE': self.propagator = SemiImplicitKrylovExponential( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator.startswith('SITE') or propagator.startswith('SIKE'): raise DeprecationWarning( 'Use propagator_kwargs to specify degree.') else: raise RuntimeError('Time propagator %s not supported.' % propagator) if self.rank == 0: if wfs.kd.comm.size > 1: if wfs.nspins == 2: self.text('Parallelization Over Spin') if wfs.gd.comm.size > 1: self.text('Using Domain Decomposition: %d x %d x %d' % tuple(wfs.gd.parsize_c)) if wfs.bd.comm.size > 1: self.text('Parallelization Over bands on %d Processors' % wfs.bd.comm.size) self.text('States per processor = ', wfs.bd.mynbands) self.hpsit = None self.eps_tmp = None self.mblas = MultiBlas(wfs.gd) # Restarting an FDTD run generates hamiltonian.fdtd_poisson, which # now overwrites hamiltonian.poisson if hasattr(self.hamiltonian, 'fdtd_poisson'): self.hamiltonian.poisson = self.hamiltonian.fdtd_poisson self.hamiltonian.poisson.set_grid_descriptor(self.density.finegd) # For electrodynamics mode if self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT': self.initialize_FDTD() self.hamiltonian.poisson.print_messages(self.text) self.log.flush() self.calculate_energy = calculate_energy if self.hamiltonian.xc.name.startswith('GLLB'): self.text('GLLB model potential. Not updating energy.') self.calculate_energy = False
class TDDFT(GPAW): """Time-dependent density functional theory calculation based on GPAW. This class is the core class of the time-dependent density functional theory implementation and is the only class which a user has to use. """ def __init__(self, filename, td_potential=None, propagator='SICN', calculate_energy=True, propagator_kwargs=None, solver='CSCG', tolerance=1e-8, **kwargs): """Create TDDFT-object. Parameters: filename: string File containing ground state or time-dependent state to propagate td_potential: class, optional Function class for the time-dependent potential. Must have a method 'strength(time)' which returns the strength of the linear potential to each direction as a vector of three floats. propagator: {'SICN','ETRSCN','ECN','SITE','SIKE4','SIKE5','SIKE6'} Name of the time propagator for the Kohn-Sham wavefunctions solver: {'CSCG','BiCGStab'} Name of the iterative linear equations solver for time propagation tolerance: float Tolerance for the linear solver The following parameters can be used: `txt`, `parallel`, `communicator` `mixer` and `dtype`. The internal parameters `mixer` and `dtype` are strictly used to specify a dummy mixer and complex type respectively. """ # Set initial time self.time = 0.0 # Set initial kick strength self.kick_strength = np.array([0.0, 0.0, 0.0], dtype=float) # Set initial value of iteration counter self.niter = 0 # Parallelization dictionary should default to strided bands self.default_parallel = GPAW.default_parallel.copy() self.default_parallel['stridebands'] = True self.default_parameters = GPAW.default_parameters.copy() self.default_parameters['mixer'] = DummyMixer() # NB: TDDFT restart files contain additional information which # will override the initial settings for time/kick/niter. GPAW.__init__(self, filename, **kwargs) assert isinstance(self.wfs, TimeDependentWaveFunctions) assert isinstance(self.wfs.overlap, TimeDependentOverlap) # Prepare for dipole moment file handle self.dm_file = None # Initialize wavefunctions and density # (necessary after restarting from file) if not self.initialized: self.initialize() self.set_positions() # Don't be too strict self.density.charge_eps = 1e-5 wfs = self.wfs self.rank = wfs.world.rank self.text = self.log self.text('') self.text('') self.text('------------------------------------------') self.text(' Time-propagation TDDFT ') self.text('------------------------------------------') self.text('') self.text('Charge epsilon: ', self.density.charge_eps) # Time-dependent variables and operators self.td_potential = td_potential self.td_hamiltonian = TimeDependentHamiltonian(self.wfs, self.spos_ac, self.hamiltonian, td_potential) self.td_overlap = self.wfs.overlap # TODO remove this property self.td_density = TimeDependentDensity(self) # Solver for linear equations self.text('Solver: ', solver) if solver == 'BiCGStab': self.solver = BiCGStab(gd=wfs.gd, timer=self.timer, tolerance=tolerance) elif solver == 'CSCG': self.solver = CSCG(gd=wfs.gd, timer=self.timer, tolerance=tolerance) else: raise RuntimeError('Solver %s not supported.' % solver) # Preconditioner # No preconditioner as none good found self.text('Preconditioner: ', 'None') self.preconditioner = None # TODO! check out SSOR preconditioning # self.preconditioner = InverseOverlapPreconditioner(self.overlap) # self.preconditioner = KineticEnergyPreconditioner( # wfs.gd, self.td_hamiltonian.hamiltonian.kin, np.complex) # Time propagator self.text('Propagator: ', propagator) if propagator_kwargs is None: propagator_kwargs = {} if propagator == 'ECN': self.propagator = ExplicitCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SICN': self.propagator = SemiImplicitCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'EFSICN': self.propagator = EhrenfestPAWSICN(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'EFSICN_HGH': self.propagator = EhrenfestHGHSICN(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'ETRSCN': self.propagator = EnforcedTimeReversalSymmetryCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SITE': self.propagator = SemiImplicitTaylorExponential( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator == 'SIKE': self.propagator = SemiImplicitKrylovExponential( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, **propagator_kwargs) elif propagator.startswith('SITE') or propagator.startswith('SIKE'): raise DeprecationWarning( 'Use propagator_kwargs to specify degree.') else: raise RuntimeError('Time propagator %s not supported.' % propagator) if self.rank == 0: if wfs.kd.comm.size > 1: if wfs.nspins == 2: self.text('Parallelization Over Spin') if wfs.gd.comm.size > 1: self.text('Using Domain Decomposition: %d x %d x %d' % tuple(wfs.gd.parsize_c)) if wfs.bd.comm.size > 1: self.text('Parallelization Over bands on %d Processors' % wfs.bd.comm.size) self.text('States per processor = ', wfs.bd.mynbands) self.hpsit = None self.eps_tmp = None self.mblas = MultiBlas(wfs.gd) # Restarting an FDTD run generates hamiltonian.fdtd_poisson, which # now overwrites hamiltonian.poisson if hasattr(self.hamiltonian, 'fdtd_poisson'): self.hamiltonian.poisson = self.hamiltonian.fdtd_poisson self.hamiltonian.poisson.set_grid_descriptor(self.density.finegd) # For electrodynamics mode if self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT': self.initialize_FDTD() self.hamiltonian.poisson.print_messages(self.text) self.log.flush() self.calculate_energy = calculate_energy if self.hamiltonian.xc.name.startswith('GLLB'): self.text('GLLB model potential. Not updating energy.') self.calculate_energy = False def create_wave_functions(self, mode, *args, **kwargs): mode = FDTDDFTMode(mode.nn, mode.interpolation, True) GPAW.create_wave_functions(self, mode, *args, **kwargs) def read(self, filename): reader = GPAW.read(self, filename) if 'tddft' in reader: self.time = reader.tddft.time self.niter = reader.tddft.niter self.kick_strength = reader.tddft.kick_strength def initialize(self, reading=False): self.parameters.mixer = DummyMixer() self.parameters.experimental['reuse_wfs_method'] = None GPAW.initialize(self, reading=reading) def _write(self, writer, mode): GPAW._write(self, writer, mode) writer.child('tddft').write(time=self.time, niter=self.niter, kick_strength=self.kick_strength) # Electrodynamics requires extra care def initialize_FDTD(self): # Sanity check assert (self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT') self.hamiltonian.poisson.set_density(self.density) # The propagate calculation_mode causes classical part to evolve # in time when self.hamiltonian.poisson.solve(...) is called self.hamiltonian.poisson.set_calculation_mode('propagate') # During each time step, self.hamiltonian.poisson.solve may be called # several times (depending on the used propagator). Using the # attached observer one ensures that actual propagation takes # place only once. This is because # the FDTDPoissonSolver changes the calculation_mode from propagate to # something else when the propagation is finished. self.attach(self.hamiltonian.poisson.set_calculation_mode, 1, 'propagate') def propagate(self, time_step, iterations, dipole_moment_file=None, restart_file=None, dump_interval=100): """Propagates wavefunctions. Parameters: time_step: float Time step in attoseconds (10^-18 s), e.g., 4.0 or 8.0 iterations: integer Iterations, e.g., 20 000 as / 4.0 as = 5000 dipole_moment_file: string, optional Name of the data file where to the time-dependent dipole moment is saved restart_file: string, optional Name of the restart file dump_interval: integer After how many iterations restart data is dumped """ if self.rank == 0: self.text() self.text('Starting time: %7.2f as' % (self.time * autime_to_attosec)) self.text('Time step: %7.2f as' % time_step) header = """\ Simulation Total log10 Iterations: Time time Energy (eV) Norm Propagator""" self.text() self.text(header) # Convert to atomic units time_step = time_step * attosec_to_autime if dipole_moment_file is not None: self.initialize_dipole_moment_file(dipole_moment_file) # Set these as class properties for use of observers self.time_step = time_step self.dump_interval = dump_interval niterpropagator = 0 self.tdmaxiter = self.niter + iterations # Let FDTD part know the time step if self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT': self.hamiltonian.poisson.set_time(self.time) self.hamiltonian.poisson.set_time_step(self.time_step) self.timer.start('Propagate') while self.niter < self.tdmaxiter: norm = self.density.finegd.integrate(self.density.rhot_g) # Write dipole moment at every iteration if dipole_moment_file is not None: self.update_dipole_moment_file(norm) # print output (energy etc.) every 10th iteration if self.niter % 10 == 0: self.get_td_energy() T = time.localtime() if self.rank == 0: iter_text = 'iter: %3d %02d:%02d:%02d %11.2f' \ ' %13.6f %9.1f %10d' self.text( iter_text % (self.niter, T[3], T[4], T[5], self.time * autime_to_attosec, self.Etot * aufrequency_to_eV, log(abs(norm) + 1e-16) / log(10), niterpropagator)) self.log.flush() # Propagate the Kohn-Shame wavefunctions a single timestep niterpropagator = self.propagator.propagate(self.time, time_step) self.time += time_step self.niter += 1 # Call registered callback functions self.call_observers(self.niter) # Write restart data if restart_file is not None and self.niter % dump_interval == 0: self.write(restart_file, 'all') if self.rank == 0: print('Wrote restart file.') print(self.niter, ' iterations done. Current time is ', self.time * autime_to_attosec, ' as.') self.timer.stop('Propagate') # Write final results and close dipole moment file if dipole_moment_file is not None: # TODO final iteration is propagated, but nothing is updated # norm = self.density.finegd.integrate(self.density.rhot_g) # self.finalize_dipole_moment_file(norm) self.finalize_dipole_moment_file() # Finalize FDTDPoissonSolver if self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT': self.hamiltonian.poisson.finalize_propagation() # Call registered callback functions self.call_observers(self.niter, final=True) if restart_file is not None: self.write(restart_file, 'all') def initialize_dipole_moment_file(self, dipole_moment_file): if self.rank == 0: if self.dm_file is not None and not self.dm_file.closed: raise RuntimeError('Dipole moment file is already open') if self.time == 0.0: mode = 'w' else: # We probably continue from restart mode = 'a' self.dm_file = open(dipole_moment_file, mode) # If the dipole moment file is empty, add a header if self.dm_file.tell() == 0: header = '# Kick = [%22.12le, %22.12le, %22.12le]\n' \ % (self.kick_strength[0], self.kick_strength[1], self.kick_strength[2]) header += '# %15s %15s %22s %22s %22s\n' \ % ('time', 'norm', 'dmx', 'dmy', 'dmz') self.dm_file.write(header) self.dm_file.flush() def update_dipole_moment_file(self, norm): dm = self.density.finegd.calculate_dipole_moment(self.density.rhot_g) if self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT': dm += self.hamiltonian.poisson.get_classical_dipole_moment() if self.rank == 0: line = '%20.8lf %20.8le %22.12le %22.12le %22.12le\n' \ % (self.time, norm, dm[0], dm[1], dm[2]) self.dm_file.write(line) self.dm_file.flush() def finalize_dipole_moment_file(self, norm=None): if norm is not None: self.update_dipole_moment_file(norm) if self.rank == 0: self.dm_file.close() self.dm_file = None def update_eigenvalues(self): kpt_u = self.wfs.kpt_u if self.hpsit is None: self.hpsit = self.wfs.gd.zeros(len(kpt_u[0].psit_nG), dtype=complex) if self.eps_tmp is None: self.eps_tmp = np.zeros(len(kpt_u[0].eps_n), dtype=complex) # self.Eband = sum_i <psi_i|H|psi_j> for kpt in kpt_u: self.td_hamiltonian.apply(kpt, kpt.psit_nG, self.hpsit, calculate_P_ani=False) self.mblas.multi_zdotc(self.eps_tmp, kpt.psit_nG, self.hpsit, len(kpt_u[0].psit_nG)) self.eps_tmp *= self.wfs.gd.dv kpt.eps_n[:] = self.eps_tmp.real self.occupations.calculate_band_energy(self.wfs) H = self.td_hamiltonian.hamiltonian # Nonlocal self.Enlkin = H.xc.get_kinetic_energy_correction() # PAW self.Ekin = H.e_kinetic0 + self.occupations.e_band + self.Enlkin self.e_coulomb = H.e_coulomb self.Eext = H.e_external self.Ebar = H.e_zero self.Exc = H.e_xc self.Etot = self.Ekin + self.e_coulomb + self.Ebar + self.Exc def get_td_energy(self): """Calculate the time-dependent total energy""" if not self.calculate_energy: self.Etot = 0.0 return 0.0 self.td_overlap.update(self.wfs) self.td_density.update() self.td_hamiltonian.update(self.td_density.get_density(), self.time) self.update_eigenvalues() return self.Etot def set_absorbing_boundary(self, absorbing_boundary): self.td_hamiltonian.set_absorbing_boundary(absorbing_boundary) # exp(ip.r) psi def absorption_kick(self, kick_strength): """Delta absorption kick for photoabsorption spectrum. Parameters: kick_strength: [float, float, float] Strength of the kick, e.g., [0.0, 0.0, 1e-3] """ if self.rank == 0: self.text('Delta kick = ', kick_strength) self.kick_strength = np.array(kick_strength) abs_kick_hamiltonian = AbsorptionKickHamiltonian( self.wfs, self.spos_ac, np.array(kick_strength, float)) abs_kick = AbsorptionKick(self.wfs, abs_kick_hamiltonian, self.td_overlap, self.solver, self.preconditioner, self.wfs.gd, self.timer) abs_kick.kick() # Kick the classical part, if it is present if self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT': self.hamiltonian.poisson.set_kick(kick=self.kick_strength)
def __init__(self, ground_state_file=None, txt='-', td_potential=None, propagator='SICN', solver='CSCG', tolerance=1e-8, parsize=None, parsize_bands=1, parstride_bands=True, communicator=None): """Create TDDFT-object. Parameters: ----------- ground_state_file: string File name for the ground state data td_potential: class, optional Function class for the time-dependent potential. Must have a method 'strength(time)' which returns the strength of the linear potential to each direction as a vector of three floats. propagator: {'SICN','ETRSCN','ECN','SITE','SIKE4','SIKE5','SIKE6'} Name of the time propagator for the Kohn-Sham wavefunctions solver: {'CSCG','BiCGStab'} Name of the iterative linear equations solver for time propagation tolerance: float Tolerance for the linear solver """ if ground_state_file is None: raise RuntimeError('TDDFT calculation has to start from converged ' 'ground or excited state restart file') # Set initial time self.time = 0.0 # Set initial kick strength self.kick_strength = np.array([0.0, 0.0, 0.0], dtype=float) # Set initial value of iteration counter self.niter = 0 # Initialize paw-object without density mixing # NB: TDDFT restart files contain additional information which # will override the initial settings for time/kick/niter. GPAW.__init__(self, ground_state_file, txt=txt, mixer=DummyMixer(), parallel={'domain': parsize, 'band': parsize_bands, 'stridebands': parstride_bands}, communicator=communicator, dtype=complex) # Prepare for dipole moment file handle self.dm_file = None # Initialize wavefunctions and density # (necessary after restarting from file) self.set_positions() # Don't be too strict self.density.charge_eps = 1e-5 wfs = self.wfs self.rank = wfs.world.rank # Convert PAW-object to complex if wfs.dtype == float: raise DeprecationWarning('This should not happen.') wfs.dtype = complex from gpaw.fd_operators import Laplace nn = self.input_parameters.stencils[0] wfs.kin = Laplace(wfs.gd, -0.5, nn, complex) wfs.pt = LFC(wfs.gd, [setup.pt_j for setup in wfs.setups], self.kpt_comm, dtype=complex) for kpt in wfs.kpt_u: for a,P_ni in kpt.P_ani.items(): assert not np.isnan(P_ni).any() kpt.P_ani[a] = np.array(P_ni, complex) self.set_positions() # Wave functions for kpt in wfs.kpt_u: kpt.psit_nG = np.array(kpt.psit_nG[:], complex) self.text('') self.text('') self.text('------------------------------------------') self.text(' Time-propagation TDDFT ') self.text('------------------------------------------') self.text('') self.text('Charge epsilon: ', self.density.charge_eps) # Time-dependent variables and operators self.td_potential = td_potential self.td_hamiltonian = TimeDependentHamiltonian(self.wfs, self.atoms, self.hamiltonian, td_potential) self.td_overlap = TimeDependentOverlap(self.wfs) self.td_density = TimeDependentDensity(self) # Solver for linear equations self.text('Solver: ', solver) if solver is 'BiCGStab': self.solver = BiCGStab(gd=wfs.gd, timer=self.timer, tolerance=tolerance) elif solver is 'CSCG': self.solver = CSCG(gd=wfs.gd, timer=self.timer, tolerance=tolerance) else: raise RuntimeError('Solver %s not supported.' % solver) # Preconditioner # No preconditioner as none good found self.text('Preconditioner: ', 'None') self.preconditioner = None #TODO! check out SSOR preconditioning #self.preconditioner = InverseOverlapPreconditioner(self.overlap) #self.preconditioner = KineticEnergyPreconditioner(wfs.gd, self.td_hamiltonian.hamiltonian.kin, np.complex) # Time propagator self.text('Propagator: ', propagator) if propagator is 'ECN': self.propagator = ExplicitCrankNicolson(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer) elif propagator is 'SICN': self.propagator = SemiImplicitCrankNicolson(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer) elif propagator is 'ETRSCN': self.propagator = EnforcedTimeReversalSymmetryCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer) elif propagator in ['SITE4', 'SITE']: self.propagator = SemiImplicitTaylorExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, degree = 4) elif propagator in ['SIKE4', 'SIKE']: self.propagator = SemiImplicitKrylovExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, degree = 4) elif propagator is 'SIKE5': self.propagator = SemiImplicitKrylovExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, degree = 5) elif propagator is 'SIKE6': self.propagator = SemiImplicitKrylovExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, degree = 6) else: raise RuntimeError('Time propagator %s not supported.' % propagator) if self.rank == 0: if wfs.kpt_comm.size > 1: if wfs.nspins == 2: self.text('Parallelization Over Spin') if wfs.gd.comm.size > 1: self.text('Using Domain Decomposition: %d x %d x %d' % tuple(wfs.gd.parsize_c)) if wfs.band_comm.size > 1: self.text('Parallelization Over bands on %d Processors' % wfs.band_comm.size) self.text('States per processor = ', wfs.mynbands) self.hpsit = None self.eps_tmp = None self.mblas = MultiBlas(wfs.gd)
class TDDFT(GPAW): """Time-dependent density functional theory calculation based on GPAW. This class is the core class of the time-dependent density functional theory implementation and is the only class which a user has to use. """ def __init__(self, ground_state_file=None, txt='-', td_potential=None, propagator='SICN', solver='CSCG', tolerance=1e-8, parsize=None, parsize_bands=1, parstride_bands=True, communicator=None): """Create TDDFT-object. Parameters: ----------- ground_state_file: string File name for the ground state data td_potential: class, optional Function class for the time-dependent potential. Must have a method 'strength(time)' which returns the strength of the linear potential to each direction as a vector of three floats. propagator: {'SICN','ETRSCN','ECN','SITE','SIKE4','SIKE5','SIKE6'} Name of the time propagator for the Kohn-Sham wavefunctions solver: {'CSCG','BiCGStab'} Name of the iterative linear equations solver for time propagation tolerance: float Tolerance for the linear solver """ if ground_state_file is None: raise RuntimeError('TDDFT calculation has to start from converged ' 'ground or excited state restart file') # Set initial time self.time = 0.0 # Set initial kick strength self.kick_strength = np.array([0.0, 0.0, 0.0], dtype=float) # Set initial value of iteration counter self.niter = 0 # Initialize paw-object without density mixing # NB: TDDFT restart files contain additional information which # will override the initial settings for time/kick/niter. GPAW.__init__(self, ground_state_file, txt=txt, mixer=DummyMixer(), parallel={'domain': parsize, 'band': parsize_bands, 'stridebands': parstride_bands}, communicator=communicator, dtype=complex) # Prepare for dipole moment file handle self.dm_file = None # Initialize wavefunctions and density # (necessary after restarting from file) self.set_positions() # Don't be too strict self.density.charge_eps = 1e-5 wfs = self.wfs self.rank = wfs.world.rank # Convert PAW-object to complex if wfs.dtype == float: raise DeprecationWarning('This should not happen.') wfs.dtype = complex from gpaw.fd_operators import Laplace nn = self.input_parameters.stencils[0] wfs.kin = Laplace(wfs.gd, -0.5, nn, complex) wfs.pt = LFC(wfs.gd, [setup.pt_j for setup in wfs.setups], self.kpt_comm, dtype=complex) for kpt in wfs.kpt_u: for a,P_ni in kpt.P_ani.items(): assert not np.isnan(P_ni).any() kpt.P_ani[a] = np.array(P_ni, complex) self.set_positions() # Wave functions for kpt in wfs.kpt_u: kpt.psit_nG = np.array(kpt.psit_nG[:], complex) self.text('') self.text('') self.text('------------------------------------------') self.text(' Time-propagation TDDFT ') self.text('------------------------------------------') self.text('') self.text('Charge epsilon: ', self.density.charge_eps) # Time-dependent variables and operators self.td_potential = td_potential self.td_hamiltonian = TimeDependentHamiltonian(self.wfs, self.atoms, self.hamiltonian, td_potential) self.td_overlap = TimeDependentOverlap(self.wfs) self.td_density = TimeDependentDensity(self) # Solver for linear equations self.text('Solver: ', solver) if solver is 'BiCGStab': self.solver = BiCGStab(gd=wfs.gd, timer=self.timer, tolerance=tolerance) elif solver is 'CSCG': self.solver = CSCG(gd=wfs.gd, timer=self.timer, tolerance=tolerance) else: raise RuntimeError('Solver %s not supported.' % solver) # Preconditioner # No preconditioner as none good found self.text('Preconditioner: ', 'None') self.preconditioner = None #TODO! check out SSOR preconditioning #self.preconditioner = InverseOverlapPreconditioner(self.overlap) #self.preconditioner = KineticEnergyPreconditioner(wfs.gd, self.td_hamiltonian.hamiltonian.kin, np.complex) # Time propagator self.text('Propagator: ', propagator) if propagator is 'ECN': self.propagator = ExplicitCrankNicolson(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer) elif propagator is 'SICN': self.propagator = SemiImplicitCrankNicolson(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer) elif propagator is 'ETRSCN': self.propagator = EnforcedTimeReversalSymmetryCrankNicolson( self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer) elif propagator in ['SITE4', 'SITE']: self.propagator = SemiImplicitTaylorExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, degree = 4) elif propagator in ['SIKE4', 'SIKE']: self.propagator = SemiImplicitKrylovExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, degree = 4) elif propagator is 'SIKE5': self.propagator = SemiImplicitKrylovExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, degree = 5) elif propagator is 'SIKE6': self.propagator = SemiImplicitKrylovExponential(self.td_density, self.td_hamiltonian, self.td_overlap, self.solver, self.preconditioner, wfs.gd, self.timer, degree = 6) else: raise RuntimeError('Time propagator %s not supported.' % propagator) if self.rank == 0: if wfs.kpt_comm.size > 1: if wfs.nspins == 2: self.text('Parallelization Over Spin') if wfs.gd.comm.size > 1: self.text('Using Domain Decomposition: %d x %d x %d' % tuple(wfs.gd.parsize_c)) if wfs.band_comm.size > 1: self.text('Parallelization Over bands on %d Processors' % wfs.band_comm.size) self.text('States per processor = ', wfs.mynbands) self.hpsit = None self.eps_tmp = None self.mblas = MultiBlas(wfs.gd) def read(self, reader): assert reader.has_array('PseudoWaveFunctions') GPAW.read(self, reader) def propagate(self, time_step, iterations, dipole_moment_file=None, restart_file=None, dump_interval=100): """Propagates wavefunctions. Parameters ---------- time_step: float Time step in attoseconds (10^-18 s), e.g., 4.0 or 8.0 iterations: integer Iterations, e.g., 20 000 as / 4.0 as = 5000 dipole_moment_file: string, optional Name of the data file where to the time-dependent dipole moment is saved restart_file: string, optional Name of the restart file dump_interval: integer After how many iterations restart data is dumped """ if self.rank == 0: self.text() self.text('Starting time: %7.2f as' % (self.time * autime_to_attosec)) self.text('Time step: %7.2f as' % time_step) header = """\ Simulation Total log10 Iterations: Time time Energy Norm Propagator""" self.text() self.text(header) # Convert to atomic units time_step = time_step * attosec_to_autime if dipole_moment_file is not None: self.initialize_dipole_moment_file(dipole_moment_file) niterpropagator = 0 maxiter = self.niter + iterations self.timer.start('Propagate') while self.niter < maxiter: norm = self.density.finegd.integrate(self.density.rhot_g) # Write dipole moment at every iteration if dipole_moment_file is not None: self.update_dipole_moment_file(norm) # print output (energy etc.) every 10th iteration if self.niter % 10 == 0: self.get_td_energy() T = time.localtime() if self.rank == 0: iter_text = 'iter: %3d %02d:%02d:%02d %11.2f' \ ' %13.6f %9.1f %10d' self.text(iter_text % (self.niter, T[3], T[4], T[5], self.time * autime_to_attosec, self.Etot, log(abs(norm)+1e-16)/log(10), niterpropagator)) self.txt.flush() # Propagate the Kohn-Shame wavefunctions a single timestep niterpropagator = self.propagator.propagate(self.wfs.kpt_u, self.time, time_step) self.time += time_step self.niter += 1 # Call registered callback functions self.call_observers(self.niter) # Write restart data if restart_file is not None and self.niter % dump_interval == 0: self.write(restart_file, 'all') if self.rank == 0: print 'Wrote restart file.' print self.niter, ' iterations done. Current time is ', \ self.time * autime_to_attosec, ' as.' self.timer.stop('Propagate') # Write final results and close dipole moment file if dipole_moment_file is not None: #TODO final iteration is propagated, but nothing is updated #norm = self.density.finegd.integrate(self.density.rhot_g) #self.finalize_dipole_moment_file(norm) self.finalize_dipole_moment_file() # Call registered callback functions self.call_observers(self.niter, final=True) if restart_file is not None: self.write(restart_file, 'all') def initialize_dipole_moment_file(self, dipole_moment_file): if self.rank == 0: if self.dm_file is not None and not self.dm_file.closed: raise RuntimeError('Dipole moment file is already open') if self.time == 0.0: mode = 'w' else: # We probably continue from restart mode = 'a' self.dm_file = file(dipole_moment_file, mode) # If the dipole moment file is empty, add a header if self.dm_file.tell() == 0: header = '# Kick = [%22.12le, %22.12le, %22.12le]\n' \ % (self.kick_strength[0], self.kick_strength[1], \ self.kick_strength[2]) header += '# %15s %15s %22s %22s %22s\n' \ % ('time', 'norm', 'dmx', 'dmy', 'dmz') self.dm_file.write(header) self.dm_file.flush() def update_dipole_moment_file(self, norm): dm = self.density.finegd.calculate_dipole_moment(self.density.rhot_g) if self.rank == 0: line = '%20.8lf %20.8le %22.12le %22.12le %22.12le\n' \ % (self.time, norm, dm[0], dm[1], dm[2]) self.dm_file.write(line) self.dm_file.flush() def finalize_dipole_moment_file(self, norm=None): if norm is not None: self.update_dipole_moment_file(norm) if self.rank == 0: self.dm_file.close() self.dm_file = None def get_td_energy(self): """Calculate the time-dependent total energy""" self.td_overlap.update() self.td_density.update() self.td_hamiltonian.update(self.td_density.get_density(), self.time) kpt_u = self.wfs.kpt_u if self.hpsit is None: self.hpsit = self.wfs.gd.zeros(len(kpt_u[0].psit_nG), dtype=complex) if self.eps_tmp is None: self.eps_tmp = np.zeros(len(kpt_u[0].eps_n), dtype=complex) # self.Eband = sum_i <psi_i|H|psi_j> for kpt in kpt_u: self.td_hamiltonian.apply(kpt, kpt.psit_nG, self.hpsit, calculate_P_ani=False) self.mblas.multi_zdotc(self.eps_tmp, kpt.psit_nG, self.hpsit, len(kpt_u[0].psit_nG)) self.eps_tmp *= self.wfs.gd.dv kpt.eps_n[:] = self.eps_tmp.real self.occupations.calculate_band_energy(self.wfs) H = self.td_hamiltonian.hamiltonian # Nonlocal self.Enlxc = 0.0#xcfunc.get_non_local_energy() self.Enlkin = H.xc.get_kinetic_energy_correction() # PAW self.Ekin = H.Ekin0 + self.occupations.e_band + self.Enlkin self.Epot = H.Epot self.Eext = H.Eext self.Ebar = H.Ebar self.Exc = H.Exc + self.Enlxc self.Etot = self.Ekin + self.Epot + self.Ebar + self.Exc return self.Etot def set_absorbing_boundary(self, absorbing_boundary): self.td_hamiltonian.set_absorbing_boundary(absorbing_boundary) # exp(ip.r) psi def absorption_kick(self, kick_strength): """Delta absorption kick for photoabsorption spectrum. Parameters ---------- kick_strength: [float, float, float] Strength of the kick, e.g., [0.0, 0.0, 1e-3] """ if self.rank == 0: self.text('Delta kick = ', kick_strength) self.kick_strength = np.array(kick_strength) abs_kick_hamiltonian = AbsorptionKickHamiltonian(self.wfs, self.atoms, np.array(kick_strength, float)) abs_kick = AbsorptionKick(self.wfs, abs_kick_hamiltonian, self.td_overlap, self.solver, self.preconditioner, self.wfs.gd, self.timer) abs_kick.kick(self.wfs.kpt_u) def __del__(self): """Destructor""" GPAW.__del__(self)