aton 0.2.4__tar.gz → 0.3.0__tar.gz

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.
Files changed (53) hide show
  1. {aton-0.2.4 → aton-0.3.0}/PKG-INFO +1 -1
  2. {aton-0.2.4 → aton-0.3.0}/aton/_version.py +1 -1
  3. {aton-0.2.4 → aton-0.3.0}/aton/qrotor/__init__.py +15 -7
  4. {aton-0.2.4 → aton-0.3.0}/aton/qrotor/plot.py +5 -5
  5. aton-0.3.0/aton/qrotor/solve.py +255 -0
  6. {aton-0.2.4 → aton-0.3.0}/aton/qrotor/system.py +23 -14
  7. {aton-0.2.4 → aton-0.3.0}/aton/qrotor/systems.py +98 -50
  8. {aton-0.2.4 → aton-0.3.0}/aton.egg-info/PKG-INFO +1 -1
  9. {aton-0.2.4 → aton-0.3.0}/tests/test_qrotor.py +3 -0
  10. aton-0.2.4/aton/qrotor/solve.py +0 -172
  11. {aton-0.2.4 → aton-0.3.0}/LICENSE +0 -0
  12. {aton-0.2.4 → aton-0.3.0}/README.md +0 -0
  13. {aton-0.2.4 → aton-0.3.0}/aton/__init__.py +0 -0
  14. {aton-0.2.4 → aton-0.3.0}/aton/interface/__init__.py +0 -0
  15. {aton-0.2.4 → aton-0.3.0}/aton/interface/castep.py +0 -0
  16. {aton-0.2.4 → aton-0.3.0}/aton/interface/phonopy.py +0 -0
  17. {aton-0.2.4 → aton-0.3.0}/aton/interface/qe.py +0 -0
  18. {aton-0.2.4 → aton-0.3.0}/aton/interface/slurm.py +0 -0
  19. {aton-0.2.4 → aton-0.3.0}/aton/phys/__init__.py +0 -0
  20. {aton-0.2.4 → aton-0.3.0}/aton/phys/atoms.py +0 -0
  21. {aton-0.2.4 → aton-0.3.0}/aton/phys/functions.py +0 -0
  22. {aton-0.2.4 → aton-0.3.0}/aton/phys/units.py +0 -0
  23. {aton-0.2.4 → aton-0.3.0}/aton/qrotor/constants.py +0 -0
  24. {aton-0.2.4 → aton-0.3.0}/aton/qrotor/potential.py +0 -0
  25. {aton-0.2.4 → aton-0.3.0}/aton/qrotor/rotate.py +0 -0
  26. {aton-0.2.4 → aton-0.3.0}/aton/spx/__init__.py +0 -0
  27. {aton-0.2.4 → aton-0.3.0}/aton/spx/classes.py +0 -0
  28. {aton-0.2.4 → aton-0.3.0}/aton/spx/deuterium.py +0 -0
  29. {aton-0.2.4 → aton-0.3.0}/aton/spx/fit.py +0 -0
  30. {aton-0.2.4 → aton-0.3.0}/aton/spx/normalize.py +0 -0
  31. {aton-0.2.4 → aton-0.3.0}/aton/spx/plot.py +0 -0
  32. {aton-0.2.4 → aton-0.3.0}/aton/spx/samples.py +0 -0
  33. {aton-0.2.4 → aton-0.3.0}/aton/st/__init__.py +0 -0
  34. {aton-0.2.4 → aton-0.3.0}/aton/st/alias.py +0 -0
  35. {aton-0.2.4 → aton-0.3.0}/aton/st/call.py +0 -0
  36. {aton-0.2.4 → aton-0.3.0}/aton/st/file.py +0 -0
  37. {aton-0.2.4 → aton-0.3.0}/aton/txt/__init__.py +0 -0
  38. {aton-0.2.4 → aton-0.3.0}/aton/txt/edit.py +0 -0
  39. {aton-0.2.4 → aton-0.3.0}/aton/txt/extract.py +0 -0
  40. {aton-0.2.4 → aton-0.3.0}/aton/txt/find.py +0 -0
  41. {aton-0.2.4 → aton-0.3.0}/aton.egg-info/SOURCES.txt +0 -0
  42. {aton-0.2.4 → aton-0.3.0}/aton.egg-info/dependency_links.txt +0 -0
  43. {aton-0.2.4 → aton-0.3.0}/aton.egg-info/requires.txt +0 -0
  44. {aton-0.2.4 → aton-0.3.0}/aton.egg-info/top_level.txt +0 -0
  45. {aton-0.2.4 → aton-0.3.0}/setup.cfg +0 -0
  46. {aton-0.2.4 → aton-0.3.0}/setup.py +0 -0
  47. {aton-0.2.4 → aton-0.3.0}/tests/__init__.py +0 -0
  48. {aton-0.2.4 → aton-0.3.0}/tests/test_edit.py +0 -0
  49. {aton-0.2.4 → aton-0.3.0}/tests/test_extract.py +0 -0
  50. {aton-0.2.4 → aton-0.3.0}/tests/test_file.py +0 -0
  51. {aton-0.2.4 → aton-0.3.0}/tests/test_find.py +0 -0
  52. {aton-0.2.4 → aton-0.3.0}/tests/test_qe.py +0 -0
  53. {aton-0.2.4 → aton-0.3.0}/tests/test_spx.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: aton
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: The Ab-iniTiO & Neutron research toolbox, or ATON, provides powerful and comprehensive tools for cutting-edge materials research, focused on (but not limited to) neutron science.
5
5
  Author: Pablo Gila-Herranz
