def plot_multi_energy_diagram(*args, **kwargs): """ Draw a potential energy diagram of a series of elementary reaction equation and save or show it. Return the Figure object. Parameters ---------- args: rxn_equations_list : list a list containing a series of reaction equation string according to rules in setup file. e.g. ['HCOOH_g + 2*_s <-> HCOO-H_s + *_s -> HCOO_s + H_s', 'HCOO_s + *_s <-> H-COO_s + *_s -> COO_s + H_s', 'COO_s -> CO2_g + *_s', '2H_s <-> H-H_s + *_s -> H2_g + 2*_s']. energy_tuples : list of tuples e.g. [ (0.0, 1.0, 0.5), (3.0, 4.7, 0.7), (0.0, 4.0), (3.0, 4.7, 0.7), ] subsection_length : int, optional Length of each subsection(x_i, x_f), defualt to be 1.0. line_color : str, optional Color code of the line. Default to be '#000000'/'black'. init_y_offset : float, optional Initial offset value on y axis. has_shadow : Bool Whether to add shadow effect to the main line. Default to be True. show_note : bool Whether to show species note on the main line. Default to be True. show_aux_line: bool Whether to show auxiliary lines on the main line. Default to be Ture. show_arrow : bool Whether to show annotation arrow. Default to be True. fmt : str, optional The output format : ['pdf'|'jpeg'|'png'|'raw'|'pgf'|'ps'|'svg'] Default to be 'jpeg' show_mode : str, 'show' or 'save' 'show' : show the figure in a interactive way. 'save' : save the figure automatically. peak_widths : tuple of floats, (1.0, ...)(default) peak widths if TS exists. Other parameters ---------------- kwargs : 'fname' : str, optional 'dpi' : int, default to be 80 'offset_coeff' : float, default to be 1.0 shadow offset coefficient. Examples -------- """ ############### args setting before plotting ################ #for args if len(args) != 2: raise ValueError("Need at least 2 args: " + "rxn_equations_list, energy_tuples.") rxn_equations_list, energy_tuples = args #for kwargs # peak widths if 'peak_widths' in kwargs: peak_widths = kwargs['peak_widths'] if len(peak_widths) != len(rxn_equations_list): raise ValueError('number of peak widths and ' + 'number of rxn equations are not matched.') else: # set to (1.0, 1.0, ...) peak_widths = tuple([1.0]*len(rxn_equations_list)) n = kwargs['n'] if 'n' in kwargs else 100 subsection_length = \ kwargs['subsection_length'] if 'subsection_length' in kwargs else 1.0 line_color = kwargs['line_color'] if 'line_color' in kwargs else '#000000' has_shadow = kwargs['has_shadow'] if 'has_shadow' in kwargs else True fmt = kwargs['fmt'] if 'fmt' in kwargs else 'png' init_y_offset = kwargs['init_y_offset'] if 'init_y_offset' in kwargs else 0.0 show_note = kwargs['show_note'] if 'show_note' in kwargs else True show_aux_line = kwargs['show_aux_line'] if 'show_aux_line' in kwargs else True show_arrow = kwargs['show_arrow'] if 'show_arrow' in kwargs else True show_mode = kwargs['show_mode'] if 'show_mode' in kwargs else 'show' ##################### args setting END ####################### #convert rxn_equation_list(str) to elementary_rxns_list alike list rxns_list = [RxnEquation(rxn_equation).tolist() for rxn_equation in rxn_equations_list] x_offset, y_offset = 0.0, init_y_offset total_x, total_y = [], [] piece_points = [] # collectors for idx, (rxn_equation, peak_width) in \ enumerate(zip(rxn_equations_list, peak_widths)): energy_tuple = get_relative_energy_tuple(energy_tuples[idx]) x, y, x2 = get_potential_energy_points( energy_tuple=energy_tuple, n=n, subsection_length=subsection_length, peak_width=peak_widths[idx], ) x_scale = 2*subsection_length + peak_width #get total y offseted_y = y + y_offset total_y.extend(offseted_y.tolist()) #get total x offseted_x = x + x_offset total_x.extend(offseted_x.tolist()) ####### collect values for adding annotations ####### #there are 3 points in each part p1 = (2*subsection_length+offseted_x[0], offseted_y[0]) p3 = (offseted_x[-1], offseted_y[-1]) if len(energy_tuple) == 3: p2 = (2*subsection_length + x2 + offseted_x[0], # x2 is the x value of barrier energy_tuple[1] + y_offset) piece_points.append((p1, p2, p3)) elif len(energy_tuple) == 2: piece_points.append((p1, p3)) ################ Collection End ################ #update offset value for next loop x_offset += x_scale y_offset = offseted_y[-1] #Add extral line #add head horizontal line head_extral_x = np.linspace(0.0, subsection_length, n).tolist() offseted_total_x = (np.array(total_x)+subsection_length).tolist() head_extral_y = [total_y[0]]*n total_x = head_extral_x + offseted_total_x total_y = head_extral_y + total_y #add the last horizontal line tail_extral_x = np.linspace(total_x[-1], total_x[-1]+subsection_length, n).tolist() tail_extral_y = [total_y[-1]]*n total_x += tail_extral_x total_y += tail_extral_y total_x, total_y = np.array(total_x), np.array(total_y) #get extreme values of x and y y_min, y_max = np.min(total_y), np.max(total_y) x_min, x_max = total_x[0], total_x[-1] y_scale = y_max-y_min ################### start Artist Mode! ###################### multi = len(rxn_equations_list)/2.2 fig = plt.figure(figsize=(multi*10, multi*5)) #fig = plt.figure() ax = fig.add_subplot(111) ax.set_ylim(y_min - y_scale/10, y_max + y_scale/10) ax.set_xlim(x_min-subsection_length, x_max+subsection_length) ax.set_title('Energy Profile', fontdict={ 'fontsize': 15, 'weight': 100, 'verticalalignment': 'bottom' }) ax.set_xlabel('Reaction Coordinate') ax.set_ylabel('Free Energy(eV)') #add shadow if has_shadow: add_line_shadow(ax, total_x, total_y, depth=8, color='#595959') #main line ax.plot(total_x, total_y, color=line_color, linewidth=3) #get state string list as notes state_str_list = [] for rxn_list in rxns_list: t = tuple([state_obj.texen() for state_obj in rxn_list[:-1]]) state_str_list.append(t) #################### Main loop to add notes ################### #This is the main loop to add extral info to main line #For each single loop, we get 3 or 2 points(tuple) for each elementary rxn #and the first 2 or 1 latex reaction state string(involved in list) def add_state_note(pts, tex_states): "function to add notes to a state" start_point, end_point = pts[0], pts[-1] # add horizontal lines and vertical arrowa if show_aux_line: # plot horizontal lines hori_x = np.linspace(start_point[0], end_point[0]+2*subsection_length, 50) hori_y = np.linspace(start_point[-1], start_point[-1], 50) ax.plot(hori_x, hori_y, color=line_color, linewidth=1, linestyle='dashed') # plot vertical arrows if len(pts) == 3: mid_point = pts[1] #plot activation energy arrow ax.annotate( '', xy=(mid_point[0], mid_point[-1]), xycoords='data', xytext=(mid_point[0], start_point[-1]), textcoords='data', arrowprops=dict(arrowstyle="<->") ) # plot reaction energy arrow ax.annotate( '', xy=(end_point[0]+subsection_length, end_point[-1]), xycoords='data', xytext=(end_point[0]+subsection_length, start_point[-1]), textcoords='data', arrowprops=dict(arrowstyle="<->") ) if show_note: # add species notes if len(pts) == 3: # those who have TS for state_idx, (pt, tex_state) in enumerate(zip(pts, tex_states)): if state_idx == 0: # for initial or final state note_x = pt[0] - 1.8*subsection_length note_y = pt[-1] + y_scale/50 ax.text(note_x, note_y, r'$\bf{'+tex_state+r'}$', fontdict={'fontsize': 13, 'color': '#1874CD'}) if state_idx == 1: # for transition state note_x = pt[0] - subsection_length/2 note_y = pt[-1] + y_scale/50 ax.text(note_x, note_y, r'$\bf{'+tex_state+r'}$', fontdict={'fontsize': 13, 'color': '#CD5555'}) if len(pts) == 2: # no TS pt, tex_state = pts[0], tex_states[0] note_x = pt[0] - 1.8*subsection_length note_y = pt[-1] + y_scale/50 ax.text(note_x, note_y, r'$\bf{'+tex_state+r'}$', fontdict={'fontsize': 13, 'color': '#1874CD'}) if show_arrow: # add energy annotations el = Ellipse((2, -1), 0.5, 0.5) # annotation between IS and FS rxn_energy = round(end_point[-1] - start_point[-1], 2) rxn_energy_latex = r'$\bf{\Delta G = ' + str(rxn_energy) + r' eV}$' ax.annotate(rxn_energy_latex, xy=(end_point[0]+subsection_length, (start_point[-1]+end_point[-1])/2), xycoords='data', xytext=(-70, 30), textcoords='offset points', size=13, color='#8E388E', arrowprops=dict(arrowstyle="simple", fc="0.6", ec="none", patchB=el, connectionstyle="arc3,rad=0.2"), ) if len(pts) == 3: # annotation between TS and IS act_energy = round((mid_point[-1] - start_point[-1]), 2) act_energy_latex = r'$\bf{G_{a} = '+str(act_energy)+r' eV}$' ax.annotate(act_energy_latex, xy=(mid_point[0], (mid_point[1]+start_point[1])/2), xycoords='data', xytext=(30, 20), textcoords='offset points', size=13, color='#B22222', arrowprops=dict(arrowstyle="simple", fc="0.6", ec="none", patchB=el, connectionstyle="arc3,rad=-0.2"), ) ######### Main Loop END ######## #use multiple thread to add notes note_threads = [] nstates = len(state_str_list) for pts, tex_states in zip(piece_points, state_str_list): t = NoteThread(add_state_note, (pts, tex_states)) note_threads.append(t) for i in xrange(nstates): note_threads[i].start() for i in xrange(nstates): note_threads[i].join() #add species note at last section if show_note: pt = pts[-1] tex_state = rxns_list[-1][-1].texen() ax.text(pt[0]+0.2*subsection_length, pt[-1]+y_scale/50, r'$\bf{'+tex_state+r'}$', fontdict={'fontsize': 13, 'color': '#1874CD'}) #remove xticks ax.set_xticks([]) ##################### Artist Mode End ##################### #creat path if not os.path.exists("./energy_profile"): os.mkdir("./energy_profile") #filename if 'fname' in kwargs: fname = kwargs['fname'] + '.' + fmt else: fname = 'multi_energy_diagram.' + fmt fullname = "./energy_profile/" + fname if show_mode == 'show': fig.show() elif show_mode == 'save': if 'dpi' in kwargs: fig.savefig(fullname, dpi=kwargs['dpi']) fig.savefig(fullname) else: raise ValueError('Unrecognized show mode parameter : ' + show_mode) return fig, total_x, total_y
def plot_single_energy_diagram(*args, **kwargs): """ Draw a potential energy diagram of a elementary reaction equation and save it. Return the Figure object. Parameters ---------- energy_tuple : tuple of float, essential energy data for profile plotting. e.g. (0.0, 1.2, 0.7) rxn_equation : str, essential reaction equation string according to rules in setup file. e.g. 'HCOOH_g + 2*_s <-> HCOO-H_s + *_s -> HCOO_s + H_s'. subsection_length : int, optional Length of each subsection(x_i, x_f), defualt to be 1.0. line_color : str, optional Color code of the line. Default to be '#000000'/'black'. has_shadow : Bool Whether to add shadow effect to the main line. Default to be False. fmt : str, optional The output format : ['pdf'|'jpeg'|'png'|'raw'|'pgf'|'ps'|'svg'] Default to be 'jpeg' show_mode : str, 'show' or 'save' 'show' : show the figure in a interactive way. 'save' : save the figure automatically. peak_width : float, 1.0(default) peak width if TS exists. Other parameters ---------------- kwargs : 'fname' : str, optional 'offset_coeff' : float, default to be 1.0 shadow offset coefficient. Examples -------- >>> plot_single_energy_diagram((0.0, 1.2, 0.6), 'COO_s -> CO2_g + *_s', has_shadow=True, fname='pytlab') >>> <matplotlib.figure.Figure at 0x5659f30> """ ############### args setting before plotting ################ #for args if len(args) != 2: raise ValueError("Need at least 2 args: energy_tuple, rxn_equation.") energy_tuple, rxn_equation = args #for kwargs n = kwargs['n'] if 'n' in kwargs else 100 subsection_length = \ kwargs['subsection_length'] if 'subsection_length' in kwargs else 1.0 line_color = kwargs['line_color'] if 'line_color' in kwargs else '#000000' has_shadow = kwargs['has_shadow'] if 'has_shadow' in kwargs else True fmt = kwargs['fmt'] if 'fmt' in kwargs else 'png' show_mode = kwargs['show_mode'] if 'show_mode' in kwargs else 'show' peak_width = kwargs['peak_width'] if 'peak_width' in kwargs else 1.0 ##################### args setting END ####################### rxn_list = RxnEquation(rxn_equation).tolist() energy_tuple = get_relative_energy_tuple(energy_tuple) #energy info rxn_energy = round(energy_tuple[-1] - energy_tuple[0], 2) if len(energy_tuple) == 3: act_energy = round(energy_tuple[1] - energy_tuple[0], 2) #get x, y array x, y, x2 = get_potential_energy_points(energy_tuple, n=n, subsection_length=subsection_length, peak_width=peak_width) # get maximum and minimum values of x and y axis y_min, y_max = np.min(y), np.max(y) x_max = 3.7*subsection_length #scale of y y_scale = abs(y_max - y_min) ################### start Artist Mode! ####################### fig = plt.figure(figsize=(10, 7)) ax = fig.add_subplot(111) ax.set_ylim(y_min - y_scale/5, y_max + y_scale/5) ax.set_xlim(-subsection_length/3, x_max) xlabel = ax.set_xlabel('Reaction Coordinate') ylabel = ax.set_ylabel('Free Energy(eV)') title = ax.set_title(r'$\bf{'+RxnEquation(rxn_equation).texen()+r'}$', fontdict={ 'fontsize': 13, 'weight': 1000, 'verticalalignment': 'bottom' }) #add shadow if has_shadow: add_line_shadow(ax, x, y, depth=8, color='#595959') #main line ax.plot(x, y, color=line_color, linewidth=3) #energy latex string if 'act_energy' in dir(): act_energy_latex = r'$\bf{G_{a} = ' + str(act_energy) + r' eV}$' rxn_energy_latex = r'$\bf{\Delta G = ' + str(rxn_energy) + r' eV}$' ####################### annotates ####################### #add species annotation #get annotate coordiantes note_offset = y_scale/40 param_list = [] #initial state note_x_i = subsection_length/10 note_y_i = energy_tuple[0] + note_offset note_str_i = r'$\bf{' + rxn_list[0].texen() + r'}$' param_list.append((note_x_i, note_y_i, note_str_i)) #final state note_x_f = subsection_length/10 + subsection_length + peak_width note_y_f = energy_tuple[-1] + note_offset note_str_f = r'$\bf{' + rxn_list[-1].texen() + r'}$' param_list.append((note_x_f, note_y_f, note_str_f)) if len(energy_tuple) == 3: #transition state note_y_b = np.max(y) + note_offset # equal to energy_tuple[1] idx = np.argmax(y) note_x_b = x[idx] - subsection_length/4 ##################### barrier_x = x[idx] # x value of TS point ##################### note_str_b = r'$\bf' + rxn_list[1].texen() + r'}$' param_list.append((note_x_b, note_y_b, note_str_b)) #add annotates for idx, param_tuple in enumerate(param_list): if idx == 2: ax.text(*param_tuple, fontdict={'fontsize': 13, 'color': '#CD5555'}) else: ax.text(*param_tuple, fontdict={'fontsize': 13, 'color': '#1874CD'}) ###################################################### # # # inflection points coordinates: # # (subsection_length, energy_tuple[0]) # # (barrier_x, np.max(y)) # # (2*subsection_length, energy_tuple[-1]) # # # ###################################################### #energy change annotation #initial state horizontal line hori_x_i = np.linspace(subsection_length, peak_width + 2*subsection_length, 50) hori_y_i = np.linspace(energy_tuple[0], energy_tuple[0], 50) ax.plot(hori_x_i, hori_y_i, color=line_color, linewidth=1, linestyle='dashed') el = Ellipse((2, -1), 0.5, 0.5) if len(energy_tuple) == 3: # for reaction with barrier #arrow between barrier ax.annotate('', xy=(barrier_x, energy_tuple[0]), xycoords='data', xytext=(barrier_x, np.max(y)), textcoords='data', arrowprops=dict(arrowstyle="<->")) #text annotations of barrier ax.annotate(act_energy_latex, xy=(barrier_x, np.max(y)/2), xycoords='data', xytext=(-150, 30), textcoords='offset points', size=13, color='#B22222', arrowprops=dict(arrowstyle="simple", fc="0.6", ec="none", patchB=el, connectionstyle="arc3,rad=0.2"), ) # arrow between reaction energy ax.annotate( '', xy=(subsection_length*3/2 + peak_width, energy_tuple[-1]), xycoords='data', xytext=(subsection_length*3/2 + peak_width, energy_tuple[0]), textcoords='data', arrowprops=dict(arrowstyle="<->") ) # text annotations of reaction energy ax.annotate( rxn_energy_latex, xy=(subsection_length*3/2 + peak_width, (energy_tuple[-1]+energy_tuple[0])/2), xycoords='data', xytext=(50, 30), textcoords='offset points', size=13, color='#8E388E', arrowprops=dict(arrowstyle="simple", fc="0.6", ec="none", patchB=el, connectionstyle="arc3,rad=-0.2"), ) ############## annotates end ################ ################# Artist Mode End ! ####################### #remove xticks ax.set_xticks([]) #save the figure object #creat path if not os.path.exists("./energy_profile"): os.mkdir("./energy_profile") #filename if 'fname' in kwargs: fname = kwargs['fname'] + '.' + fmt else: fname = 'elementary_energy_diagram.' + fmt fullname = "./energy_profile/" + fname if show_mode == 'save': if 'dpi' in kwargs: fig.savefig(fullname, dpi=kwargs['dpi']) fig.savefig(fullname) elif show_mode == 'show': plt.show() else: raise ValueError('Unrecognized show mode parameter : ' + show_mode) return fig, x, y