qrotor 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of qrotor might be problematic. Click here for more details.
- qrotor/__init__.py +14 -0
- qrotor/_version.py +14 -0
- qrotor/constants.py +85 -0
- qrotor/plot.py +337 -0
- qrotor/potential.py +472 -0
- qrotor/rotate.py +202 -0
- qrotor/solve.py +271 -0
- qrotor/system.py +275 -0
- qrotor/systems.py +245 -0
- qrotor-4.0.0.dist-info/METADATA +168 -0
- qrotor-4.0.0.dist-info/RECORD +16 -0
- qrotor-4.0.0.dist-info/WHEEL +5 -0
- qrotor-4.0.0.dist-info/licenses/LICENSE +661 -0
- qrotor-4.0.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_qrotor.py +101 -0
qrotor/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. include:: ../_README_temp.md
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from ._version import __version__ as version
|
|
7
|
+
from .system import System
|
|
8
|
+
from .constants import *
|
|
9
|
+
from . import systems
|
|
10
|
+
from . import rotate
|
|
11
|
+
from . import potential
|
|
12
|
+
from . import solve
|
|
13
|
+
from . import plot
|
|
14
|
+
|
qrotor/_version.py
ADDED
qrotor/constants.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# Description
|
|
3
|
+
|
|
4
|
+
Common constants and default inertia values used in the QRotor subpackage.
|
|
5
|
+
|
|
6
|
+
Bond lengths and angles were obtained from MAPbI3, see
|
|
7
|
+
[K. Drużbicki *et al*., Crystal Growth & Design 24, 391–404 (2024)](https://doi.org/10.1021/acs.cgd.3c01112).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import periodictable
|
|
15
|
+
import scipy.constants as const
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Distance between Carbon and Hydrogen atoms (measured from MAPbI3)
|
|
19
|
+
distance_CH = 1.09285 # Angstroms
|
|
20
|
+
"""Distance of the C-H bond, in Angstroms."""
|
|
21
|
+
distance_NH = 1.040263 # Angstroms
|
|
22
|
+
"""Distance of the N-H bond, in Angstroms."""
|
|
23
|
+
|
|
24
|
+
# Angles between atoms: C-C-H or N-C-H etc (from MAPbI3)
|
|
25
|
+
angle_CH_external = 108.7223
|
|
26
|
+
"""External angle of the X-C-H bond, in degrees."""
|
|
27
|
+
angle_NH_external = 111.29016
|
|
28
|
+
"""External angle of the X-N-H bond, in degrees."""
|
|
29
|
+
angle_CH = 180 - angle_CH_external
|
|
30
|
+
"""Internal angle of the X-C-H bond, in degrees."""
|
|
31
|
+
angle_NH = 180 - angle_NH_external
|
|
32
|
+
"""Internal angle of the X-N-H bond, in degrees."""
|
|
33
|
+
|
|
34
|
+
# Rotation radius (calculated from distance and angle)
|
|
35
|
+
r_CH = distance_CH * np.sin(np.deg2rad(angle_CH)) * 1e-10
|
|
36
|
+
"""Rotation radius of the methyl group, in meters."""
|
|
37
|
+
r_NH = distance_NH * np.sin(np.deg2rad(angle_NH)) * 1e-10
|
|
38
|
+
"""Rotation radius of the amine group, in meters."""
|
|
39
|
+
|
|
40
|
+
# Inertia, SI units
|
|
41
|
+
_amu = const.physical_constants['atomic mass constant'][0]
|
|
42
|
+
I_CH3 = 3 * (periodictable.H.mass * _amu * r_CH**2)
|
|
43
|
+
"""Inertia of CH3, in kg·m^2."""
|
|
44
|
+
I_CD3 = 3 * (periodictable.D.mass * _amu * r_CH**2)
|
|
45
|
+
"""Inertia of CD3, in kg·m^2."""
|
|
46
|
+
I_NH3 = 3 * (periodictable.H.mass * _amu * r_NH**2)
|
|
47
|
+
"""Inertia of NH3, in kg·m^2."""
|
|
48
|
+
I_ND3 = 3 * (periodictable.D.mass * _amu * r_NH**2)
|
|
49
|
+
"""Inertia of ND3, in kg·m^2."""
|
|
50
|
+
|
|
51
|
+
# Rotational energy
|
|
52
|
+
_hbar = const.physical_constants['reduced Planck constant'][0]
|
|
53
|
+
B_CH3 = ((_hbar**2) / (2 * I_CH3)) * const.eV * 1000
|
|
54
|
+
"""Rotational energy of CH3, in meV·s/kg·m^2."""
|
|
55
|
+
B_CD3 = ((_hbar**2) / (2 * I_CD3)) * const.eV * 1000
|
|
56
|
+
"""Rotational energy of CD3, in meV·s/kg·m^2."""
|
|
57
|
+
B_NH3 = ((_hbar**2) / (2 * I_NH3)) * const.eV * 1000
|
|
58
|
+
"""Rotational energy of NH3, in meV·s/kg·m^2."""
|
|
59
|
+
B_ND3 = ((_hbar**2) / (2 * I_ND3)) * const.eV * 1000
|
|
60
|
+
"""Rotational energy of ND3, in meV·s/kg·m^2."""
|
|
61
|
+
|
|
62
|
+
# Potential constants from titov2023 [C1, C2, C3, C4, C5]
|
|
63
|
+
constants_titov2023 = [
|
|
64
|
+
[2.7860, 0.0130,-1.5284,-0.0037,-1.2791], # ZIF-8
|
|
65
|
+
[2.6507, 0.0158,-1.4111,-0.0007,-1.2547], # ZIF-8 + Ar-1
|
|
66
|
+
[2.1852, 0.0164,-1.0017, 0.0003,-1.2061], # ZIF-8 + Ar-{1,2}
|
|
67
|
+
[5.9109, 0.0258,-7.0152,-0.0168, 1.0213], # ZIF-8 + Ar-{1,2,3}
|
|
68
|
+
[1.4526, 0.0134,-0.3196, 0.0005,-1.1461], # ZIF-8 + Ar-{1,2,4}
|
|
69
|
+
]
|
|
70
|
+
"""Potential constants from
|
|
71
|
+
[K. Titov et al., Phys. Rev. Mater. 7, 073402 (2023)](https://link.aps.org/doi/10.1103/PhysRevMaterials.7.073402)
|
|
72
|
+
for the `qrotor.potential.titov2023` potential.
|
|
73
|
+
In meV units.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# Quick conversion factors
|
|
77
|
+
eV_to_Ry = const.physical_constants['Rydberg constant times hc in eV'][0]
|
|
78
|
+
"""Quick conversion factor from eV to Rydberg energy."""
|
|
79
|
+
meV_to_Ry = eV_to_Ry / 1000
|
|
80
|
+
"""Quick conversion factor from meV to Rydberg energy."""
|
|
81
|
+
Ry_to_eV = 1 / eV_to_Ry
|
|
82
|
+
"""Quick conversion factor from Rydberg to eV."""
|
|
83
|
+
Ry_to_meV = 1 / meV_to_Ry
|
|
84
|
+
"""Quick conversion factor from Rydberg to meV."""
|
|
85
|
+
|
qrotor/plot.py
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# Description
|
|
3
|
+
|
|
4
|
+
This 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 |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
from .system import System
|
|
23
|
+
from . import systems
|
|
24
|
+
from . import constants
|
|
25
|
+
import matplotlib.pyplot as plt
|
|
26
|
+
import numpy as np
|
|
27
|
+
from copy import deepcopy
|
|
28
|
+
import aton.alias as alias
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def potential(
|
|
32
|
+
data,
|
|
33
|
+
title:str=None,
|
|
34
|
+
marker='',
|
|
35
|
+
linestyle='-',
|
|
36
|
+
cm:bool=False,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Plot the potential values of `data` (System object, or list of systems).
|
|
39
|
+
|
|
40
|
+
Title can be customized with `title`.
|
|
41
|
+
If empty, system[0].comment will be used as title if no more comments are present.
|
|
42
|
+
|
|
43
|
+
`marker` and `linestyle` can be a Matplotlib string or list of strings.
|
|
44
|
+
Optionally, the Viridis colormap can be used with `cm = True`.
|
|
45
|
+
"""
|
|
46
|
+
system = systems.as_list(data)
|
|
47
|
+
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')
|
|
48
|
+
# Marker as a list
|
|
49
|
+
if isinstance(marker, list):
|
|
50
|
+
if len(marker) < len(system):
|
|
51
|
+
marker.extend([''] * (len(system) - len(marker)))
|
|
52
|
+
else:
|
|
53
|
+
marker = [marker] * len(system)
|
|
54
|
+
# Linestyle as a list
|
|
55
|
+
if isinstance(linestyle, list):
|
|
56
|
+
if len(linestyle) < len(system):
|
|
57
|
+
linestyle.extend(['-'] * (len(system) - len(linestyle)))
|
|
58
|
+
else:
|
|
59
|
+
linestyle = [linestyle] * len(system)
|
|
60
|
+
|
|
61
|
+
plt.figure()
|
|
62
|
+
plt.title(title_str)
|
|
63
|
+
plt.xlabel('Angle / rad')
|
|
64
|
+
plt.ylabel('Potential energy / meV')
|
|
65
|
+
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$'])
|
|
66
|
+
|
|
67
|
+
if cm: # Plot using a colormap
|
|
68
|
+
colors = plt.cm.viridis(np.linspace(0, 1, len(system)+1)) # +1 to avoid the lighter tones
|
|
69
|
+
for i, s in enumerate(system):
|
|
70
|
+
plt.plot(s.grid, s.potential_values, marker=marker[i], linestyle=linestyle[i], label=s.comment, color=colors[i])
|
|
71
|
+
else: # Regular plot
|
|
72
|
+
for i, s in enumerate(system):
|
|
73
|
+
plt.plot(s.grid, s.potential_values, marker=marker[i], linestyle=linestyle[i], label=s.comment)
|
|
74
|
+
|
|
75
|
+
if all(s.comment for s in system) and len(system) != 1:
|
|
76
|
+
plt.legend(fontsize='small')
|
|
77
|
+
|
|
78
|
+
plt.show()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def energies(
|
|
82
|
+
data,
|
|
83
|
+
title:str=None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Plot the eigenvalues of `data` (System or a list of System objects)."""
|
|
86
|
+
if isinstance(data, System):
|
|
87
|
+
var = [data]
|
|
88
|
+
else: # Should be a list
|
|
89
|
+
systems.as_list(data)
|
|
90
|
+
var = data
|
|
91
|
+
|
|
92
|
+
V_colors = ['C0', 'C1', 'C2', 'C3', 'C4']
|
|
93
|
+
E_colors = ['lightblue', 'sandybrown', 'lightgrey', 'lightcoral', 'plum']
|
|
94
|
+
E_linestyles = ['--', ':', '-.']
|
|
95
|
+
edgecolors = E_colors
|
|
96
|
+
|
|
97
|
+
V_linestyle = '-'
|
|
98
|
+
title = title if title else (var[0].comment if var[0].comment else 'Energy eigenvalues')
|
|
99
|
+
ylabel_text = f'Energy / meV'
|
|
100
|
+
xlabel_text = 'Angle / radians'
|
|
101
|
+
|
|
102
|
+
plt.figure(figsize=(10, 6))
|
|
103
|
+
plt.xlabel(xlabel_text)
|
|
104
|
+
plt.ylabel(ylabel_text)
|
|
105
|
+
plt.title(title)
|
|
106
|
+
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$'])
|
|
107
|
+
|
|
108
|
+
unique_potentials = []
|
|
109
|
+
unique_groups = []
|
|
110
|
+
for i, system in enumerate(var):
|
|
111
|
+
V_color = V_colors[i % len(V_colors)]
|
|
112
|
+
E_color = E_colors[i % len(E_colors)]
|
|
113
|
+
E_linestyle = E_linestyles[i % len(E_linestyles)]
|
|
114
|
+
edgecolor = edgecolors[i % len(edgecolors)]
|
|
115
|
+
|
|
116
|
+
# Plot potential energy if it is unique
|
|
117
|
+
if not any(np.array_equal(system.potential_values, value) for value in unique_potentials):
|
|
118
|
+
unique_potentials.append(system.potential_values)
|
|
119
|
+
plt.plot(system.grid, system.potential_values, color=V_color, linestyle=V_linestyle)
|
|
120
|
+
|
|
121
|
+
# Plot eigenvalues
|
|
122
|
+
if any(system.eigenvalues):
|
|
123
|
+
text_offset = 3 * len(unique_groups)
|
|
124
|
+
if system.group not in unique_groups:
|
|
125
|
+
unique_groups.append(system.group)
|
|
126
|
+
for j, energy in enumerate(system.eigenvalues):
|
|
127
|
+
plt.axhline(y=energy, color=E_color, linestyle=E_linestyle)
|
|
128
|
+
# Textbox positions are a bit weird when plotting more than 2 systems, but whatever...
|
|
129
|
+
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))
|
|
130
|
+
if len(systems.get_groups(var)) > 1:
|
|
131
|
+
plt.plot([], [], color=E_color, label=f'{system.group} Energies') # Add to legend
|
|
132
|
+
|
|
133
|
+
if len(systems.get_groups(var)) > 1:
|
|
134
|
+
plt.subplots_adjust(right=0.85)
|
|
135
|
+
plt.legend(bbox_to_anchor=(1.1, 0.5), loc='center', fontsize='small')
|
|
136
|
+
|
|
137
|
+
plt.show()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def reduced_energies(
|
|
141
|
+
data:list,
|
|
142
|
+
title:str=None,
|
|
143
|
+
values:list=[],
|
|
144
|
+
legend:list=[],
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Plots the reduced energy of the system E/B vs the reduced potential energy V/B.
|
|
147
|
+
|
|
148
|
+
Takes a `data` list of System objects as input.
|
|
149
|
+
An optional `title` can be specified.
|
|
150
|
+
|
|
151
|
+
Optional maximum reduced potential `values` are plotted
|
|
152
|
+
as vertical lines (floats or ints) or regions
|
|
153
|
+
(lists inside the values list, from min to max).
|
|
154
|
+
A `legend` of the same len as `values` can be included.
|
|
155
|
+
These values are assumed to be divided by B by the user.
|
|
156
|
+
"""
|
|
157
|
+
if values and (isinstance(values, float) or isinstance(values, int) or isinstance(values, np.float64)):
|
|
158
|
+
values = [values]
|
|
159
|
+
if values and len(values) <= len(legend):
|
|
160
|
+
plot_legend = True
|
|
161
|
+
else:
|
|
162
|
+
plot_legend = False
|
|
163
|
+
legend = [''] * len(values)
|
|
164
|
+
systems.as_list(data)
|
|
165
|
+
title = title if title else (data[0].comment if data[0].comment else 'Reduced energies')
|
|
166
|
+
number_of_levels = data[0].searched_E
|
|
167
|
+
x = []
|
|
168
|
+
for system in data:
|
|
169
|
+
potential_max_B = system.potential_max / system.B
|
|
170
|
+
x.append(potential_max_B)
|
|
171
|
+
colors = plt.cm.viridis(np.linspace(0, 1, number_of_levels+1)) # +1 to avoid the lighter tones
|
|
172
|
+
for i in range(number_of_levels):
|
|
173
|
+
y = []
|
|
174
|
+
for system in data:
|
|
175
|
+
eigenvalues_B_i = system.eigenvalues[i] / system.B
|
|
176
|
+
y.append(eigenvalues_B_i)
|
|
177
|
+
plt.plot(x, y, marker='', linestyle='-', color=colors[i])
|
|
178
|
+
# Add vertical lines in the specified values
|
|
179
|
+
line_colors = plt.cm.tab10(np.linspace(0, 1, len(values)))
|
|
180
|
+
for i, value in enumerate(values):
|
|
181
|
+
if isinstance(value, list):
|
|
182
|
+
min_value = min(value)
|
|
183
|
+
max_value = max(value)
|
|
184
|
+
plt.axvspan(min_value, max_value, color=line_colors[i], alpha=0.2, linestyle='', label=legend[i])
|
|
185
|
+
else:
|
|
186
|
+
plt.axvline(x=value, color=line_colors[i], linestyle='--', label=legend[i], alpha=0.5)
|
|
187
|
+
plt.xlabel('V$_{B}$ / B')
|
|
188
|
+
plt.ylabel('E / B')
|
|
189
|
+
plt.title(title)
|
|
190
|
+
if plot_legend:
|
|
191
|
+
plt.legend()
|
|
192
|
+
plt.show()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def wavefunction(
|
|
196
|
+
system:System,
|
|
197
|
+
title:str=None,
|
|
198
|
+
square:bool=True,
|
|
199
|
+
levels=[0, 1, 2],
|
|
200
|
+
overlap=False,
|
|
201
|
+
yticks:bool=False,
|
|
202
|
+
) -> None:
|
|
203
|
+
"""Plot the wavefunction of a `system` for the specified `levels`.
|
|
204
|
+
|
|
205
|
+
Wavefunctions are squared by default, showing the probabilities;
|
|
206
|
+
To show the actual wavefunctions, set `square = False`.
|
|
207
|
+
|
|
208
|
+
`levels` can be a list of indexes, or the number of levels to plot.
|
|
209
|
+
|
|
210
|
+
Specific wavefunctions can be overlapped with `overlap` as a list with the target indexes.
|
|
211
|
+
The `overlap` value can also be the max number of wavefunctions to add.
|
|
212
|
+
All found wavefunctions can be added together with `overlap = True`;
|
|
213
|
+
but note that this overlap is limited by the number of System.searched_E,
|
|
214
|
+
that must be specified before solving the system.
|
|
215
|
+
Setting `overlap` will ignore the `levels` argument.
|
|
216
|
+
|
|
217
|
+
Set `yticks = True` to plot the wavefunction yticks.
|
|
218
|
+
"""
|
|
219
|
+
data = deepcopy(system)
|
|
220
|
+
eigenvectors = data.eigenvectors
|
|
221
|
+
title = title if title else (data.comment if data.comment else 'System wavefunction')
|
|
222
|
+
fig, ax1 = plt.subplots()
|
|
223
|
+
plt.title(title)
|
|
224
|
+
ax1.set_xlabel('Angle / radians')
|
|
225
|
+
ax1.set_ylabel('Potential / meV')
|
|
226
|
+
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$'])
|
|
227
|
+
ax1.plot(data.grid, data.potential_values, color='blue', linestyle='-')
|
|
228
|
+
ax2 = ax1.twinx()
|
|
229
|
+
if not yticks:
|
|
230
|
+
ax2.set_yticks([])
|
|
231
|
+
ax2.set_ylabel('Squared wavefunction' if square else 'Wavefunction')
|
|
232
|
+
# Set levels list
|
|
233
|
+
if isinstance(levels, int) or isinstance(levels, float):
|
|
234
|
+
levels = [x for x in range(int(levels))]
|
|
235
|
+
if not isinstance(levels, list):
|
|
236
|
+
raise ValueError('levels must be an int or a list of ints')
|
|
237
|
+
# Set overlap if requested
|
|
238
|
+
if overlap == True and isinstance(overlap, bool):
|
|
239
|
+
eigenvectors = [np.sum(eigenvectors, axis=0)]
|
|
240
|
+
levels = [0]
|
|
241
|
+
show_legend = False
|
|
242
|
+
elif overlap is not False and (isinstance(overlap, int) or isinstance(overlap, float)):
|
|
243
|
+
max_int = int(overlap)
|
|
244
|
+
eigenvectors = [np.sum(eigenvectors[:max_int], axis=0)]
|
|
245
|
+
levels = [0]
|
|
246
|
+
show_legend = False
|
|
247
|
+
elif isinstance(overlap, list):
|
|
248
|
+
eigenvectors = [np.sum([eigenvectors[i] for i in overlap], axis=0)]
|
|
249
|
+
levels = [0]
|
|
250
|
+
show_legend = False
|
|
251
|
+
else:
|
|
252
|
+
show_legend = True
|
|
253
|
+
# Square values if so
|
|
254
|
+
if square:
|
|
255
|
+
eigenvectors = [vec**2 for vec in eigenvectors]
|
|
256
|
+
# Plot the wavefunction
|
|
257
|
+
for i in levels:
|
|
258
|
+
ax2.plot(data.grid, eigenvectors[i], linestyle='--', label=f'{i}')
|
|
259
|
+
if show_legend:
|
|
260
|
+
fig.legend(loc='upper right', bbox_to_anchor=(0.9, 0.88), fontsize='small', title='Index')
|
|
261
|
+
plt.show()
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def splittings(
|
|
265
|
+
data:list,
|
|
266
|
+
title:str=None,
|
|
267
|
+
units:str='ueV'
|
|
268
|
+
) -> None:
|
|
269
|
+
"""Plot the tunnel splitting energies of a `data` list of systems.
|
|
270
|
+
|
|
271
|
+
The different `System.comment` are shown in the horizontal axis.
|
|
272
|
+
An optional `title` can be specified.
|
|
273
|
+
Default units shown are $\\mu$eV (`'ueV'`).
|
|
274
|
+
Available units are: `'ueV'`, `'meV'`, `'Ry'`.
|
|
275
|
+
"""
|
|
276
|
+
title = title if title != None else 'Tunnel splitting energies'
|
|
277
|
+
calcs = deepcopy(data)
|
|
278
|
+
calcs = systems.as_list(calcs)
|
|
279
|
+
|
|
280
|
+
fig, ax = plt.subplots()
|
|
281
|
+
ax.set_ylabel("Energy / meV")
|
|
282
|
+
|
|
283
|
+
y = [c.splittings[0] for c in calcs]
|
|
284
|
+
x = [c.comment for c in calcs]
|
|
285
|
+
# What units do we want?
|
|
286
|
+
if units.lower() in alias.units['ueV']:
|
|
287
|
+
y = [j * 1000 for j in y] # Convert meV to micro eV
|
|
288
|
+
ax.set_ylabel("Energy / $\\mu$eV")
|
|
289
|
+
elif units.lower() in alias.units['Ry']:
|
|
290
|
+
y = [j * constants.meV_to_Ry for j in y]
|
|
291
|
+
ax.set_ylabel("Energy / Ry")
|
|
292
|
+
#else: # It's okay let's use meV
|
|
293
|
+
|
|
294
|
+
ax.bar(range(len(y)), y)
|
|
295
|
+
for i, comment in enumerate(x):
|
|
296
|
+
ax.text(x=i, y=0, s=comment+' ', rotation=45, verticalalignment='top', horizontalalignment='right')
|
|
297
|
+
ax.set_xlabel("")
|
|
298
|
+
ax.set_title(title)
|
|
299
|
+
ax.set_xticks([])
|
|
300
|
+
fig.tight_layout()
|
|
301
|
+
plt.show()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def convergence(data:list) -> None:
|
|
305
|
+
"""Plot the energy convergence of a `data` list of Systems as a function of the gridsize."""
|
|
306
|
+
systems.as_list(data)
|
|
307
|
+
gridsizes = [system.gridsize for system in data]
|
|
308
|
+
runtimes = [system.runtime for system in data]
|
|
309
|
+
deviations = [] # List of lists, containing all eigenvalue deviations for every system
|
|
310
|
+
searched_E = data[0].searched_E
|
|
311
|
+
for system in data:
|
|
312
|
+
deviation_list = []
|
|
313
|
+
for i, eigenvalue in enumerate(system.eigenvalues):
|
|
314
|
+
ideal_E = systems.get_ideal_E(i)
|
|
315
|
+
deviation = abs(ideal_E - eigenvalue)
|
|
316
|
+
deviation_list.append(deviation)
|
|
317
|
+
deviation_list = deviation_list[1:] # Remove ground state
|
|
318
|
+
deviations.append(deviation_list)
|
|
319
|
+
# Plotting
|
|
320
|
+
fig, ax1 = plt.subplots()
|
|
321
|
+
ax1.set_xlabel('Grid size')
|
|
322
|
+
ax1.set_ylabel('Error / meV')
|
|
323
|
+
ax1.set_xscale('log')
|
|
324
|
+
ax1.set_yscale('log')
|
|
325
|
+
ax2 = ax1.twinx()
|
|
326
|
+
ax2.set_ylabel('Runtime / s')
|
|
327
|
+
ax2.set_yscale('log')
|
|
328
|
+
ax2.plot(gridsizes, runtimes, color='tab:grey', label='Runtime', linestyle='--')
|
|
329
|
+
colors = plt.cm.viridis(np.linspace(0, 1, searched_E)) # Should be searched_E-1 but we want to avoid lighter colors
|
|
330
|
+
for i in range(searched_E-1):
|
|
331
|
+
if i % 2 == 0: # Ignore even numbers, since those levels are degenerated.
|
|
332
|
+
continue
|
|
333
|
+
ax1.plot(gridsizes, [dev[i] for dev in deviations], label=f'$E_{{{int((i+1)/2)}}}$', color=colors[i])
|
|
334
|
+
fig.legend(loc='upper right', bbox_to_anchor=(0.9, 0.88), fontsize='small')
|
|
335
|
+
plt.title(data[0].comment if data[0].comment else 'Energy convergence vs grid size')
|
|
336
|
+
plt.show()
|
|
337
|
+
|