6
6
  Author-email: pgila001@ikasle.ehu.eus
@@ -10,5 +10,5 @@ https://semver.org/
10
10
 
11
11
  """
12
12
 
13
- __version__ = 'v0.2.4'
13
+ __version__ = 'v0.3.0'
14
14
 
@@ -97,25 +97,33 @@ qr.plot.energies(system)
97
97
 
98
98
  ## Tunnel splittings and excitations
99
99
 
100
- By default, energy eigenvalues are assumed to present triplet degeneracy,
101
- see [A. J. Horsewill, Progress in Nuclear Magnetic Resonance Spectroscopy 35, 359–389 (1999)](https://doi.org/10.1016/S0079-6565(99)00016-3).
102
- If this is not the case, check `aton.qrotor.solve.excitations()`.
103
-
104
- When the quantum System is solved, tunnel splittings and excitations are also calculated:
100
+ When the quantum System is solved, tunnel splittings,
101
+ excitations and energy level degeneracy
102
+ below the potential maximum are also calculated:
105
103
 
106
104
  ```python
107
105
  system.solve()
108
106
  system.splittings
109
107
  system.excitations
108
+ system.deg
110
109
  ```
111
110
 
112
- To export the tunnel splittings of several calculations to a CSV file:
111
+ To export the energies and the tunnel splittings of several calculations to a CSV file:
113
112
 
114
113
  ```python
115
114
  calculations = [system1, system2, system3]
116
- qr.systems.splittings(calculations)
115
+ qr.systems.save_energies(calculations)
116
+ qr.systems.save_splittings(calculations)
117
117
  ```
118
118
 
119
+ Excitations are calculated using the mean for each energy level
120
+ with respect to the ground state.
121
+ Tunnel splittings for each level are calculated as the difference between A and E,
122
+ considering the mean of the eigenvalues for each sublevel.
123
+ See [R. M. Dimeo, American Journal of Physics 71, 885–893 (2003)](https://doi.org/10.1119/1.1538575)
124
+ and [A. J. Horsewill, Progress in Nuclear Magnetic Resonance Spectroscopy 35, 359–389 (1999)](https://doi.org/10.1016/S0079-6565(99)00016-3)
125
+ for further reference.
126
+
119
127
 
120
128
  Check the API documentation for more details.
121
129
 
@@ -151,7 +151,7 @@ def reduced_energies(
151
151
 
152
152
  systems.as_list(data)
153
153
  title = title if title else (data[0].comment if data[0].comment else 'Reduced energies')
154
- number_of_levels = data[0].E_levels
154
+ number_of_levels = data[0].searched_E
155
155
  x = []
156
156
  for system in data:
157
157
  potential_max_B = system.potential_max / system.B
@@ -198,7 +198,7 @@ def wavefunction(
198
198
  Specific wavefunctions can be overlapped with `overlap` as a list with the target indexes.
199
199
  The `overlap` value can also be the max number of wavefunctions to add.
200
200
  All found wavefunctions can be added together with `overlap = True`;
201
- but note that this overlap is limited by the number of System.E_levels,
201
+ but note that this overlap is limited by the number of System.searched_E,
202
202
  that must be specified before solving the system.
203
203
  Setting `overlap` will ignore the `levels` argument.
204
204
 
@@ -258,7 +258,7 @@ def convergence(data:list) -> None:
258
258
  gridsizes = [system.gridsize for system in data]
259
259
  runtimes = [system.runtime for system in data]
260
260
  deviations = [] # List of lists, containing all eigenvalue deviations for every system
261
- E_levels = data[0].E_levels
261
+ searched_E = data[0].searched_E
262
262
  for system in data:
263
263
  deviation_list = []
264
264
  for i, eigenvalue in enumerate(system.eigenvalues):
@@ -277,8 +277,8 @@ def convergence(data:list) -> None:
277
277
  ax2.set_ylabel('Runtime / s')
278
278
  ax2.set_yscale('log')
279
279
  ax2.plot(gridsizes, runtimes, color='tab:grey', label='Runtime', linestyle='--')
280
- colors = plt.cm.viridis(np.linspace(0, 1, E_levels)) # Should be E_levels-1 but we want to avoid lighter colors
281
- for i in range(E_levels-1):
280
+ colors = plt.cm.viridis(np.linspace(0, 1, searched_E)) # Should be searched_E-1 but we want to avoid lighter colors
281
+ for i in range(searched_E-1):
282
282
  if i % 2 == 0: # Ignore even numbers, since those levels are degenerated.
283
283
  continue
284
284
  ax1.plot(gridsizes, [dev[i] for dev in deviations], label=f'$E_{{{int((i+1)/2)}}}$', color=colors[i])
@@ -0,0 +1,255 @@
1
+ """
2
+ # Description
3
+
4
+ This module is used to solve any given quantum system.
5
+
6
+ Although the functions of this module can be used independently,
7
+ it is highly recommended to use the `System.solve()` method instead,
8
+ which does all the solving automatically (see `aton.qrotor.system.System.solve()`).
9
+ This documentation page is left for reference and for advanced users.
10
+
11
+
12
+ # Index
13
+
14
+ | | |
15
+ | --- | --- |
16
+ | `energies()` | Solve the quantum system, including eigenvalues and eigenvectors |
17
+ | `potential()` | Solve the potential values of the system |
18
+ | `schrodinger()` | Solve the Schrödiger equation for the system |
19
+ | `hamiltonian_matrix()` | Calculate the hamiltonian matrix of the system |
20
+ | `laplacian_matrix()` | Calculate the second derivative matrix for a given grid |
21
+ | `excitations()` | Get excitation levels and tunnel splitting energies |
22
+ | `E_levels` | Group a list of degenerated eigenvalues by energy levels |
23
+
24
+ ---
25
+ """
26
+
27
+
28
+ from .system import System
29
+ from .potential import solve as solve_potential
30
+ from .potential import interpolate
31
+ import time
32
+ import numpy as np
33
+ from scipy import sparse
34
+ import aton
35
+ from aton._version import __version__
36
+
37
+
38
+ def energies(system:System, filename:str=None) -> System:
39
+ """Solves the quantum `system`.
40
+
41
+ This includes solving the potential, the eigenvalues and the eigenvectors.
42
+
43
+ The resulting System object is saved with pickle to `filename` if specified.
44
+ """
45
+ if not any(system.grid):
46
+ system.set_grid()
47
+ system = potential(system)
48
+ system = schrodinger(system)
49
+ if filename:
50
+ aton.st.file.save(system, filename)
51
+ return system
52
+
53
+
54
+ def potential(system:System) -> System:
55
+ """Solves the potential values of the `system`.
56
+
57
+ It interpolates the potential if `system.gridsize` is larger than the current grid.
58
+ It solves the potential according to the potential name,
59
+ by calling `aton.qrotor.potential.solve()`.
60
+ Then it applies extra operations, such as removing the potential offset
61
+ if `system.correct_potential_offset = True`.
62
+ """
63
+ if system.gridsize and any(system.grid):
64
+ if system.gridsize > len(system.grid):
65
+ system = interpolate(system)
66
+ V = solve_potential(system)
67
+ if system.correct_potential_offset is True:
68
+ offset = min(V)
69
+ V = V - offset
70
+ system.potential_offset = offset
71
+ system.potential_values = V
72
+ return system
73
+
74
+
75
+ def schrodinger(system:System) -> System:
76
+ """Solves the Schrödinger equation for a given `system`.
77
+
78
+ Uses ARPACK in shift-inverse mode to solve the hamiltonian sparse matrix.
79
+ """
80
+ time_start = time.time()
81
+ V = system.potential_values
82
+ H = hamiltonian_matrix(system)
83
+ print('Solving Schrodinger equation...')
84
+ # Solve eigenvalues with ARPACK in shift-inverse mode, with a sparse matrix
85
+ eigenvalues, eigenvectors = sparse.linalg.eigsh(H, system.searched_E, which='LM', sigma=0, maxiter=10000)
86
+ if any(eigenvalues) is None:
87
+ print('WARNING: Not all eigenvalues were found.\n')
88
+ else: print('Done.')
89
+ system.version = __version__
90
+ system.runtime = time.time() - time_start
91
+ system.eigenvalues = eigenvalues
92
+ system.potential_max = max(V)
93
+ system.potential_min = min(V)
94
+ system.energy_barrier = max(V) - min(eigenvalues)
95
+ # Solve excitations and tunnel splittings, assuming triplet degeneracy
96
+ system = excitations(system)
97
+ # Do we really need to save eigenvectors?
98
+ if system.save_eigenvectors == True:
99
+ system.eigenvectors = np.transpose(eigenvectors)
100
+ return system
101
+
102
+
103
+ def hamiltonian_matrix(system:System):
104
+ """Calculates the Hamiltonian sparse matrix for a given `system`."""
105
+ print(f'Creating Hamiltonian matrix of size {system.gridsize}...')
106
+ V = system.potential_values.tolist()
107
+ potential = sparse.diags(V, format='lil')
108
+ B = system.B
109
+ x = system.grid
110
+ H = -B * laplacian_matrix(x) + potential
111
+ return H
112
+
113
+
114
+ def laplacian_matrix(grid):
115
+ """Calculates the Laplacian (second derivative) matrix for a given `grid`."""
116
+ x = grid
117
+ diagonals = [-2*np.ones(len(x)), np.ones(len(x)), np.ones(len(x))]
118
+ laplacian_matrix = sparse.spdiags(diagonals, [0, -1, 1], format='lil')
119
+ # Periodic boundary conditions
120
+ laplacian_matrix[0, -1] = 1
121
+ laplacian_matrix[-1, 0] = 1
122
+ dx = x[1] - x[0]
123
+ laplacian_matrix /= dx**2
124
+ return laplacian_matrix
125
+
126
+
127
+ def excitations(system: System) -> System:
128
+ """Calculate the excitation levels and the tunnel splitting energies of a system.
129
+
130
+ Automatically detects degenerated energy levels by looking at significant jumps
131
+ between consecutive eigenvalues. Within each level, finds two subgroups
132
+ to calculate tunnel splittings. Stops when energies reach the maximum potential.
133
+
134
+ Excitations are calculated as the energy difference between the mean energy of the
135
+ ground state level and the mean energy of each excited level.
136
+
137
+ Tunnel splittings are calculated as the difference between the medians of
138
+ the two subgroups within each degenerate level.
139
+ """
140
+ # Get eigenvalues, stop before any possible None value
141
+ eigenvalues = system.eigenvalues
142
+ if not isinstance(eigenvalues, (list, np.ndarray)) or len(eigenvalues) == 0:
143
+ return system
144
+ if None in eigenvalues:
145
+ none_index = eigenvalues.tolist().index(None)
146
+ eigenvalues = eigenvalues[:none_index]
147
+ if len(eigenvalues) < 3:
148
+ return system
149
+ # Group degenerated eigenvalues into energy levels
150
+ levels, degeneracy = E_levels(eigenvalues, system.potential_max)
151
+ system.E_levels = levels
152
+ system.deg = degeneracy
153
+ # Calculate excitations and splittings
154
+ ground_energy = np.mean(levels[0]) # Mean of ground state level
155
+ excitations = []
156
+ tunnel_splittings = []
157
+ for level in levels:
158
+ level_mean = np.mean(level)
159
+ excitations.append(level_mean - ground_energy)
160
+ # Get the tunnel splitting within the level
161
+ if len(level) > 1:
162
+ # Find the largest gap within the level to split into two subgroups
163
+ internal_gaps = np.diff(level)
164
+ split_idx = np.argmax(internal_gaps) + 1
165
+ # Split into two subgroups
166
+ subgroup1 = level[:split_idx]
167
+ subgroup2 = level[split_idx:]
168
+ # Medians of subgroups
169
+ median1 = np.median(subgroup1)
170
+ median2 = np.median(subgroup2)
171
+ # Tunnel splitting is the difference between medians
172
+ tunnel_splittings.append(abs(median2 - median1))
173
+ else:
174
+ tunnel_splittings.append(0)
175
+ system.excitations = excitations[1:] # Exclude ground state
176
+ system.splittings = tunnel_splittings
177
+ return system
178
+
179
+
180
+ def E_levels(eigenvalues, vmax:float=None) -> list:
181
+ """Group a list of degenerated eigenvalues by energy levels.
182
+
183
+ Automatically detects degenerated energy levels by
184
+ looking at significant jumps between consecutive eigenvalues.
185
+
186
+ An optional `vmax` can be specified,
187
+ to avoid including too many eigenvalues
188
+ above a certain potential maximum.
189
+ Only two more eigenvalues are considered after `vmax`,
190
+ to properly detect energy levels around the maximum.
191
+
192
+ Example:
193
+ ```python
194
+ levels, deg = qr.solve.E_levels(array([1.1, 1.2, 1.3, 5.4, 5.5, 5.6]))
195
+ levels # [array([1.1, 1.2, 1.3]), array([5.4, 5.5, 5.6])]
196
+ deg # 3
197
+ ```
198
+ """
199
+ if vmax: # Include all eigenvalues below Vmax plus 3 more eigenvalues
200
+ index_first_above_Vmax = np.where(eigenvalues > vmax)[0][0]
201
+ eigenvalues = eigenvalues[:(index_first_above_Vmax + 2)]
202
+ # Group degenerated eigenvalues into energy levels
203
+ for scale in np.arange(2, 4, 0.25): # First search going to bigger scales
204
+ levels, degeneracy = _get_E_levels_by_gap(eigenvalues, scale)
205
+ if (degeneracy > 1) and (degeneracy % 1 == 0):
206
+ break
207
+ else:
208
+ levels, degeneracy = None, None
209
+ if not degeneracy: # If it didn't work, search with tighter values
210
+ for scale in np.arange(0.75, 2, 0.25):
211
+ levels, degeneracy = _get_E_levels_by_gap(eigenvalues, scale)
212
+ if (degeneracy > 1) and (degeneracy % 1 == 0):
213
+ break
214
+ if not (degeneracy > 1) and not (degeneracy % 1) == 0:
215
+ return levels, degeneracy # I give up
216
+ # Correct the last two levels
217
+ if len(levels) >= 2 and len(levels[-2]) != degeneracy:
218
+ levels[-2] = np.concatenate((levels[-2], levels[-1]))
219
+ levels.pop(-1)
220
+ # Split last level into groups of size = degeneracy
221
+ last_level = levels[-1]
222
+ additional_levels = len(last_level) // degeneracy
223
+ if additional_levels > 0:
224
+ # Replace last level with list of complete degeneracy groups
225
+ complete_groups = [last_level[i:i+degeneracy] for i in range(0, additional_levels*degeneracy, degeneracy)]
226
+ levels.pop(-1) # Remove original last level
227
+ levels.extend(complete_groups) # Add all complete groups
228
+ else:
229
+ levels.pop(-1) # Remove incomplete last level
230
+ return levels, degeneracy
231
+
232
+
233
+ def _get_E_levels_by_gap(eigenvalues, scale:float=2) -> tuple:
234
+ """Split a list of eigenvalues into energy levels by looking at gaps.
235
+
236
+ If the gap is bigger than the average gap times `scale`, it is considered a new level.
237
+
238
+ Returns a tuple with the estimated levels and the average degeneracy.
239
+ The last two levels are not taken into account to estimate the degeneracy.
240
+ """
241
+ # Find gaps between consecutive eigenvalues
242
+ gaps = np.diff(eigenvalues)
243
+ # Use mean gap times scale as threshold to distinguish energy levels
244
+ med_gap = np.mean(gaps)
245
+ level_breaks = np.where(gaps > scale * med_gap)[0] + 1
246
+ levels = np.split(eigenvalues, level_breaks)
247
+ # Calculate average degeneracy excluding last two levels if possible
248
+ if len(levels) > 2:
249
+ avg_degeneracy = float(np.mean([len(level) for level in levels[:-2]]))
250
+ else:
251
+ avg_degeneracy = float(len(levels[0]))
252
+ if avg_degeneracy % 1 == 0:
253
+ avg_degeneracy = int(avg_degeneracy)
254
+ return levels, avg_degeneracy
255
+
@@ -25,7 +25,7 @@ class System:
25
25
  self,
26
26
  comment: str = None,
27
27
  group: str = 'CH3',
28
- E_levels: int = 15,
28
+ searched_E: int = 21,
29
29
  correct_potential_offset: bool = True,
30
30
  save_eigenvectors: bool = True,
31
31
  gridsize: int = 200000,
@@ -43,8 +43,8 @@ class System:
43
43
  self.group: str = group
44
44
  """Chemical group, methyl or amine: `'CH3'`, `'CD3'`, `'NH3'`, `'ND3'`."""
45
45
  self.set_group(group) # Normalise the group name, and set the value of B
46
- self.E_levels: int = E_levels
47
- """Number of energy levels to be studied."""
46
+ self.searched_E: int = searched_E
47
+ """Number of energy eigenvalues to be searched."""
48
48
  self.correct_potential_offset: bool = correct_potential_offset
49
49
  """Correct the potential offset as `V - min(V)` or not."""
50
50
  self.save_eigenvectors: bool = save_eigenvectors
@@ -82,19 +82,28 @@ class System:
82
82
  self.potential_max: float = None
83
83
  """`max(V)`"""
84
84
  # Energies
85
- self.eigenvalues = []
86
- """Calculated eigenvalues of the system. Should be in meV."""
87
85
  self.eigenvectors = []
88
86
  """Eigenvectors, if `save_eigenvectors` is True. Beware of the file size."""
87
+ self.eigenvalues = []
88
+ """Calculated eigenvalues of the system. In meV."""
89
+ self.E_levels: list = []
90
+ """List of `eigenvalues` grouped by energy levels, found below `potential_max`."""
91
+ self.deg: float = None
92
+ """Estimated degeneracy of the `E_levels` found below `potential_max`."""
93
+ self.excitations: list = []
94
+ """Torsional excitations, as the difference between each energy level with respect to the ground state.
95
+
96
+ Considers the means between degenerated eigenvalues for all energy levels below `potential_max`.
97
+ """
98
+ self.splittings: list = []
99
+ """Tunnel splitting energies, for every degenerated energy level.
100
+
101
+ Calculated for all `E_levels` as the difference between
102
+ the mean of the eigenvalues from A and the mean of the eigenvalues from E,
103
+ see [R. M. Dimeo, American Journal of Physics 71, 885–893 (2003)](https://doi.org/10.1119/1.1538575).
104
+ """
89
105
  self.energy_barrier: float = None
90
106
  """Activation energy or energy barrier, from the ground torsional state to the top of the potential barrier, `max(V) - min(eigenvalues)`"""
91
- self.excitations: list = None
92
- """Torsional excitations, as eigenvalues with respect to the ground state.
93
-
94
- Considers the lowest eigenvalue from each degenerated energy level.
95
- """
96
- self.splittings: list = None
97
- """Tunnel splitting energies, for every degenerated energy level."""
98
107
  self.runtime: float = None
99
108
  """Time taken to solve the eigenvalues."""
100
109
 
@@ -113,8 +122,8 @@ class System:
113
122
  def change_phase(self, phase:float, calculate:bool=True):
114
123
  """Apply a phase shift to the grid and potential values.
115
124
 
116
- The `phase` should be a multiple of $\pi$ (e.g., 3/2 for $3\pi/2$).
117
- The resulting grid will be expressed between $-2\pi$ and $2\pi$.
125
+ The `phase` should be a multiple of $\\pi$ (e.g., 3/2 for $3\\pi/2$).
126
+ The resulting grid will be expressed between $-2\\pi$ and $2\\pi$.
118
127
 
119
128
  The System is solved immediately after the phase change.
120
129
  This last step ensures that all eigenvalues and wavefunctions are correct.
@@ -2,6 +2,7 @@
2
2
  # Description
3
3
 
4
4
  This module contains utility functions to handle multiple `aton.qrotor.system` calculations.
5
+ These are commonly used as a list of `System` objects.
5
6
 
6
7
 
7
8
  # Index
@@ -9,14 +10,15 @@ This module contains utility functions to handle multiple `aton.qrotor.system` c
9
10
  | | |
10
11
  | --- | --- |
11
12
  | `as_list()` | Ensures that a list only contains System objects |
13
+ | `save_energies()` | Save the energy eigenvalues for all systems to a CSV |
14
+ | `save_splittings()` | Save the tunnel splitting energies for all systems to a CSV |
12
15
  | `get_energies()` | Get the eigenvalues from all systems |
13
16
  | `get_gridsizes()` | Get all gridsizes |
14
17
  | `get_runtimes()` | Get all runtimes |
15
18
  | `get_groups()` | Get the chemical groups in use |
19
+ | `get_ideal_E()` | Calculate the ideal energy for a specified level |
16
20
  | `sort_by_gridsize()` | Sort systems by gridsize |
17
21
  | `reduce_size()` | Discard data that takes too much space |
18
- | `get_ideal_E()` | Calculate the ideal energy for a specified level |
19
- | `splittings()` | Get the first tunnel splitting energies for all systems |
20
22
 
21
23
  ---
22
24
  """
@@ -45,8 +47,88 @@ def as_list(systems) -> None:
45
47
  return systems
46
48
 
47
49
 
50
+ def save_energies(
51
+ systems:list,
52
+ comment:str='',
53
+ filepath:str='eigenvalues.csv',
54
+ ) -> pd.DataFrame:
55
+ """Save the energy eigenvalues for all `systems` to a eigenvalues.csv file.
56
+
57
+ Returns a Pandas Dataset with `System.comment` columns and `System.eigenvalues` values.
58
+
59
+ The output file can be changed with `filepath`,
60
+ or set to null to avoid saving the dataset.
61
+ A `comment` can be included at the top of the file.
62
+ Note that `System.comment` must not include commas (`,`).
63
+ """
64
+ as_list(systems)
65
+ version = systems[0].version
66
+ E = {}
67
+ # Find max length of eigenvalues
68
+ max_len = max((len(s.eigenvalues) if s.eigenvalues is not None else 0) for s in systems)
69
+ for s in systems:
70
+ if s.eigenvalues is not None:
71
+ # Filter out None values and replace with NaN
72
+ valid_eigenvalues = [float('nan') if e is None else e for e in s.eigenvalues]
73
+ padded_eigenvalues = valid_eigenvalues + [float('nan')] * (max_len - len(s.eigenvalues))
74
+ else:
75
+ padded_eigenvalues = [float('nan')] * max_len
76
+ E[s.comment] = padded_eigenvalues
77
+ df = pd.DataFrame(E)
78
+ if not filepath:
79
+ return df
80
+ # Else save to file
81
+ df.to_csv(filepath, sep=',', index=False)
82
+ # Include a comment at the top of the file
83
+ file_comment = f'# {comment}\n' if comment else f''
84
+ file_comment += f'# Energy eigenvalues\n'
85
+ file_comment += f'# Calculated with ATON {version}\n'
86
+ file_comment += f'# https://pablogila.github.io/ATON\n#'
87
+ txt.edit.insert_at(filepath, file_comment, 0)
88
+ print(f'Energy eigenvalues saved to {filepath}')
89
+ return df
90
+
91
+
92
+ def save_splittings(
93
+ systems:list,
94
+ comment:str='',
95
+ filepath:str='tunnel_splittings.csv',
96
+ ) -> pd.DataFrame:
97
+ """Save the tunnel splitting energies for all `systems` to a tunnel_splittings.csv file.
98
+
99
+ Returns a Pandas Dataset with `System.comment` columns and `System.splittings` values.
100
+
101
+ The output file can be changed with `filepath`,
102
+ or set to null to avoid saving the dataset.
103
+ A `comment` can be included at the top of the file.
104
+ Note that `System.comment` must not include commas (`,`).
105
+ Different splitting lengths across systems are allowed - missing values will be NaN.
106
+ """
107
+ as_list(systems)
108
+ version = systems[0].version
109
+ tunnelling_E = {}
110
+ # Find max length of splittings
111
+ max_len = max(len(s.splittings) for s in systems)
112
+ for s in systems: # Pad shorter splittings with NaN
113
+ padded_splittings = s.splittings + [float('nan')] * (max_len - len(s.splittings))
114
+ tunnelling_E[s.comment] = padded_splittings
115
+ df = pd.DataFrame(tunnelling_E)
116
+ if not filepath:
117
+ return df
118
+ # Else save to file
119
+ df.to_csv(filepath, sep=',', index=False)
120
+ # Include a comment at the top of the file
121
+ file_comment = f'# {comment}\n' if comment else f''
122
+ file_comment += f'# Tunnel splitting energies\n'
123
+ file_comment += f'# Calculated with ATON {version}\n'
124
+ file_comment += f'# https://pablogila.github.io/ATON\n#'
125
+ txt.edit.insert_at(filepath, file_comment, 0)
126
+ print(f'Tunnel splitting energies saved to {filepath}')
127
+ return df
128
+
129
+
48
130
  def get_energies(systems:list) -> list:
49
- """Get a list with all eigenvalues from all systems.
131
+ """Get a list with all lists of eigenvalues from all systems.
50
132
 
51
133
  If no eigenvalues are present for a particular system, appends None.
52
134
  """
@@ -100,25 +182,6 @@ def get_groups(systems:list) -> list:
100
182
  return groups
101
183
 
102
184
 
103
- def sort_by_gridsize(systems:list) -> list:
104
- """Sorts a list of System objects by `System.gridsize`."""
105
- as_list(systems)
106
- systems = sorted(systems, key=lambda sys: sys.gridsize)
107
- return systems
108
-
109
-
110
- def reduce_size(systems:list) -> list:
111
- """Discard data that takes too much space.
112
-
113
- Removes eigenvectors, potential values and grids,
114
- for all System values inside the `systems` list.
115
- """
116
- as_list(systems)
117
- for dataset in systems:
118
- dataset = dataset.reduce_size()
119
- return systems
120
-
121
-
122
185
  def get_ideal_E(E_level:int) -> int:
123
186
  """Calculates the ideal energy for a specified `E_level`.
124
187
 
@@ -133,36 +196,21 @@ def get_ideal_E(E_level:int) -> int:
133
196
  return ideal_E
134
197
 
135
198
 
136
- def splittings(
137
- systems:list,
138
- comment:str='',
139
- filepath:str='tunnel_splittings.csv',
140
- ) -> pd.DataFrame:
141
- """Save the tunnel splitting energies for all `systems` to a tunnel_splittings.csv file.
199
+ def sort_by_gridsize(systems:list) -> list:
200
+ """Sorts a list of System objects by `System.gridsize`."""
201
+ as_list(systems)
202
+ systems = sorted(systems, key=lambda sys: sys.gridsize)
203
+ return systems
142
204
 
143
- Returns a Pandas Dataset with `System.comment` columns and `System.splittings` values.
144
205
 
145
- The output file can be changed with `filepath`,
146
- or set to null to avoid saving the dataset.
147
- A `comment` can be included at the top of the file.
148
- Note that `System.comment` must not include commas (`,`).
206
+ def reduce_size(systems:list) -> list:
207
+ """Discard data that takes too much space.
208
+
209
+ Removes eigenvectors, potential values and grids,
210
+ for all System values inside the `systems` list.
149
211
  """
150
212
  as_list(systems)
151
- version = systems[0].version
152
- tunnelling_E = {}
153
- for s in systems:
154
- tunnelling_E[s.comment] = s.splittings
155
- df = pd.DataFrame(tunnelling_E)
156
- if not filepath:
157
- return df
158
- # Else save to file
159
- df.to_csv(filepath, sep=',', index=False)
160
- # Include a comment at the top of the file
161
- file_comment = f'# {comment}\n' if comment else f''
162
- file_comment += f'# Tunnel splitting energies\n'
163
- file_comment += f'# Calculated with ATON {version}\n'
164
- file_comment += f'# https://pablogila.github.io/ATON\n#'
165
- txt.edit.insert_at(filepath, file_comment, 0)
166
- print(f'Tunnel splitting energies saved to {filepath}')
167
- return df
213
+ for dataset in systems:
214
+ dataset = dataset.reduce_size()
215
+ return systems
168
216
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: aton
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: The Ab-iniTiO & Neutron research toolbox, or ATON, provides powerful and comprehensive tools for cutting-edge materials research, focused on (but not limited to) neutron science.
5
5
  Author: Pablo Gila-Herranz
6
6
  Author-email: pgila001@ikasle.ehu.eus
@@ -3,6 +3,7 @@ import aton.interface as interface
3
3
  import aton.txt.extract as extract
4
4
  import aton.st.file as file
5
5
  import numpy as np
6
+ import warnings
6
7
 
7
8
 
8
9
  folder = 'tests/samples/'
@@ -87,4 +88,6 @@ def test_phase():
87
88
  sys.change_phase(-1)
88
89
  assert round(sys.grid[0], 2) == round(-np.pi/2, 2)
89
90
  assert round(sys.potential_values[0], 2) == 0.5
91
+ # Were eigenvalues calculated?
92
+ assert len(sys.eigenvalues) > 0
90
93
 
@@ -1,172 +0,0 @@
1
- """
2
- # Description
3
-
4
- This module is used to solve any given quantum system.
5
-
6
- Although the functions of this module can be used independently,
7
- it is highly recommended to use the `System.solve()` method instead,
8
- which does all the solving automatically (see `aton.qrotor.system.System.solve()`).
9
- However, advanced users might want to use some of these functions independently;
10
- for example, if your system energy levels are not degenerated in triplets,
11
- you might want to use `excitations()` to solve the energy excitations and tunnel splittings with the proper degeneracy.
12
-
13
-
14
- # Index
15
-
16
- | | |
17
- | --- | --- |
18
- | `energies()` | Solve the quantum system, including eigenvalues and eigenvectors |
19
- | `potential()` | Solve the potential values of the system |
20
- | `schrodinger()` | Solve the Schrödiger equation for the system |
21
- | `hamiltonian_matrix()` | Calculate the hamiltonian matrix of the system |
22
- | `laplacian_matrix()` | Calculate the second derivative matrix for a given grid |
23
- | `excitations()` | Get excitation levels and tunnel splitting energies for a set of eigenvalues |
24
-
25
- ---
26
- """
27
-
28
-
29
- from .system import System
30
- from .potential import solve as solve_potential
31
- from .potential import interpolate
32
- import time
33
- import numpy as np
34
- from scipy import sparse
35
- import aton
36
- from aton._version import __version__
37
-
38
-
39
- def energies(system:System, filename:str=None) -> System:
40
- """Solves the quantum `system`.
41
-
42
- This includes solving the potential, the eigenvalues and the eigenvectors.
43
-
44
- The resulting System object is saved with pickle to `filename` if specified.
45
- """
46
- if not any(system.grid):
47
- system.set_grid()
48
- system = potential(system)
49
- system = schrodinger(system)
50
- if filename:
51
- aton.st.file.save(system, filename)
52
- return system
53
-
54
-
55
- def potential(system:System) -> System:
56
- """Solves the potential values of the `system`.
57
-
58
- It interpolates the potential if `system.gridsize` is larger than the current grid.
59
- It solves the potential according to the potential name,
60
- by calling `aton.qrotor.potential.solve()`.
61
- Then it applies extra operations, such as removing the potential offset
62
- if `system.correct_potential_offset = True`.
63
- """
64
- if system.gridsize and any(system.grid):
65
- if system.gridsize > len(system.grid):
66
- system = interpolate(system)
67
- V = solve_potential(system)
68
- if system.correct_potential_offset is True:
69
- offset = min(V)
70
- V = V - offset
71
- system.potential_offset = offset
72
- system.potential_values = V
73
- return system
74
-
75
-
76
- def schrodinger(system:System) -> System:
77
- """Solves the Schrödinger equation for a given `system`.
78
-
79
- Uses ARPACK in shift-inverse mode to solve the hamiltonian sparse matrix.
80
- """
81
- time_start = time.time()
82
- V = system.potential_values
83
- H = hamiltonian_matrix(system)
84
- print('Solving Schrodinger equation...')
85
- # Solve eigenvalues with ARPACK in shift-inverse mode, with a sparse matrix
86
- eigenvalues, eigenvectors = sparse.linalg.eigsh(H, system.E_levels, which='LM', sigma=0, maxiter=10000)
87
- if any(eigenvalues) is None:
88
- print('WARNING: Not all eigenvalues were found.\n')
89
- else: print('Done.')
90
- system.version = __version__
91
- system.runtime = time.time() - time_start
92
- system.eigenvalues = eigenvalues
93
- system.potential_max = max(V)
94
- system.potential_min = min(V)
95
- system.energy_barrier = max(V) - min(eigenvalues)
96
- # Solve excitations and tunnel splittings, assuming triplet degeneracy
97
- system = excitations(system, deg=3)
98
- # Do we really need to save eigenvectors?
99
- if system.save_eigenvectors == True:
100
- system.eigenvectors = np.transpose(eigenvectors)
101
- return system
102
-
103
-
104
- def hamiltonian_matrix(system:System):
105
- """Calculates the Hamiltonian sparse matrix for a given `system`."""
106
- print(f'Creating Hamiltonian matrix of size {system.gridsize}...')
107
- V = system.potential_values.tolist()
108
- potential = sparse.diags(V, format='lil')
109
- B = system.B
110
- x = system.grid
111
- H = -B * laplacian_matrix(x) + potential
112
- return H
113
-
114
-
115
- def laplacian_matrix(grid):
116
- """Calculates the Laplacian (second derivative) matrix for a given `grid`."""
117
- x = grid
118
- diagonals = [-2*np.ones(len(x)), np.ones(len(x)), np.ones(len(x))]
119
- laplacian_matrix = sparse.spdiags(diagonals, [0, -1, 1], format='lil')
120
- # Periodic boundary conditions
121
- laplacian_matrix[0, -1] = 1
122
- laplacian_matrix[-1, 0] = 1
123
- dx = x[1] - x[0]
124
- laplacian_matrix /= dx**2
125
- return laplacian_matrix
126
-
127
-
128
- def excitations(
129
- system:System,
130
- deg:int=3,
131
- ) -> tuple:
132
- """Calculate the excitation levels and the tunnel splitting energies of a system.
133
-
134
- Stops the calculation when energies reach the maximum potential.
135
- Assumes that eigenvalues are degenerated in triplets;
136
- this degeneracy can be specified with `deg`.
137
- """
138
- eigenvalues = system.eigenvalues
139
- V_max = system.potential_max
140
- ground_energy = min(eigenvalues)
141
- excitations = []
142
- tunnel_splittings = []
143
- i = 0
144
- while (i + deg-1) <= len(eigenvalues):
145
- # Get the eigenvalues corresponding to this triplet (or whatever degeneracy)
146
- i_max = i + deg # Index indicating the end of this triplet
147
- triplet = eigenvalues[i:i_max]
148
- # Check that we are still below the potential max
149
- if any(triplet) > V_max:
150
- break
151
- # Check that all eigenvalues are valid, and not None
152
- if any(triplet) is None:
153
- break
154
- # Get the excitation energy, by comparing with the ground state
155
- if i != 0: # Skip the ground energy level
156
- excitations.append(min(triplet) - ground_energy)
157
- # Check the energy differences inside each triplet (or whatever degeneracy)
158
- E_diff = []
159
- for j in range(deg-1): # 0, 1 (not 2)
160
- E_0 = triplet[j]
161
- E_1 = triplet[j+1]
162
- diff = E_1 - E_0
163
- E_diff.append(diff)
164
- # Get the maximum energy difference inside the triplet, which is the tunnel splitting
165
- tunnel_splittings.append(max(E_diff))
166
- # Move to the next triplet
167
- i += deg
168
- # Set the energy excitations and tunnel splittings
169
- system.excitations = excitations
170
- system.splittings = tunnel_splittings
171
- return system
172
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes