def spawn_backward(child, current_time, end_time, dt): """Propagates the child backwards in time until the current time is reached.""" nstep = int(round( np.abs((current_time-end_time) / dt) )) back_time = current_time for i in range(nstep): step.step_trajectory(child, back_time, dt) back_time = back_time + dt log.print_message('spawn_back', [back_time])
def step_trajectory(traj, init_time, dt): """Propagates a single trajectory. Used to backward/forward propagate a trajectory during spawning. NOTE: step_wavefunction and step_trajectory could/should probably be integrated somehow... """ current_time = init_time end_time = init_time + dt time_step = dt min_time_step = abs(dt / 2.**5) while not step_complete(current_time, end_time, time_step): # save the wavefunction from previous step in case step rejected traj0 = traj.copy() # propagate single trajectory glbl.modules['propagator'].propagate_trajectory(traj, time_step) # update the couplings for the trajectory glbl.modules['interface'].evaluate_coupling(traj) # update current time proposed_time = current_time + time_step # check time_step is fine, energy/amplitude conserved accept = check_step_trajectory(traj0, traj) # if everything is ok.. if accept: current_time = proposed_time else: # redo time step # recall -- this time trying to propagate # to the failed step time_step *= 0.5 if abs(time_step) < min_time_step: log.print_message('general', ['minimum time step exceeded -- STOPPING.']) raise ValueError('Trajectory minimum step exceeded.') # reset the beginning of the time step and go to beginning of loop traj = traj0.copy()
def set_initial_coords(wfn): """Takes initial position and momentum from geometry specified in input""" coords = glbl.properties['init_coords'] ndim = coords.shape[-1] log.print_message('string',[' Initial coordinates taken from input file(s).\n']) for coord in coords: itraj = trajectory.Trajectory(glbl.properties['n_states'], ndim, width=glbl.properties['crd_widths'], mass=glbl.properties['crd_masses'], parent=0, kecoef=glbl.modules['integrals'].kecoef) # set position and momentum itraj.update_x(np.array(coord[0])) itraj.update_p(np.array(coord[1])) # add a single trajectory specified by geometry.dat wfn.add_trajectory(itraj)
def set_initial_state(wfn): """Sets the initial state of the trajectories in the bundle.""" if glbl.properties['init_brightest']: # initialize to the state with largest transition dipole moment # set all states to the ground state for i in range(wfn.n_traj()): wfn.traj[i].state = 0 # compute transition dipoles evaluate.update_pes_traj(wfn.traj[i]) # set the initial state to the one with largest t. dip. for i in range(wfn.n_traj()): if 'dipole' not in wfn.traj[i].pes.avail_data(): raise KeyError('trajectory ' + str(i) + ': Cannot set state by transition moments - ' + 'dipole not in pes.avail_data()') tr_dipole = wfn.traj[i].pes.get_data('dipole') tdip = np.array([ np.linalg.norm(tr_dipole[:, 0, j]) for j in range(1, glbl.properties['n_states']) ]) log.print_message('general', ['Initializing trajectory '+str(i)+ ' to state '+str(np.argmax(tdip)+1)+ ' | tr. dipople array='+np.array2string(tdip, \ formatter={'float_kind':lambda x: "%.4f" % x})]) wfn.traj[i].state = np.argmax(tdip) + 1 elif len(glbl.properties['init_state']) == wfn.n_traj(): # use "init_state" to set the initial state for i in range(wfn.n_traj()): istate = glbl.properties['init_state'][i] if istate < 0: wfn.traj[i].state = glbl.properties['n_states'] + istate else: wfn.traj[i].state = istate else: raise ValueError('Ambiguous initial state assignment.')
def spawn_forward(parent, child_state, initial_time, dt): """Propagates the parent forward (into the future) until the coupling decreases.""" parent_state = parent.state current_time = initial_time spawn_time = initial_time exit_time = initial_time child_created = False parent_at_spawn = None child_at_spawn = None coup = np.zeros(3) log.print_message('spawn_start', [parent.label, parent_state, child_state]) while True: coup = np.roll(coup,1) coup[0] = abs(parent.coupling(parent_state, child_state)) child_attempt = parent.copy() child_attempt.state = child_state adjust_success = utils.adjust_child(parent, child_attempt, parent.derivative(parent_state, child_state)) sij = abs(glbl.modules['integrals'].nuc_overlap(parent, child_attempt)) # if the coupling has already peaked, either we exit with a successful # spawn from previous step, or we exit with a fail if np.all(coup[0] < coup[1:]): sp_str = 'no [decreasing coupling]' log.print_message('spawn_step', [current_time, coup[0], sij, sp_str]) if child_created: log.print_message('spawn_success', [spawn_time]) child_at_spawn.exit_time[parent_state] = current_time else: log.print_message('spawn_failure', [current_time]) # exit, we're done trying to spawn exit_time = current_time break # coupling still increasing else: # try to set up the child if not adjust_success: sp_str = 'no [momentum adjust fail]' elif sij < glbl.properties['spawn_olap_thresh']: sp_str = 'no [overlap too small]' elif not np.all(coup[0] > coup[1:]): sp_str = 'no [decreasing coupling]' else: spawn_time = current_time child_created = True parent_at_spawn = parent.copy() child_at_spawn = child_attempt.copy() child_at_spawn.last_spawn[parent_state] = spawn_time child_at_spawn.amplitude = 0j child_at_spawn.parent = parent.label child_at_spawn.label = parent.label sp_str = 'yes' log.print_message('spawn_step', [current_time, coup[0], sij, sp_str]) step.step_trajectory(parent, current_time, dt) current_time = current_time + dt return child_created, child_at_spawn, parent_at_spawn, spawn_time, exit_time
def spawn(wfn, dt): """Propagates to the point of maximum coupling, spawns a new basis function, then propagates the function to the current time.""" global coup_hist basis_grown = False current_time = wfn.time # list of added trajectories # we want to know the history of the coupling for each trajectory # in order to assess spawning criteria -- make sure coup_hist has a slot # for every trajectory if len(coup_hist) < wfn.n_traj(): n_add = wfn.n_traj() - len(coup_hist) for i in range(n_add): coup_hist.append(np.zeros((glbl.properties['n_states'], 3))) # iterate over all trajectories in bundle for i in range(wfn.n_traj()): # only live trajectories can spawn if not wfn.traj[i].alive: continue for st in range(glbl.properties['n_states']): # can only spawn to different electronic states if wfn.traj[i].state == st: continue # compute magnitude of coupling to state j coup = abs(wfn.traj[i].coupling(wfn.traj[i].state, st)) coup_hist[i][st,:] = np.roll(coup_hist[i][st,:],1) coup_hist[i][st,0] = coup # if we satisfy spawning conditions, begin spawn process if spawn_trajectory(wfn, i, st, coup_hist[i][st,:], current_time): # we're going to messing with this trajectory -- mess with a copy parent = wfn.traj[i].copy() # propagate the parent forward in time until coupling maximized [success, child, parent_spawn, spawn_time, exit_time] = spawn_forward(parent, st, current_time, dt) # set the spawn attempt in wfn, even if spawn failed (avoid repeated fails) wfn.traj[i].last_spawn[st] = spawn_time wfn.traj[i].exit_time[st] = exit_time if success: # at this point, child is at the spawn point. Propagate # backwards in time until we reach the current time child_spawn = child.copy() # need electronic structure at current geometry -- on correct state evaluate.update_pes_traj(child) spawn_backward(child, spawn_time, current_time, -dt) bundle_overlap = utils.overlap_with_bundle(child, wfn) if not bundle_overlap: basis_grown = True wfn.add_trajectory(child) child_spawn.label = wfn.traj[-1].label # a little hacky... utils.write_spawn_log(current_time, spawn_time, exit_time, parent_spawn, child_spawn) else: log.print_message('spawn_bad_step', ['overlap with bundle too large']) # let caller known if the basis has been changed return basis_grown
def init_interface(): """Reads the operator file. Note that the order of active modes is determined by the init_coords read from the input file. """ global ham # Read in operator file ham = VibHam() ham.mlbl_active = [lbl.lower() for lbl in glbl.properties['crd_labels']] ham.nmode_active = len(glbl.properties['crd_labels']) # operator file will always be a separate file #ham.rdoperfile(glbl.home_path + '/' + glbl.vibronic['opfile']) ham.rdoperfile(glbl.vibronic['opfile']) # Ouput some information about the Hamiltonian log.print_message('string', ['*'*72]) log.print_message('string', ['* Vibronic Coupling Hamiltonian Information']) log.print_message('string', ['*'*72]) log.print_message('string', ['Operator file: ' + glbl.vibronic['opfile']]) log.print_message('string', ['Number of Hamiltonian terms: ' + str(ham.nterms)]) string = 'Total no. modes: ' + str(ham.nmode_total) log.print_message('string', [string]) string = 'No. active modes: ' + str(ham.nmode_active) log.print_message('string', [string]) log.print_message('string', ['Active mode labels:']) for i in range(ham.nmode_active): string = str(i+1) + ' ' + ham.mlbl_active[i] log.print_message('string', [string]) log.print_message('string', ['Active mode frequencies (a.u.):']) for i in range(ham.nmode_active): string = str(i+1) + ' ' + str(ham.freq[i]) log.print_message('string', [string])
def set_initial_coords(wfn): """Samples a v=0 Wigner distribution.""" # Set the coordinate type: Cartesian or normal mode coordinates if glbl.methods['interface'] == 'vibronic': coordtype = 'normal' ham = glbl.modules['interface'].ham else: coordtype = 'cart' log.print_message('string', [' sampling from v=0 Wigner distribution.\n']) # if multiple geometries, just take the first one coords = glbl.properties['init_coords'] ndim = coords.shape[-1] x_ref = coords[0, 0] p_ref = coords[0, 1] w_vec = glbl.properties['crd_widths'] m_vec = glbl.properties['crd_masses'] # create template trajectory basis function template = trajectory.Trajectory(glbl.properties['n_states'], ndim, width=w_vec, mass=m_vec, parent=0, kecoef=glbl.modules['integrals'].kecoef) template.update_x(x_ref) template.update_p(p_ref) # If Cartesian coordinates are being used, then set up the # mass-weighted Hessian and diagonalise to obtain the normal modes # and frequencies if coordtype == 'cart': iface_dict = glbl.sections[glbl.methods['interface']] hessian = iface_dict['hessian'] invmass = np.asarray([ 1. / np.sqrt(m_vec[i]) if m_vec[i] != 0. else 0 for i in range(len(m_vec)) ], dtype=float) mw_hess = invmass * hessian * invmass[:, np.newaxis] evals, evecs = sp_linalg.eigh(mw_hess) f_cutoff = 0.0001 freq_list = [] mode_list = [] for i in range(len(evals)): if evals[i] >= 0 and np.sqrt(evals[i]) >= f_cutoff: freq_list.append(np.sqrt(evals[i])) mode_list.append(evecs[:, i].tolist()) n_modes = len(freq_list) freqs = np.asarray(freq_list) modes = np.asarray(mode_list).transpose() # confirm that modes * tr(modes) = 1 m_chk = np.dot(modes.T, modes) if not np.allclose(m_chk, np.eye(len(m_chk))): raise ValueError('Internal coordinates not orthonormal.') log.print_message('string', [' -- frequencies from hessian.dat --\n']) # If normal modes are being used, set the no. modes # equal to the total number of modes of the model # Hamiltonian and load only the active frequencies if coordtype == 'normal': n_modes = len(w_vec) # we multiply by 0.5 below -- multiply by 2 here (i.e. default width for # vibronic hamiltonans is 1/2, assuming frequency weighted coords freqs = 2. * w_vec log.print_message( 'string', ['\n -- widths employed in coordinate sampling --\n']) # write out frequencies fstr = '\n'.join([ '{0:.5f} au == {1:10.1f} cm^-1'.format(freqs[j], freqs[j] * constants.au2cm) for j in range(n_modes) ]) log.print_message('string', [fstr + '\n']) # loop over the number of initial trajectories max_try = 1000 ntraj = glbl.properties['n_init_traj'] for i in range(ntraj): delta_x = np.zeros(n_modes) delta_p = np.zeros(n_modes) x_sample = np.zeros(n_modes) p_sample = np.zeros(n_modes) for j in range(n_modes): alpha = 0.5 * freqs[j] if alpha > constants.fpzero: sigma_x = (glbl.properties['distrib_compression'] * np.sqrt(0.25 / alpha)) sigma_p = (glbl.properties['distrib_compression'] * np.sqrt(alpha)) itry = 0 while itry <= max_try: dx = np.random.normal(0., sigma_x) dp = np.random.normal(0., sigma_p) itry += 1 if mode_overlap( alpha, dx, dp) > glbl.properties['init_mode_min_olap']: break if mode_overlap(alpha, dx, dp) < glbl.properties['init_mode_min_olap']: raise ValueError( 'Cannot get mode overlap > ' + str(glbl.properties['init_mode_min_olap']) + ' within ' + str(max_try) + ' attempts. Exiting...') delta_x[j] = dx delta_p[j] = dp # If Cartesian coordinates are being used, displace along each # normal mode to generate the final geometry... if coordtype == 'cart': disp_x = np.dot(modes, delta_x) / np.sqrt(m_vec) disp_p = np.dot(modes, delta_p) * np.sqrt(m_vec) # ... else if mass- and frequency-scaled normal modes are # being used, then take the frequency-scaled normal mode # displacements and momenta as the inital point in phase # space elif coordtype == 'normal': disp_x = delta_x disp_p = delta_p #disp_x = delta_x * np.sqrt(freqs) #disp_p = delta_p * np.sqrt(freqs) x_sample = x_ref + disp_x p_sample = p_ref + disp_p # add new trajectory to the bundle new_traj = template.copy() new_traj.update_x(x_sample) new_traj.update_p(p_sample) # Add the trajectory to the bundle wfn.add_trajectory(new_traj)
def step_wavefunction(dt): """Propagates the wave packet using a run-time selected propagator.""" # save the wavefunction from previous step in case step rejected end_time = min(glbl.modules['wfn'].time + dt, glbl.properties['simulation_time']) time_step = min( dt, glbl.properties['simulation_time'] - glbl.modules['wfn'].time) min_time_step = dt / 2.**5 while not step_complete(glbl.modules['wfn'].time, end_time, dt): # save the wavefunction from previous step in case step rejected wfn_start = glbl.modules['wfn'].copy() # propagate each trajectory in the wavefunction time_step = min(time_step, end_time - glbl.modules['wfn'].time) # propagate amplitudes for 1/2 time step using x0 glbl.modules['wfn'].update_amplitudes(0.5 * dt) # the propagators update the potential energy surface as need be. glbl.modules['propagator'].propagate_wfn(glbl.modules['wfn'], time_step) # update the couplings for all the trajectories for i in range(glbl.modules['wfn'].n_traj()): glbl.modules['interface'].evaluate_coupling( glbl.modules['wfn'].traj[i]) # propagate amplitudes for 1/2 time step using x1 glbl.modules['matrices'].build(glbl.modules['wfn'], glbl.modules['integrals']) glbl.modules['wfn'].update_matrices(glbl.modules['matrices']) glbl.modules['wfn'].update_amplitudes(0.5 * dt) # Renormalization if glbl.properties['renorm'] == 1: glbl.modules['wfn'].renormalize() # check time_step is fine, energy/amplitude conserved accept, error_msg = check_step_wfn(wfn_start, glbl.modules['wfn'], time_step) # if everything is ok.. if accept: # update the wavefunction time glbl.modules['wfn'].time += time_step # spawn new basis functions if necessary basis_grown = glbl.modules['adapt'].spawn(glbl.modules['wfn'], time_step) # kill the dead trajectories basis_pruned = utilities.prune(glbl.modules['wfn']) # if a trajectory has been added, then call update_pes # to get the electronic structure information at the associated # centroids. This is necessary in order to propagate the amplitudes # at the start of the next time step. if basis_grown and glbl.modules['integrals'].require_centroids: evaluate.update_pes(glbl.modules['wfn']) # update the Hamiltonian and associated matrices if basis_grown or basis_pruned: glbl.modules['matrices'].build(glbl.modules['wfn'], glbl.modules['integrals']) glbl.modules['wfn'].update_matrices(glbl.modules['matrices']) for i in range(glbl.modules['wfn'].n_traj()): glbl.modules['interface'].evaluate_coupling( glbl.modules['wfn'].traj[i]) # re-expression of the basis using the matching pursuit # algorithm #if glbl.properties['matching_pursuit'] == 1: # mp.reexpress_basis(master) # update the running log log.print_message('t_step', [ glbl.modules['wfn'].time, time_step, glbl.modules['wfn'].nalive ]) #del wfn_start else: # recall -- this time trying to propagate to the failed step time_step *= 0.5 log.print_message('new_step', [error_msg, time_step]) if time_step < min_time_step: log.print_message('general', ['minimum time step exceeded -- STOPPING.']) raise ValueError('Bundle minimum step exceeded.') # reset the beginning of the time step and go to beginning of loop #del master glbl.modules['wfn'] = wfn_start.copy()
def spawn(wfn, dt): """Spawns a basis function if the minimum overlap drops below a given threshold.""" basis_grown = False current_time = wfn.time for i in range(wfn.n_traj()): if not wfn.traj[i].alive: continue parent = wfn.traj[i] for st in range(glbl.properties['n_states']): # don't check overlap with basis functions on same state if st == parent.state: continue s_array = [ abs(glbl.modules['integrals'].nuc_overlap(parent, wfn.traj[j])) if wfn.traj[j].state == st and wfn.traj[j].alive else 0. for j in range(wfn.n_traj()) ] if max(s_array, key=abs) < glbl.properties['continuous_min_overlap']: child = parent.copy() child.amplitude = 0j child.state = st child.parent = parent.label success = utilities.adjust_child( parent, child, parent.derivative(parent.state, child.state)) sij = glbl.modules['integrals'].nuc_overlap(parent, child) # try to set up the child if not success: pass #log.print_message('spawn_bad_step', # ['cannot adjust kinetic energy of child']) elif abs(sij) < glbl.properties['spawn_olap_thresh']: pass #log.print_message('spawn_bad_step', # ['child-parent overlap too small']) else: child_created = True spawn_time = current_time parent.last_spawn[child.state] = spawn_time child.last_spawn[parent.state] = spawn_time bundle_overlap = utilities.overlap_with_bundle(child, wfn) if not bundle_overlap: basis_grown = True wfn.add_trajectory(child) log.print_message('spawn_success', [current_time, parent.label, st]) utilities.write_spawn_log(current_time, current_time, current_time, parent, wfn.traj[-1]) else: err_msg = ('Traj ' + str(parent.label) + ' from state ' + str(parent.state) + ' to state ' + str(st) + ': ' + 'overlap with bundle too large,' + ' s_max=' + str(glbl.properties['sij_thresh'])) log.print_message('spawn_bad_step', [err_msg]) return basis_grown
def init_wavefunction(): """Initializes the trajectories.""" # initialize the interface we'll be using the determine the # the PES. There are some details here that trajectories # will want to know about log.print_message( 'string', ['\n **************\n' + ' initialization\n' + ' **************\n']) # creat the wave function instance glbl.modules['wfn'] = wavefunction.Wavefunction() glbl.modules['interface'] = __import__('nomad.interfaces.' + glbl.methods['interface'], fromlist=['NA']) glbl.modules['interface'].init_interface() # create glbl modules glbl.modules['matrices'] = matrices.Matrices() glbl.modules['init_conds'] = __import__('nomad.initconds.' + glbl.methods['init_conds'], fromlist=['NA']) glbl.modules['adapt'] = __import__('nomad.adapt.' + glbl.methods['adapt_basis'], fromlist=['a']) glbl.modules['propagator'] = __import__('nomad.propagators.' + glbl.methods['propagator'], fromlist=['a']) # WARNING: this should be done more eloquently # need to determine form of the coefficients on the kinetic energy # operator, i.e. cartesian vs. normal mode coordinate basis # this is messy -- should be re-worked mvoing forward... if glbl.methods['interface'] == 'vibronic': # if normal modes, read frequencies from vibronic interface kecoef = 0.5 * glbl.modules['interface'].ham.freq else: kecoef = 0.5 / glbl.properties['crd_masses'] glbl.modules['integrals'] = integral.Integral( kecoef, glbl.methods['ansatz'], glbl.methods['integral_eval']) # now load the initial trajectories into the bundle if glbl.properties['restart']: # print to logfile that we're restarting simulation log.print_message('string', [ ' restarting simulation from checkpoint file: ' + str(glbl.paths['chkpt_file']) + '\n' ]) # retrieve current wave function, no arguments defaults to most recent simulation wfn0 = None ints0 = None if glbl.mpi['rank'] == 0: [glbl.modules['wfn'], glbl.modules['integrals']] = checkpoint.retrieve_simulation( time=glbl.properties['restart_time']) [wfn0, ints0] = checkpoint.retrieve_simulation(time=0.) # only root reads checkpoint file -- then broadcasts contents to other proceses if glbl.mpi['parallel']: # synchronize tasks glbl.mpi['comm'].barrier() glbl.modules['wfn'] = glbl.mpi['comm'].bcast(glbl.modules['wfn'], root=0) wfn0 = glbl.mpi['comm'].bcast(wfn0, root=0) if glbl.modules['integrals'].require_centroids: glbl.modules['integrals'].centroid_required = glbl.mpi[ 'comm'].bcast(glbl.modules['integrals'].centroid_required, root=0) glbl.modules['integrals'].centroids = glbl.mpi['comm'].bcast( glbl.modules['integrals'].centroids, root=0) # save copy of t=0 wfn for autocorrelation function save_initial_wavefunction(wfn0) # check that we have all the data we need to propagate the first step -- otherwise, we # need to update the potential update_surface = False for i in range(glbl.modules['wfn'].n_traj()): if glbl.modules['wfn'].traj[i].alive and glbl.modules['wfn'].traj[ i].active: pes_data = glbl.modules['wfn'].traj[i].pes if ('potential' not in pes_data.avail_data() or 'derivative' not in pes_data.avail_data()): update_suface = True if glbl.modules['integrals'].require_centroids: for j in range(i): pes_data = glbl.modules['integrals'].centroids[i][j].pes if glbl.modules['integrals'].centroid_required[i][j]: if ('potential' not in pes_data.avail_data() and glbl.modules['wfn'].traj[i].state == glbl.modules['wfn'].traj[j].state): update_surface = True if ('derivative' not in pes_data.avail_data() and glbl.modules['wfn'].traj[i].state != glbl.modules['wfn'].traj[j].state): update_surface = True if update_surface: evaluate.update_pes(glbl.modules['wfn']) # update the couplings for all the trajectories for i in range(glbl.modules['wfn'].n_traj()): glbl.modules['interface'].evaluate_coupling( glbl.modules['wfn'].traj[i]) # build the necessary matrices glbl.modules['matrices'].build(glbl.modules['wfn'], glbl.modules['integrals']) glbl.modules['wfn'].update_matrices(glbl.modules['matrices']) else: # first generate the initial nuclear coordinates and momenta # and add the resulting trajectories to the bundle glbl.modules['init_conds'].set_initial_coords(glbl.modules['wfn']) # set the initial state of the trajectories in bundle. This may # require evaluation of electronic structure set_initial_state(glbl.modules['wfn']) # set the initial amplitudes of the basis functions set_initial_amplitudes(glbl.modules['wfn']) # add virtual basis functions, if desired if glbl.properties['virtual_basis']: virtual_basis(glbl.modules['wfn']) # update the integrals glbl.modules['integrals'].update(glbl.modules['wfn']) # once phase space position and states of the basis functions # are set, update the potential information evaluate.update_pes(glbl.modules['wfn']) # update the couplings for all the trajectories for i in range(glbl.modules['wfn'].n_traj()): glbl.modules['interface'].evaluate_coupling( glbl.modules['wfn'].traj[i]) # compute the hamiltonian matrix... glbl.modules['matrices'].build(glbl.modules['wfn'], glbl.modules['integrals']) glbl.modules['wfn'].update_matrices(glbl.modules['matrices']) # so that we may appropriately renormalize to unity glbl.modules['wfn'].renormalize() # this is the bundle at time t=0. Save in order to compute auto # correlation function save_initial_wavefunction(glbl.modules['wfn']) # write the wavefunction to the archive if glbl.mpi['rank'] == 0: checkpoint.archive_simulation(glbl.modules['wfn'], glbl.modules['integrals']) log.print_message( 'string', ['\n ***********\n' + ' propagation\n' + ' ***********\n\n']) log.print_message('t_step', [ glbl.modules['wfn'].time, glbl.properties['default_time_step'], glbl.modules['wfn'].nalive ]) return glbl.modules['wfn'].time