def align_all_phases(self): """ align_all_phases() Makes sure the eigenfunctions saved to file does not change sign from one R to another. """ #File where the eigenstates are stored. filename = name_gen.electronic_eigenstates_R(self.basis.config) m_max = self.basis.config.m_max #q_max is the same as mu_max. q_max = self.basis.config.mu_max f = tables.openFile(filename, "r+") try: for m in range(-m_max, m_max + 1): m_group = name_gen.m_name(m) for q in range(q_max + 1): q_group = name_gen.q_name(q) V = eval("f.root.%s.%s.V"%(m_group, q_group)) #Flipping the appropriate wavefunctions. self.align_phases(V) finally: f.close()
def calculate_dipole_eig_R(self, index_array, R): """ dipole_matrix_eig, E = calculate_dipole_eig_R(index_array, R) Calculates the dipole matrix in the eigenstate basis for a given internuclear distance <R>. Parameters ---------- index_array : (3 x N) int array, where row i contains [m value, q value, index] of the i-th eigenstate basis function in the eigenstate HDF5 file. R : float, the internuclear distance for which the dipole matrix should be calculated. Returns ------- dipole_matrix_eig : (N x N) complex array, containing the dipole matrix in the eigenstate basis. E : 1D float array, containing the energies of the eigenstate basis function. """ #Create the correct basis. temp_config = config.Config(self.config) temp_config.R = R basis = psc_basis.PSC_basis(temp_config) #Computes the dipole matrix in the psc basis. dipole_matrix_psc = self.dipole_psc(basis) #Initialize eigenstate/value arrays. V = zeros([basis.basis_size, index_array.shape[0]], dtype = complex) E = zeros([index_array.shape[0]]) f = tables.openFile(self.eigenstate_file) try: #Find index of current R in the R_grid. R_index = find(f.root.R_grid[:] == R)[0] for i, [m, q, index] in enumerate(index_array): m_group = name_gen.m_name(m) q_group = name_gen.q_name(q) V[:,i] = eval("f.root.%s.%s.V[:, %i, %i]"%( m_group, q_group, index, R_index)) E[i] = eval("f.root.%s.%s.E[%i, %i]"%( m_group, q_group, index, R_index)) finally: f.close() #Transform the dipole matrix to the eigenstate basis. dipole_matrix_eig = dot(conj(transpose(V)), dot(dipole_matrix_psc, V)) return dipole_matrix_eig, E
def sort_in_m_and_q(self, E, V): """ E_and_V_dictionary = sort_in_m_and_q(E, V) Sorts the eigenfunctions according to the m and q quantum number, (i.e. number of nodes in the eta part of the function (polar angle function, roughly)). Returns a dictionary with m and q values as keys. Parameters ---------- E : 1D float array, containing the sorted eigenvalues. V : 2D complex array, containing the normalized eigenvectors. Returns ------- E_and_V_dictionary : nested dictionary instance, with m and q numbers as keys, and a tuple of corresponding E and V constituents as values. Notes ----- The q quantum number seems to correspond to the l quantum number in atoms. It is explained further in Barmaki et al., Phys. Rev. A 69, 043403 (2004). """ #Initialize dictionaries. index_dictionary = {} E_and_V_dictionary = {} #Getting the q quantum numbers from the eigenfunction. q = self.extract_q(V) #Loop over eigenstates. for i in range(V.shape[1]): #Singling out a wavefunction. v = V[:,i] #Getting the m quantum number from the eigenfunction. m = self.extract_m(v) m_key = name_gen.m_name(m) q_key = name_gen.q_name(q[i]) #Add the index to the index dictionary. if m_key in index_dictionary: if q_key in index_dictionary[m_key]: index_dictionary[m_key][q_key].append(i) else: index_dictionary[m_key][q_key] = [i] else: index_dictionary[m_key] = {q_key:[i]} #Putting the actual eigenstates/energies in the dictionary. for m_key, m_value in index_dictionary.iteritems(): for q_key, indices in m_value.iteritems(): if m_key in E_and_V_dictionary: E_and_V_dictionary[m_key][q_key] = (E[indices], V[:, indices]) else: E_and_V_dictionary[m_key] = {q_key:(E[indices], V[:, indices])} return E_and_V_dictionary
def BO_dipole_couplings(self, m_list, q_list, E_lim): """ BO_dipole_couplings(m_list, q_list, E_lim) Parallel program that calculates the dipole couplings for a z-polarized laser in lenght gauge. An eigenstate basis is used, of states whose quantum numbers are in <m_list> and <q_list>, that have energies below <E_lim>. The couplings are stored to an HDF5 file. Parameters ---------- m_list : list of integers, containing the m values wanted in the basis. q_list : list of integers, containing the q values wanted in the basis. E_lim : float, the upper limit of the energies wanted in the basis, for R ~ 2.0. Notes ----- I sometimes observe unnatural spikes in the couplings (as a function of R), which should be removed before the couplings are used. I don't know why they are there. Example ------- >>> filename = "el_states_m_0_nu_70_mu_25_beta_1_00_theta_0_00.h5" >>> tdse = tdse_electron.TDSE_length_z(filename = filename) >>> m = [0] >>> q = [0,1,2,3] >>> E_lim = 5.0 >>> tdse.BO_dipole_couplings(m, q, E_lim) """ #Name of the HDF5 file where the couplings will be saved. self.coupling_file = name_gen.electronic_eig_couplings_R(self, m_list, q_list, E_lim) #Parallel stuff #-------------- #Get processor 'name'. my_id = pypar.rank() #Get total number of processors. nr_procs = pypar.size() #Size of eigenstate basis. (Buffer for broadcast.) basis_size_buffer = r_[0] #Get number of tasks. f = tables.openFile(self.eigenstate_file) try: R_grid = f.root.R_grid[:] finally: f.close() nr_tasks = len(R_grid) #Get a list of the indices of this processors share of R_grid. my_tasks = nice_stuff.distribute_work(nr_procs, nr_tasks, my_id) #The processors will be writing to the same file. #In order to avoid problems, the procs will do a relay race of writing to #file. This is handeled by blocking send() and receive(). #Hopefully there will not be to much waiting. #ID of the processor that will start writing. starter = 0 #ID of the processor that will be the last to write. ender = (nr_tasks - 1) % nr_procs #Buffer for the baton, i.e. the permission slip for file writing. baton = r_[0] #The processor one is to receive the baton from. receive_from = (my_id - 1) % nr_procs #The processor one is to send the baton to. send_to = (my_id + 1) % nr_procs #------------------------------- #Initializing the HDF5 file #-------------------------- if my_id == 0: #Initialize index list. index_array = [] #Find the index of the R closest to 2.0. R_index = argmin(abs(R_grid - 2.0)) #Choose basis functions. f = tables.openFile(self.eigenstate_file) try: for m in m_list: m_group = name_gen.m_name(m) for q in q_list: q_group = name_gen.q_name(q) for i in range(self.config.nu_max + 1): if eval("f.root.%s.%s.E[%i,%i]"%(m_group, q_group, i, R_index)) > E_lim: break else: #Collect indices of the basis functions. index_array.append(r_[m, q, i]) finally: f.close() #Cast index list as an array. index_array = array(index_array) #Number of eigenstates in the basis. basis_size = len(index_array) print basis_size, "is the basis size" basis_size_buffer[0] = basis_size f = tables.openFile(self.coupling_file, 'w') try: f.createArray("/", "R_grid", R_grid) #Saving the index array. f.createArray("/", "index_array", index_array) #Initializing the arrays for the couplings and energies. f.createCArray('/', 'E', tables.atom.FloatAtom(), (basis_size, nr_tasks), chunkshape=(basis_size, 1)) f.createCArray('/', 'couplings', tables.atom.ComplexAtom(16), (basis_size, basis_size, nr_tasks), chunkshape=(basis_size, basis_size, 1)) finally: f.close() #Save config instance. self.config.save_config(self.coupling_file) #---------------------------------- #Calculating the dipole couplings #-------------------------------- #Broadcasting the basis size from processor 0. pypar.broadcast(basis_size_buffer, 0) #Initializing the index array. if my_id != 0: index_array = zeros([basis_size_buffer[0], 3], dtype=int) #Broadcasting the index array from proc. 0. pypar.broadcast(index_array, 0) #Looping over the tasks of this processor. for i in my_tasks: #Calculate the dipole couplings for one value of R. couplings, E = self.calculate_dipole_eig_R(index_array, R_grid[i]) #First file write. (Send, but not receive baton.) if starter == my_id: #Write to file. self.save_dipole_eig_R(couplings, E, R_grid[i]) #Avoiding this statement 2nd time around. starter = -1 #Sending the baton to the next writer. pypar.send(baton, send_to, use_buffer = True) #Last file write. (Receive, but not send baton.) elif i == my_tasks[-1] and ender == my_id : #Receiving the baton from the previous writer. pypar.receive(receive_from, buffer = baton) #Write to file. self.save_dipole_eig_R(couplings, E, R_grid[i]) #The rest of the file writes. else: #Receiving the baton from the previous writer. pypar.receive(receive_from, buffer = baton) #Write to file. self.save_dipole_eig_R(couplings, E, R_grid[i]) #Sending the baton to the next writer. pypar.send(baton, send_to, use_buffer = True) #Showing the progress of the work. if my_id == 0: nice_stuff.status_bar("Electronic dipole couplings:", i, len(my_tasks)) #---------------------------- #Letting everyone catch up. pypar.barrier()
def save_electronic_eigenstates(m_max, nu_max, mu_max, R_grid, beta, theta): """ save_electronic_eigenstates(m_max, nu_max, mu_max, R_grid, beta, theta) This program solves the electronic TISE for a range of internuclear distances, given in <R_grid>, and stores them in an HDF5 file. This program must be run in parallel. Example ------- To run this program on 5 processors: $ mpirun -n 5 python electronic_BO.py """ #Parallel stuff #-------------- #Get processor 'name'. my_id = pypar.rank() #Get total number of processors. nr_procs = pypar.size() #Get number of tasks. nr_tasks = len(R_grid) #Get a list of the indices of this processors share of R_grid. my_tasks = nice_stuff.distribute_work(nr_procs, nr_tasks, my_id) #The processors will be writing to the same file. #In order to avoid problems, the procs will do a relay race of writing to #file. This is handeled by blocking send() and receive(). #Hopefully there will not be to much waiting. #ID of the processor that will start writing. starter = 0 #ID of the processor that will be the last to write. ender = (nr_tasks - 1) % nr_procs #Buffer for the baton, i.e. the permission slip for file writing. baton = r_[0] #The processor one is to receive the baton from. receive_from = (my_id - 1) % nr_procs #The processor one is to send the baton to. send_to = (my_id + 1) % nr_procs #------------------------------- #Initializing the HDF5 file #-------------------------- if my_id == 0: #Creates a config instance. my_config = config.Config(m = m_max, nu = nu_max, mu = mu_max, R = R_grid[0], beta = beta, theta = theta) #Number of basis functions. basis_size = (2 * m_max + 1) * (nu_max + 1) * (mu_max + 1) #Generate a filename. filename = name_gen.electronic_eigenstates_R(my_config) f = tables.openFile(filename, 'w') try: f.createArray("/", "R_grid", R_grid) #Looping over the m values. for m in range(-1 * m_max, m_max + 1): #Creating an m group in the file. m_group = name_gen.m_name(m) f.createGroup("/", m_group) #Looping over th q values. for q in range(mu_max + 1): #Creating a q group in the m group in the file. q_group = name_gen.q_name(q) f.createGroup("/%s/"%m_group, q_group) #Initializing the arrays for the eigenvalues and states. f.createCArray('/%s/%s/'%(m_group, q_group),'E', tables.atom.FloatAtom(), (basis_size/(mu_max + 1), nr_tasks), chunkshape=(basis_size/(mu_max + 1), 1)) f.createCArray('/%s/%s/'%(m_group, q_group),'V', tables.atom.ComplexAtom(16), (basis_size, basis_size/(mu_max + 1), nr_tasks), chunkshape=(basis_size, basis_size/(mu_max + 1), 1)) finally: f.close() #Save config instance. my_config.save_config(filename) #---------------------------------- #Solving the TISE #---------------- #Looping over the tasks of this processor. for i in my_tasks: #Creating TISE instance. tise = tise_electron.TISE_electron(m = m_max, nu = nu_max, mu = mu_max, R = R_grid[i], beta = beta, theta = theta) #Diagonalizing the hamiltonian. E,V = tise.solve() #First file write. (Send, but not receive baton.) if starter == my_id: #Write to file. tise.save_eigenfunctions_R(E, V, R_grid[i]) #Avoiding this statement 2nd time around. starter = -1 #Sending the baton to the next writer. pypar.send(baton, send_to, use_buffer = True) #Last file write. (Receive, but not send baton.) elif i == my_tasks[-1] and ender == my_id : #Receiving the baton from the previous writer. pypar.receive(receive_from, buffer = baton) #Write to file. tise.save_eigenfunctions_R(E, V, R_grid[i]) #The rest of the file writes. else: #Receiving the baton from the previous writer. pypar.receive(receive_from, buffer = baton) #Write to file. tise.save_eigenfunctions_R(E, V, R_grid[i]) #Sending the baton to the next writer. pypar.send(baton, send_to, use_buffer = True) #Showing the progress of the work. if my_id == 0: nice_stuff.status_bar("Electronic BO calculations", i, len(my_tasks)) #---------------------------- #Letting everyone catch up. pypar.barrier() #Since the sign of the eigenfunctions are completely arbitrary, one must #make sure they do not change sign from one R to another. if my_id == 0: tise.align_all_phases() #Letting 0 catch up. pypar.barrier()