qrotor.plot
Description
This module provides straightforward functions to plot QRotor data.
Index
potential() |
Potential values as a function of the angle |
energies() |
Calculated eigenvalues |
reduced_energies() |
Reduced energies E/B as a function of the reduced potential V/B |
wavefunction() |
Selected wavefunctions or squared wavefunctions of a system |
splittings() |
Tunnel splitting energies of a list of systems |
convergence() |
Energy convergence of a list of systems calculated with different parameters |
1""" 2# Description 3 4This module provides straightforward functions to plot QRotor data. 5 6 7# Index 8 9| | | 10| --- | --- | 11| `potential()` | Potential values as a function of the angle | 12| `energies()` | Calculated eigenvalues | 13| `reduced_energies()` | Reduced energies E/B as a function of the reduced potential V/B | 14| `wavefunction()` | Selected wavefunctions or squared wavefunctions of a system | 15| `splittings()` | Tunnel splitting energies of a list of systems | 16| `convergence()` | Energy convergence of a list of systems calculated with different parameters | 17 18--- 19""" 20 21 22from .system import System 23from . import systems 24from . import constants 25import matplotlib.pyplot as plt 26import numpy as np 27from copy import deepcopy 28import aton.alias as alias 29 30 31def potential( 32 data:System|list, 33 title:str=None, 34 marker:str|list='', 35 linestyle:str|list='-', 36 cm:str=None, 37 cm_range:tuple|list=(0.0,0.8), 38 color:list|tuple|str=None, 39 normalize:bool=False, 40 ylim:tuple=None, 41 rc:dict={}, 42 ) -> None: 43 """Plot the potential values of `data` (System object, or list of systems). 44 45 Title can be customized with `title`. 46 If empty, system[0].comment will be used as title if no more comments are present. 47 48 `marker` and `linestyle` can be a Matplotlib string or list of strings. 49 Custom colormaps can be set through `cm`, in a custom `cm_range` from 0 to 1. 50 Custom colors can also be set through `color`. 51 Alternatively, custom color lists can be set through the `color` variable. 52 53 Set `normalize = True` to normalize by their respective `qrotor.system.System.potential_max`. 54 This can be useful if you have performed subtractions or similar operations. 55 In this case, you might also want to play with `ylim` to adjust the y-axis limits. 56 57 Additional matplotlib runtime configuration 58 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 59 can be set with the `rc` dict. 60 """ 61 data_copy = deepcopy(data) 62 system = systems.as_list(data_copy) 63 title_str = title if title else (system[0].comment if (system[0].comment and (len(system) == 1 or not system[-1].comment)) else 'Rotational potential energy') 64 # Marker as a list 65 if isinstance(marker, list): 66 if len(marker) < len(system): 67 marker.extend([''] * (len(system) - len(marker))) 68 else: 69 marker = [marker] * len(system) 70 # Linestyle as a list 71 if isinstance(linestyle, list): 72 if len(linestyle) < len(system): 73 linestyle.extend(['-'] * (len(system) - len(linestyle))) 74 else: 75 linestyle = [linestyle] * len(system) 76 # Custom colormap or colors 77 colors = None 78 if isinstance(cm, str): 79 cmap = plt.get_cmap(cm) 80 colors = cmap(np.linspace(cm_range[0], cm_range[1], len(system))) 81 elif color: 82 colors = color 83 if isinstance(colors, list): 84 if len(colors) < len(system): 85 colors.extend(['grey'] * (len(system) - len(colors))) 86 else: 87 colors = [colors] * len(system) 88 89 with plt.rc_context(rc): 90 plt.figure(layout='constrained') 91 plt.title(title_str) 92 plt.xlabel('Angle / rad') 93 plt.ylabel('Potential energy / meV') 94 if normalize: 95 plt.ylabel('Energy / V$_{3}$') 96 for s in system: 97 s.potential_values = s.potential_values / s.potential_max 98 if ylim: 99 plt.ylim(ylim) 100 101 plt.xticks([-2*np.pi, -3*np.pi/2, -np.pi, -np.pi/2, 0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi], [r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', r'$-\frac{\pi}{2}$', '0', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$']) 102 103 if colors is not None: 104 for i, s in enumerate(system): 105 plt.plot(s.grid, s.potential_values, marker=marker[i], linestyle=linestyle[i], label=s.comment, color=colors[i]) 106 else: # Regular plot 107 for i, s in enumerate(system): 108 plt.plot(s.grid, s.potential_values, marker=marker[i], linestyle=linestyle[i], label=s.comment) 109 110 if all(s.comment for s in system) and len(system) != 1: 111 leg = plt.legend() 112 leg.set_draggable(True) 113 114 plt.show() 115 116 117def energies( 118 data, 119 title:str=None, 120 rc:dict={}, 121 ) -> None: 122 """Plot the eigenvalues of `data` (System or a list of System objects). 123 124 You can use up to 1 tag per system to differentiate between molecular groups. 125 126 Additional matplotlib runtime configuration 127 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 128 can be set with the `rc` dict. 129 """ 130 if isinstance(data, System): 131 var = [data] 132 else: # Should be a list 133 systems.as_list(data) 134 var = data 135 136 V_colors = ['C0', 'C1', 'C2', 'C3', 'C4'] 137 E_colors = ['lightblue', 'sandybrown', 'lightgrey', 'lightcoral', 'plum'] 138 E_linestyles = ['--', ':', '-.'] 139 edgecolors = E_colors 140 141 V_linestyle = '-' 142 title = title if title else (var[0].comment if var[0].comment else 'Energy eigenvalues') 143 ylabel_text = f'Energy / meV' 144 xlabel_text = 'Angle / radians' 145 146 with plt.rc_context(rc): 147 plt.figure(layout='constrained') 148 plt.xlabel(xlabel_text) 149 plt.ylabel(ylabel_text) 150 plt.title(title) 151 plt.xticks([-2*np.pi, -3*np.pi/2, -np.pi, -np.pi/2, 0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi], [r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', r'$-\frac{\pi}{2}$', '0', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$']) 152 153 unique_potentials = [] 154 unique_groups = [] 155 for i, system in enumerate(var): 156 V_color = V_colors[i % len(V_colors)] 157 E_color = E_colors[i % len(E_colors)] 158 E_linestyle = E_linestyles[i % len(E_linestyles)] 159 edgecolor = edgecolors[i % len(edgecolors)] 160 161 # Plot potential energy if it is unique 162 if not any(np.array_equal(system.potential_values, value) for value in unique_potentials): 163 unique_potentials.append(system.potential_values) 164 plt.plot(system.grid, system.potential_values, color=V_color, linestyle=V_linestyle) 165 166 # Plot eigenvalues 167 if any(system.eigenvalues): 168 text_offset = 3 * len(unique_groups) 169 if system.tags not in unique_groups: 170 unique_groups.append(system.tags) 171 for j, energy in enumerate(system.eigenvalues): 172 plt.axhline(y=energy, color=E_color, linestyle=E_linestyle) 173 # Textbox positions are a bit weird when plotting more than 2 systems, but whatever... 174 plt.text(j%3*1.0 + text_offset, energy, f'$E_{{{j}}}$ = {round(energy,4):.04f}', va='top', bbox=dict(edgecolor=edgecolor, boxstyle='round,pad=0.2', facecolor='white', alpha=0.8)) 175 if len(systems.get_tags(var)) > 1: 176 plt.plot([], [], color=E_color, label=f'{system.tags} Energies') # Add to legend 177 178 if len(systems.get_tags(var)) > 1: 179 plt.subplots_adjust(right=0.85) 180 leg = plt.legend() #(bbox_to_anchor=(1.1, 0.5), loc='center', fontsize='small') 181 leg.set_draggable(True) 182 183 plt.show() 184 185 186def reduced_energies( 187 data:list, 188 title:str=None, 189 values:list=[], 190 legend:list=[], 191 rc:dict={}, 192 ) -> None: 193 """Plots the reduced energy of the system E/B vs the reduced potential energy V/B. 194 195 Takes a `data` list of System objects as input. 196 An optional `title` can be specified. 197 198 Optional maximum reduced potential `values` are plotted 199 as vertical lines (floats or ints) or regions 200 (lists inside the values list, from min to max). 201 A `legend` of the same len as `values` can be included. 202 These values are assumed to be divided by B by the user. 203 204 Additional matplotlib runtime configuration 205 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 206 can be set with the `rc` dict. 207 """ 208 if values and (isinstance(values, float) or isinstance(values, int) or isinstance(values, np.float64)): 209 values = [values] 210 if values and len(values) <= len(legend): 211 plot_legend = True 212 else: 213 plot_legend = False 214 legend = [''] * len(values) 215 systems.as_list(data) 216 title = title if title else (data[0].comment if data[0].comment else 'Reduced energies') 217 number_of_levels = data[0].searched_E 218 x = [] 219 for system in data: 220 potential_max_B = system.potential_max / system.B 221 x.append(potential_max_B) 222 colors = plt.cm.viridis(np.linspace(0, 1, number_of_levels+1)) # +1 to avoid the lighter tones 223 224 with plt.rc_context(rc): 225 plt.figure(layout='constrained') 226 227 for i in range(number_of_levels): 228 y = [] 229 for system in data: 230 eigenvalues_B_i = system.eigenvalues[i] / system.B 231 y.append(eigenvalues_B_i) 232 plt.plot(x, y, marker='', linestyle='-', color=colors[i]) 233 # Add vertical lines in the specified values 234 line_colors = plt.cm.tab10(np.linspace(0, 1, len(values))) 235 for i, value in enumerate(values): 236 if isinstance(value, list): 237 min_value = min(value) 238 max_value = max(value) 239 plt.axvspan(min_value, max_value, color=line_colors[i], alpha=0.2, linestyle='', label=legend[i]) 240 else: 241 plt.axvline(x=value, color=line_colors[i], linestyle='--', label=legend[i], alpha=0.5) 242 plt.xlabel('V$_{B}$ / B') 243 plt.ylabel('E / B') 244 plt.title(title) 245 if plot_legend: 246 leg = plt.legend() 247 leg.set_draggable(True) 248 plt.show() 249 250 251def wavefunction( 252 system:System, 253 title:str=None, 254 square:bool=True, 255 levels:list|int=[0, 1, 2], 256 overlap:bool|int=False, 257 yticks:bool=False, 258 rc:dict={}, 259 ) -> None: 260 """Plot the wavefunction of a `system` for the specified `levels`. 261 262 Wavefunctions are squared by default, showing the probabilities; 263 To show the actual wavefunctions, set `square = False`. 264 265 `levels` can be a list of indexes, or the number of levels to plot. 266 267 Specific wavefunctions can be overlapped with `overlap` as a list with the target indexes. 268 The `overlap` value can also be the max number of wavefunctions to add. 269 All found wavefunctions can be added together with `overlap = True`; 270 but note that this overlap is limited by the number of System.searched_E, 271 that must be specified before solving the system. 272 Setting `overlap` will ignore the `levels` argument. 273 274 Set `yticks = True` to plot the wavefunction yticks. 275 276 Additional matplotlib runtime configuration 277 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 278 can be set with the `rc` dict. 279 """ 280 data = deepcopy(system) 281 eigenvectors = data.eigenvectors 282 title = title if title else (data.comment if data.comment else 'System wavefunction') 283 with plt.rc_context(rc): 284 fig, ax1 = plt.subplots(layout='constrained') 285 plt.title(title) 286 ax1.set_xlabel('Angle / radians') 287 ax1.set_ylabel('Potential / meV') 288 ax1.set_xticks([-2*np.pi, -3*np.pi/2, -np.pi, -np.pi/2, 0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi], [r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', r'$-\frac{\pi}{2}$', '0', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$']) 289 ax1.plot(data.grid, data.potential_values, color='blue', linestyle='-') 290 ax2 = ax1.twinx() 291 if not yticks: 292 ax2.set_yticks([]) 293 ax2.set_ylabel('Squared wavefunction' if square else 'Wavefunction') 294 # Set levels list 295 if isinstance(levels, int): 296 levels = [levels] 297 if not isinstance(levels, list): 298 raise ValueError('levels must be an int or a list of ints') 299 # Set overlap if requested 300 if overlap == True and isinstance(overlap, bool): 301 eigenvectors = [np.sum(eigenvectors, axis=0)] 302 levels = [0] 303 show_legend = False 304 elif overlap is not False and (isinstance(overlap, int) or isinstance(overlap, float)): 305 max_int = int(overlap) 306 eigenvectors = [np.sum(eigenvectors[:max_int], axis=0)] 307 levels = [0] 308 show_legend = False 309 elif isinstance(overlap, list): 310 eigenvectors = [np.sum([eigenvectors[i] for i in overlap], axis=0)] 311 levels = [0] 312 show_legend = False 313 if len(levels) > 1: 314 show_legend = True 315 else: 316 show_legend = False 317 # Square values if so 318 if square: 319 eigenvectors = [vec**2 for vec in eigenvectors] 320 # Plot the wavefunction 321 for i in levels: 322 ax2.plot(data.grid, eigenvectors[i], linestyle='--', label=f'{i}') 323 if show_legend: 324 leg = fig.legend()#(loc='upper right', bbox_to_anchor=(0.9, 0.88), fontsize='small', title='Index') 325 leg.set_draggable(True) 326 plt.show() 327 328 329def splittings( 330 data:list, 331 title:str=None, 332 units:str='ueV', 333 rc:dict={}, 334 ) -> None: 335 """Plot the tunnel splitting energies of a `data` list of systems. 336 337 The different `System.comment` are shown in the horizontal axis. 338 An optional `title` can be specified. 339 Default units shown are $\\mu$eV (`'ueV'`). 340 Available units are: `'ueV'`, `'meV'`, `'Ry'`, or `'B'` (free rotor units). 341 342 Additional matplotlib runtime configuration 343 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 344 can be set with the `rc` dict. 345 """ 346 title = title if title != None else 'Tunnel splitting energies' 347 calcs = deepcopy(data) 348 calcs = systems.as_list(calcs) 349 350 with plt.rc_context(rc): 351 fig, ax = plt.subplots(layout='constrained') 352 ax.set_ylabel("Energy / meV") 353 354 y = [c.splittings[0] for c in calcs] 355 x = [c.comment for c in calcs] 356 # What units do we want? 357 if units.lower() in alias.units['ueV']: 358 y = [j * 1000 for j in y] # Convert meV to micro eV 359 ax.set_ylabel("Energy / $\\mu$eV") 360 elif units.lower() in alias.units['Ry']: 361 y = [j * constants.meV_to_Ry for j in y] 362 ax.set_ylabel("Energy / Ry") 363 elif units.upper() == 'B': 364 y = [j / c.B for j, c in zip(y, calcs)] 365 ax.set_ylabel("Energy / B") 366 #else: # It's okay let's use meV 367 368 ax.bar(range(len(y)), y) 369 for i, comment in enumerate(x): 370 ax.text(x=i, y=0, s=comment+' ', rotation=45, verticalalignment='top', horizontalalignment='right') 371 ax.set_xlabel("") 372 ax.set_title(title) 373 ax.set_xticks([]) 374 fig.tight_layout() 375 plt.show() 376 377 378def convergence(data:list, rc:dict={}) -> None: 379 """Plot the energy convergence of a `data` list of Systems as a function of the gridsize. 380 381 Additional matplotlib runtime configuration 382 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 383 can be set with the `rc` dict. 384 """ 385 systems.as_list(data) 386 gridsizes = [system.gridsize for system in data] 387 runtimes = [system.runtime for system in data] 388 deviations = [] # List of lists, containing all eigenvalue deviations for every system 389 searched_E = data[0].searched_E 390 for system in data: 391 deviation_list = [] 392 for i, eigenvalue in enumerate(system.eigenvalues): 393 ideal_E = systems.calculate_ideal_E(i) 394 deviation = abs(ideal_E - eigenvalue) 395 deviation_list.append(deviation) 396 deviation_list = deviation_list[1:] # Remove ground state 397 deviations.append(deviation_list) 398 # Plotting 399 with plt.rc_context(rc): 400 fig, ax1 = plt.subplots(layout='constrained') 401 ax1.set_xlabel('Grid size') 402 ax1.set_ylabel('Error / meV') 403 ax1.set_xscale('log') 404 ax1.set_yscale('log') 405 ax2 = ax1.twinx() 406 ax2.set_ylabel('Runtime / s') 407 ax2.set_yscale('log') 408 ax2.plot(gridsizes, runtimes, color='tab:grey', label='Runtime', linestyle='--') 409 colors = plt.cm.viridis(np.linspace(0, 1, searched_E)) # Should be searched_E-1 but we want to avoid lighter colors 410 for i in range(searched_E-1): 411 if i % 2 == 0: # Ignore even numbers, since those levels are degenerated. 412 continue 413 ax1.plot(gridsizes, [dev[i] for dev in deviations], label=f'$E_{{{int((i+1)/2)}}}$', color=colors[i]) 414 leg = fig.legend()#(loc='upper right', bbox_to_anchor=(0.9, 0.88), fontsize='small') 415 leg.set_draggable(True) 416 plt.title(data[0].comment if data[0].comment else 'Energy convergence vs grid size') 417 plt.show()
32def potential( 33 data:System|list, 34 title:str=None, 35 marker:str|list='', 36 linestyle:str|list='-', 37 cm:str=None, 38 cm_range:tuple|list=(0.0,0.8), 39 color:list|tuple|str=None, 40 normalize:bool=False, 41 ylim:tuple=None, 42 rc:dict={}, 43 ) -> None: 44 """Plot the potential values of `data` (System object, or list of systems). 45 46 Title can be customized with `title`. 47 If empty, system[0].comment will be used as title if no more comments are present. 48 49 `marker` and `linestyle` can be a Matplotlib string or list of strings. 50 Custom colormaps can be set through `cm`, in a custom `cm_range` from 0 to 1. 51 Custom colors can also be set through `color`. 52 Alternatively, custom color lists can be set through the `color` variable. 53 54 Set `normalize = True` to normalize by their respective `qrotor.system.System.potential_max`. 55 This can be useful if you have performed subtractions or similar operations. 56 In this case, you might also want to play with `ylim` to adjust the y-axis limits. 57 58 Additional matplotlib runtime configuration 59 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 60 can be set with the `rc` dict. 61 """ 62 data_copy = deepcopy(data) 63 system = systems.as_list(data_copy) 64 title_str = title if title else (system[0].comment if (system[0].comment and (len(system) == 1 or not system[-1].comment)) else 'Rotational potential energy') 65 # Marker as a list 66 if isinstance(marker, list): 67 if len(marker) < len(system): 68 marker.extend([''] * (len(system) - len(marker))) 69 else: 70 marker = [marker] * len(system) 71 # Linestyle as a list 72 if isinstance(linestyle, list): 73 if len(linestyle) < len(system): 74 linestyle.extend(['-'] * (len(system) - len(linestyle))) 75 else: 76 linestyle = [linestyle] * len(system) 77 # Custom colormap or colors 78 colors = None 79 if isinstance(cm, str): 80 cmap = plt.get_cmap(cm) 81 colors = cmap(np.linspace(cm_range[0], cm_range[1], len(system))) 82 elif color: 83 colors = color 84 if isinstance(colors, list): 85 if len(colors) < len(system): 86 colors.extend(['grey'] * (len(system) - len(colors))) 87 else: 88 colors = [colors] * len(system) 89 90 with plt.rc_context(rc): 91 plt.figure(layout='constrained') 92 plt.title(title_str) 93 plt.xlabel('Angle / rad') 94 plt.ylabel('Potential energy / meV') 95 if normalize: 96 plt.ylabel('Energy / V$_{3}$') 97 for s in system: 98 s.potential_values = s.potential_values / s.potential_max 99 if ylim: 100 plt.ylim(ylim) 101 102 plt.xticks([-2*np.pi, -3*np.pi/2, -np.pi, -np.pi/2, 0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi], [r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', r'$-\frac{\pi}{2}$', '0', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$']) 103 104 if colors is not None: 105 for i, s in enumerate(system): 106 plt.plot(s.grid, s.potential_values, marker=marker[i], linestyle=linestyle[i], label=s.comment, color=colors[i]) 107 else: # Regular plot 108 for i, s in enumerate(system): 109 plt.plot(s.grid, s.potential_values, marker=marker[i], linestyle=linestyle[i], label=s.comment) 110 111 if all(s.comment for s in system) and len(system) != 1: 112 leg = plt.legend() 113 leg.set_draggable(True) 114 115 plt.show()
Plot the potential values of data (System object, or list of systems).
Title can be customized with title.
If empty, system[0].comment will be used as title if no more comments are present.
marker and linestyle can be a Matplotlib string or list of strings.
Custom colormaps can be set through cm, in a custom cm_range from 0 to 1.
Custom colors can also be set through color.
Alternatively, custom color lists can be set through the color variable.
Set normalize = True to normalize by their respective qrotor.system.System.potential_max.
This can be useful if you have performed subtractions or similar operations.
In this case, you might also want to play with ylim to adjust the y-axis limits.
Additional matplotlib runtime configuration
rcParams
can be set with the rc dict.
118def energies( 119 data, 120 title:str=None, 121 rc:dict={}, 122 ) -> None: 123 """Plot the eigenvalues of `data` (System or a list of System objects). 124 125 You can use up to 1 tag per system to differentiate between molecular groups. 126 127 Additional matplotlib runtime configuration 128 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 129 can be set with the `rc` dict. 130 """ 131 if isinstance(data, System): 132 var = [data] 133 else: # Should be a list 134 systems.as_list(data) 135 var = data 136 137 V_colors = ['C0', 'C1', 'C2', 'C3', 'C4'] 138 E_colors = ['lightblue', 'sandybrown', 'lightgrey', 'lightcoral', 'plum'] 139 E_linestyles = ['--', ':', '-.'] 140 edgecolors = E_colors 141 142 V_linestyle = '-' 143 title = title if title else (var[0].comment if var[0].comment else 'Energy eigenvalues') 144 ylabel_text = f'Energy / meV' 145 xlabel_text = 'Angle / radians' 146 147 with plt.rc_context(rc): 148 plt.figure(layout='constrained') 149 plt.xlabel(xlabel_text) 150 plt.ylabel(ylabel_text) 151 plt.title(title) 152 plt.xticks([-2*np.pi, -3*np.pi/2, -np.pi, -np.pi/2, 0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi], [r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', r'$-\frac{\pi}{2}$', '0', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$']) 153 154 unique_potentials = [] 155 unique_groups = [] 156 for i, system in enumerate(var): 157 V_color = V_colors[i % len(V_colors)] 158 E_color = E_colors[i % len(E_colors)] 159 E_linestyle = E_linestyles[i % len(E_linestyles)] 160 edgecolor = edgecolors[i % len(edgecolors)] 161 162 # Plot potential energy if it is unique 163 if not any(np.array_equal(system.potential_values, value) for value in unique_potentials): 164 unique_potentials.append(system.potential_values) 165 plt.plot(system.grid, system.potential_values, color=V_color, linestyle=V_linestyle) 166 167 # Plot eigenvalues 168 if any(system.eigenvalues): 169 text_offset = 3 * len(unique_groups) 170 if system.tags not in unique_groups: 171 unique_groups.append(system.tags) 172 for j, energy in enumerate(system.eigenvalues): 173 plt.axhline(y=energy, color=E_color, linestyle=E_linestyle) 174 # Textbox positions are a bit weird when plotting more than 2 systems, but whatever... 175 plt.text(j%3*1.0 + text_offset, energy, f'$E_{{{j}}}$ = {round(energy,4):.04f}', va='top', bbox=dict(edgecolor=edgecolor, boxstyle='round,pad=0.2', facecolor='white', alpha=0.8)) 176 if len(systems.get_tags(var)) > 1: 177 plt.plot([], [], color=E_color, label=f'{system.tags} Energies') # Add to legend 178 179 if len(systems.get_tags(var)) > 1: 180 plt.subplots_adjust(right=0.85) 181 leg = plt.legend() #(bbox_to_anchor=(1.1, 0.5), loc='center', fontsize='small') 182 leg.set_draggable(True) 183 184 plt.show()
Plot the eigenvalues of data (System or a list of System objects).
You can use up to 1 tag per system to differentiate between molecular groups.
Additional matplotlib runtime configuration
rcParams
can be set with the rc dict.
187def reduced_energies( 188 data:list, 189 title:str=None, 190 values:list=[], 191 legend:list=[], 192 rc:dict={}, 193 ) -> None: 194 """Plots the reduced energy of the system E/B vs the reduced potential energy V/B. 195 196 Takes a `data` list of System objects as input. 197 An optional `title` can be specified. 198 199 Optional maximum reduced potential `values` are plotted 200 as vertical lines (floats or ints) or regions 201 (lists inside the values list, from min to max). 202 A `legend` of the same len as `values` can be included. 203 These values are assumed to be divided by B by the user. 204 205 Additional matplotlib runtime configuration 206 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 207 can be set with the `rc` dict. 208 """ 209 if values and (isinstance(values, float) or isinstance(values, int) or isinstance(values, np.float64)): 210 values = [values] 211 if values and len(values) <= len(legend): 212 plot_legend = True 213 else: 214 plot_legend = False 215 legend = [''] * len(values) 216 systems.as_list(data) 217 title = title if title else (data[0].comment if data[0].comment else 'Reduced energies') 218 number_of_levels = data[0].searched_E 219 x = [] 220 for system in data: 221 potential_max_B = system.potential_max / system.B 222 x.append(potential_max_B) 223 colors = plt.cm.viridis(np.linspace(0, 1, number_of_levels+1)) # +1 to avoid the lighter tones 224 225 with plt.rc_context(rc): 226 plt.figure(layout='constrained') 227 228 for i in range(number_of_levels): 229 y = [] 230 for system in data: 231 eigenvalues_B_i = system.eigenvalues[i] / system.B 232 y.append(eigenvalues_B_i) 233 plt.plot(x, y, marker='', linestyle='-', color=colors[i]) 234 # Add vertical lines in the specified values 235 line_colors = plt.cm.tab10(np.linspace(0, 1, len(values))) 236 for i, value in enumerate(values): 237 if isinstance(value, list): 238 min_value = min(value) 239 max_value = max(value) 240 plt.axvspan(min_value, max_value, color=line_colors[i], alpha=0.2, linestyle='', label=legend[i]) 241 else: 242 plt.axvline(x=value, color=line_colors[i], linestyle='--', label=legend[i], alpha=0.5) 243 plt.xlabel('V$_{B}$ / B') 244 plt.ylabel('E / B') 245 plt.title(title) 246 if plot_legend: 247 leg = plt.legend() 248 leg.set_draggable(True) 249 plt.show()
Plots the reduced energy of the system E/B vs the reduced potential energy V/B.
Takes a data list of System objects as input.
An optional title can be specified.
Optional maximum reduced potential values are plotted
as vertical lines (floats or ints) or regions
(lists inside the values list, from min to max).
A legend of the same len as values can be included.
These values are assumed to be divided by B by the user.
Additional matplotlib runtime configuration
rcParams
can be set with the rc dict.
252def wavefunction( 253 system:System, 254 title:str=None, 255 square:bool=True, 256 levels:list|int=[0, 1, 2], 257 overlap:bool|int=False, 258 yticks:bool=False, 259 rc:dict={}, 260 ) -> None: 261 """Plot the wavefunction of a `system` for the specified `levels`. 262 263 Wavefunctions are squared by default, showing the probabilities; 264 To show the actual wavefunctions, set `square = False`. 265 266 `levels` can be a list of indexes, or the number of levels to plot. 267 268 Specific wavefunctions can be overlapped with `overlap` as a list with the target indexes. 269 The `overlap` value can also be the max number of wavefunctions to add. 270 All found wavefunctions can be added together with `overlap = True`; 271 but note that this overlap is limited by the number of System.searched_E, 272 that must be specified before solving the system. 273 Setting `overlap` will ignore the `levels` argument. 274 275 Set `yticks = True` to plot the wavefunction yticks. 276 277 Additional matplotlib runtime configuration 278 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 279 can be set with the `rc` dict. 280 """ 281 data = deepcopy(system) 282 eigenvectors = data.eigenvectors 283 title = title if title else (data.comment if data.comment else 'System wavefunction') 284 with plt.rc_context(rc): 285 fig, ax1 = plt.subplots(layout='constrained') 286 plt.title(title) 287 ax1.set_xlabel('Angle / radians') 288 ax1.set_ylabel('Potential / meV') 289 ax1.set_xticks([-2*np.pi, -3*np.pi/2, -np.pi, -np.pi/2, 0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi], [r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', r'$-\frac{\pi}{2}$', '0', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$']) 290 ax1.plot(data.grid, data.potential_values, color='blue', linestyle='-') 291 ax2 = ax1.twinx() 292 if not yticks: 293 ax2.set_yticks([]) 294 ax2.set_ylabel('Squared wavefunction' if square else 'Wavefunction') 295 # Set levels list 296 if isinstance(levels, int): 297 levels = [levels] 298 if not isinstance(levels, list): 299 raise ValueError('levels must be an int or a list of ints') 300 # Set overlap if requested 301 if overlap == True and isinstance(overlap, bool): 302 eigenvectors = [np.sum(eigenvectors, axis=0)] 303 levels = [0] 304 show_legend = False 305 elif overlap is not False and (isinstance(overlap, int) or isinstance(overlap, float)): 306 max_int = int(overlap) 307 eigenvectors = [np.sum(eigenvectors[:max_int], axis=0)] 308 levels = [0] 309 show_legend = False 310 elif isinstance(overlap, list): 311 eigenvectors = [np.sum([eigenvectors[i] for i in overlap], axis=0)] 312 levels = [0] 313 show_legend = False 314 if len(levels) > 1: 315 show_legend = True 316 else: 317 show_legend = False 318 # Square values if so 319 if square: 320 eigenvectors = [vec**2 for vec in eigenvectors] 321 # Plot the wavefunction 322 for i in levels: 323 ax2.plot(data.grid, eigenvectors[i], linestyle='--', label=f'{i}') 324 if show_legend: 325 leg = fig.legend()#(loc='upper right', bbox_to_anchor=(0.9, 0.88), fontsize='small', title='Index') 326 leg.set_draggable(True) 327 plt.show()
Plot the wavefunction of a system for the specified levels.
Wavefunctions are squared by default, showing the probabilities;
To show the actual wavefunctions, set square = False.
levels can be a list of indexes, or the number of levels to plot.
Specific wavefunctions can be overlapped with overlap as a list with the target indexes.
The overlap value can also be the max number of wavefunctions to add.
All found wavefunctions can be added together with overlap = True;
but note that this overlap is limited by the number of System.searched_E,
that must be specified before solving the system.
Setting overlap will ignore the levels argument.
Set yticks = True to plot the wavefunction yticks.
Additional matplotlib runtime configuration
rcParams
can be set with the rc dict.
330def splittings( 331 data:list, 332 title:str=None, 333 units:str='ueV', 334 rc:dict={}, 335 ) -> None: 336 """Plot the tunnel splitting energies of a `data` list of systems. 337 338 The different `System.comment` are shown in the horizontal axis. 339 An optional `title` can be specified. 340 Default units shown are $\\mu$eV (`'ueV'`). 341 Available units are: `'ueV'`, `'meV'`, `'Ry'`, or `'B'` (free rotor units). 342 343 Additional matplotlib runtime configuration 344 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 345 can be set with the `rc` dict. 346 """ 347 title = title if title != None else 'Tunnel splitting energies' 348 calcs = deepcopy(data) 349 calcs = systems.as_list(calcs) 350 351 with plt.rc_context(rc): 352 fig, ax = plt.subplots(layout='constrained') 353 ax.set_ylabel("Energy / meV") 354 355 y = [c.splittings[0] for c in calcs] 356 x = [c.comment for c in calcs] 357 # What units do we want? 358 if units.lower() in alias.units['ueV']: 359 y = [j * 1000 for j in y] # Convert meV to micro eV 360 ax.set_ylabel("Energy / $\\mu$eV") 361 elif units.lower() in alias.units['Ry']: 362 y = [j * constants.meV_to_Ry for j in y] 363 ax.set_ylabel("Energy / Ry") 364 elif units.upper() == 'B': 365 y = [j / c.B for j, c in zip(y, calcs)] 366 ax.set_ylabel("Energy / B") 367 #else: # It's okay let's use meV 368 369 ax.bar(range(len(y)), y) 370 for i, comment in enumerate(x): 371 ax.text(x=i, y=0, s=comment+' ', rotation=45, verticalalignment='top', horizontalalignment='right') 372 ax.set_xlabel("") 373 ax.set_title(title) 374 ax.set_xticks([]) 375 fig.tight_layout() 376 plt.show()
Plot the tunnel splitting energies of a data list of systems.
The different System.comment are shown in the horizontal axis.
An optional title can be specified.
Default units shown are $\mu$eV ('ueV').
Available units are: 'ueV', 'meV', 'Ry', or 'B' (free rotor units).
Additional matplotlib runtime configuration
rcParams
can be set with the rc dict.
379def convergence(data:list, rc:dict={}) -> None: 380 """Plot the energy convergence of a `data` list of Systems as a function of the gridsize. 381 382 Additional matplotlib runtime configuration 383 [rcParams](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.RcParams) 384 can be set with the `rc` dict. 385 """ 386 systems.as_list(data) 387 gridsizes = [system.gridsize for system in data] 388 runtimes = [system.runtime for system in data] 389 deviations = [] # List of lists, containing all eigenvalue deviations for every system 390 searched_E = data[0].searched_E 391 for system in data: 392 deviation_list = [] 393 for i, eigenvalue in enumerate(system.eigenvalues): 394 ideal_E = systems.calculate_ideal_E(i) 395 deviation = abs(ideal_E - eigenvalue) 396 deviation_list.append(deviation) 397 deviation_list = deviation_list[1:] # Remove ground state 398 deviations.append(deviation_list) 399 # Plotting 400 with plt.rc_context(rc): 401 fig, ax1 = plt.subplots(layout='constrained') 402 ax1.set_xlabel('Grid size') 403 ax1.set_ylabel('Error / meV') 404 ax1.set_xscale('log') 405 ax1.set_yscale('log') 406 ax2 = ax1.twinx() 407 ax2.set_ylabel('Runtime / s') 408 ax2.set_yscale('log') 409 ax2.plot(gridsizes, runtimes, color='tab:grey', label='Runtime', linestyle='--') 410 colors = plt.cm.viridis(np.linspace(0, 1, searched_E)) # Should be searched_E-1 but we want to avoid lighter colors 411 for i in range(searched_E-1): 412 if i % 2 == 0: # Ignore even numbers, since those levels are degenerated. 413 continue 414 ax1.plot(gridsizes, [dev[i] for dev in deviations], label=f'$E_{{{int((i+1)/2)}}}$', color=colors[i]) 415 leg = fig.legend()#(loc='upper right', bbox_to_anchor=(0.9, 0.88), fontsize='small') 416 leg.set_draggable(True) 417 plt.title(data[0].comment if data[0].comment else 'Energy convergence vs grid size') 418 plt.show()
Plot the energy convergence of a data list of Systems as a function of the gridsize.
Additional matplotlib runtime configuration
rcParams
can be set with the rc dict.