teacups 2.3.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.
teacups/__init__.py ADDED
File without changes
teacups/classes.py ADDED
@@ -0,0 +1,42 @@
1
+ import numpy as np
2
+
3
+
4
+ class Sys:
5
+ def __init__(self):
6
+ self.spin_system = "doub"
7
+ self.precursor = "eigen"
8
+ self.population = [1, 0]
9
+
10
+ self.g = [1.95, 2, 2.1]
11
+
12
+ self.width_gauss = 3
13
+ self.sigma_time = 0
14
+ self.decay = 1e-6
15
+
16
+ self.dynamics = None
17
+ self.T_relax_1 = 1e-6
18
+ self.T_relax_2 = 1e-6
19
+
20
+
21
+ class Exp:
22
+ def __init__(self):
23
+ self.B_z = np.linspace(320, 380, 600)
24
+ self.t_scale = [0, 2e-6]
25
+ self.t_points = 60
26
+ self.B_mw = 0.001
27
+ self.freq_mw = 9.75e9
28
+
29
+
30
+ class SimOpt:
31
+ def __init__(self):
32
+ self.grid_points = 15
33
+ self.grid = "fibonacci"
34
+ self.theta = [np.pi]
35
+ self.phi = [0]
36
+ self.sym = "D2h"
37
+ self.space = "hilbert"
38
+ self.pop_evolution = False
39
+ self.eigval_mode = False
40
+ self.cpu_cores = 1
41
+ self.CUPY = False
42
+ self.extend_t = False
teacups/convolution.py ADDED
@@ -0,0 +1,87 @@
1
+ import numpy as np
2
+ import scipy.ndimage as snd
3
+
4
+
5
+ def voigt_convolution(
6
+ sigma_time: float, width: float, spectrum: "np.ndarray", extend_t=False
7
+ ) -> "np.ndarray":
8
+ """
9
+ Calculate the Voigt profile of a Lorentzian function by convolution of the
10
+ existing Lorentzian function and a Gaussian distribution with a given FWHH.
11
+ A 2D-spectrum is convolved with the Gaussian function along its second
12
+ axis, which should be the B-field-axis. Additional the spectrum is convolved
13
+ with a second Gaussian distribution along the first axis (time-axis) for
14
+ simulating the time resoltuion of the signal.
15
+
16
+ Parameters
17
+ ----------
18
+ width_time : float
19
+ Standardderivation of the Gauß-Filter, Time resolution of the signal.
20
+ In pixels.
21
+ width : float
22
+ Gaussian line width, standard deviation. In pixels.
23
+ spectrum : np.ndarray
24
+ 2D-Array containing values of the ordinate of a spectrum.
25
+ In case of TREPR spectra this would be the intensity for each time and
26
+ magnetic field point. The convolution is carried out along the
27
+ second axis.
28
+ extend_t : bool
29
+ If true the time axis is extended by a zero-baseline with
30
+ 5*time_sigma pixels. Default is False.
31
+
32
+ Returns
33
+ -------
34
+ spectrum_conv : np.ndarray
35
+ Array containing convoluted values of the ordinate. In case of TREPR
36
+ spectra this would be the covoluted intensities of the spectrum for
37
+ each magnetic field and time point. Spectrum_conv is computed using the
38
+ convolution alogrithm of SciPy.
39
+
40
+ """
41
+ pad_t = int(5 * sigma_time)
42
+ spec_pad = np.pad(
43
+ spectrum, ((pad_t, 0), (0, 0)), mode="constant", constant_values=0
44
+ )
45
+ spec_conv_pad = snd.gaussian_filter(spec_pad, [sigma_time, width])
46
+
47
+ if extend_t:
48
+ spectrum_conv = spec_conv_pad
49
+ else:
50
+ spectrum_conv = spec_conv_pad[pad_t:, :]
51
+
52
+ return spectrum_conv
53
+
54
+
55
+ def extend_time_axis(t: np.ndarray, spec: np.ndarray) -> "np.ndarray":
56
+ """
57
+ If the time axis is shorter than the time-dimension of the spectrum it gets
58
+ extended by negative values.
59
+
60
+ Parameters
61
+ ----------
62
+ t : np.ndarray
63
+ Time axis.
64
+ spec : np.ndarray
65
+ 2D-Array containing values of the ordinate of a spectrum.
66
+ In case of TREPR spectra this would be the intensity for each time and
67
+ magnetic field point. The convolution is carried out along the
68
+ second axis.
69
+
70
+ Returns
71
+ -------
72
+ t_extended : np.ndarray
73
+ Time axis. If it has been shorter than the first dimension of spec
74
+ before it is extended by negative values.
75
+
76
+ """
77
+ if len(t) < spec.shape[0]:
78
+ dt = t[1] - t[0]
79
+ diff = spec.shape[0] - len(t)
80
+
81
+ t_neg = np.arange(-diff, 0) * dt
82
+ t_extended = np.concatenate((t_neg, t))
83
+
84
+ return t_extended
85
+
86
+ else:
87
+ return t
teacups/creators.py ADDED
@@ -0,0 +1,314 @@
1
+ import teacups.matrix_tools as mt
2
+ import numpy as np
3
+
4
+ COMPLEX_TYPE = np.complex64
5
+ FLOAT_TYPE = np.float32
6
+
7
+
8
+ def create_tensor(
9
+ diag: list, phi: "np.ndarray", theta: "np.ndarray", first_rotation=list()
10
+ ) -> object:
11
+ """
12
+ Setup any tensor. An object of class mt.Tensor is built. Diagonal elements
13
+ are filled into the tensor in its diagonal coordinate frame. If
14
+ first_rotation is not empty, an initial rotation to laboratory frame will
15
+ be carried out. Afterwards the tensor.multirot attribute is built by using
16
+ the tensor.multirotation function with the angle-arrays phi and theta.
17
+
18
+ Parameters
19
+ ----------
20
+ diag : list
21
+ A list of the three diagonal elements of the tensor.
22
+ phi : np.ndarray
23
+ Array with the phi values for each angle point.
24
+ theta: np.ndarray
25
+ Array with the theta values for each angle point.
26
+ first_rotation : list
27
+ If a first rotation into laboratory frame is wished, first_rotation
28
+ must be set to a list of the three euler angles phi, theta and psi
29
+ around which the tensor is rotated. If it is empty, no rotation will be
30
+ carried out. Default is an empty list.
31
+
32
+ Returns
33
+ -------
34
+ tensor : object
35
+ Tensor object with initialized tensor and multirot.
36
+
37
+ """
38
+ tensor = mt.Tensor(np.array(diag))
39
+
40
+ if len(first_rotation) == 0:
41
+ pass
42
+ else:
43
+ angle_list = np.array(first_rotation, dtype=FLOAT_TYPE)
44
+ tensor.rotation(angle_list[0], angle_list[1], angle_list[2])
45
+ tensor.matrix = tensor.rot.astype(FLOAT_TYPE)
46
+
47
+ tensor.multirotation(phi, theta)
48
+ tensor.multirot = tensor.multirot.astype(FLOAT_TYPE)
49
+ return tensor
50
+
51
+
52
+ def create_zfs_tensor_diagonals(D: float, E: float) -> "np.ndarray":
53
+ """
54
+ Create an array with three diagonal elements of the zero-field splitting
55
+ (ZFS) tensor. Calculate this values from the zero-field-splitting
56
+ parameters D and E by:
57
+
58
+ .. math::
59
+ -1/3*D+E; -1/3*D-E; 2/3*D.
60
+
61
+ Parameters
62
+ ----------
63
+ D : float
64
+ Zero-field-splitting parameter D.
65
+ E : float
66
+ Zero-field-splitting parameter E.
67
+
68
+ Returns
69
+ -------
70
+ diag : np.ndarray
71
+ The three diagonal elements of a ZFS tensor (-1/3*D+E, -1/3*D-E, 2/3*D).
72
+ This is a 1D-array.
73
+
74
+ """
75
+ diag = np.array([-1 / 3 * D + E, -1 / 3 * D - E, 2 / 3 * D], dtype=FLOAT_TYPE)
76
+ return diag
77
+
78
+
79
+ def create_dipol_tensor_diagonals(D: float, E: float) -> "np.ndarray":
80
+ """
81
+ Create an array with three diagonal elements of the dipolar interaction
82
+ tensor of two spin species. Calculate this values from the
83
+ axial dipolar coupling $D$ and the rhombic dipolar coupling $E$ by:
84
+
85
+ .. math::
86
+ D+E; D-E; -2D.
87
+
88
+ Parameters
89
+ ----------
90
+ D : float
91
+ Axial dipolar coupling.
92
+ E : float
93
+ Rhombic dipolar coupling.
94
+
95
+ Returns
96
+ -------
97
+ diag : np.ndarray
98
+ The three diagonal elements of a dipol tensor (D+E, -D-E, -2D).
99
+ This is a 1D-array.
100
+
101
+ """
102
+ diag = np.array([D + E, D - E, -2 * D], dtype=FLOAT_TYPE)
103
+ return diag
104
+
105
+
106
+ def set_up_tensors(sys: object, cal: object) -> None:
107
+ """
108
+ Set up all wished tensors for further calculations. The tensors are set up
109
+ for all orientations and are saved as tensor objects.
110
+ Furthermore, if the frame argument in sys is given, the tensors
111
+ will be rotated to an initial frame.
112
+
113
+ Parameters
114
+ ----------
115
+ sys : object
116
+ Contains parameters concerning the spin system. This function can use
117
+ the following arguments and returns the tensors:
118
+ g1, g2 (g-tensors of a radical pair) + g1_frame/g2_frame [lists]
119
+ g (g-tensor of a radical) + g_frame [lists]
120
+ g_tri (g-tensor of a triplet) + g_tri_frame [lists]
121
+ D, E (dipole coupling parameters of a coupled spin system) [floats]
122
+ + D_frame [list], if D, E are not given, they will be set to zero
123
+ D_tri, E_tri (ZFS parameters of a triplet) [floats]
124
+ + D_tr_frame [list], if D, E are not given, they will be set
125
+ to zero
126
+ cal : object
127
+ Container for calculated tensors. The attributes cal.phi and cal.theta
128
+ (arrays with the angle points on a sphere for rotations) are needed.
129
+
130
+ Attributes
131
+ ----------
132
+ cal.g1_tensor : object
133
+ g1-tensor of the radical pair. This is a Tensor object. (optional)
134
+ cal.g2_tensor : object
135
+ g2-tensor of the radical pair. This is a Tensor object. (optional)
136
+ cal.D_tensor : object
137
+ D-tensor of coupled electrons. This is a Tensor object. (optional)
138
+ cal.g_tri_tensor : object
139
+ g-tensor of the triplet precursor. This is a Tensor object.
140
+ cal.D_tri_tensor : object
141
+ D-Tensor of the triplet precursor. This is a Tensor object.
142
+ cal.g_tensor : object
143
+ g-Tensor of a radical. This is a Tensor object. (optional)
144
+ cal.g_iso : float
145
+ Isotropic g-value.
146
+
147
+ Returns
148
+ -------
149
+ None.
150
+
151
+ """
152
+ if not hasattr(sys, "D"):
153
+ sys.D = 0
154
+ if not hasattr(sys, "E"):
155
+ sys.E = 0
156
+ if not hasattr(sys, "D_tri"):
157
+ sys.D_tri = 0
158
+ if not hasattr(sys, "E_tri"):
159
+ sys.E_tri = 0.01
160
+
161
+ for attr in vars(sys):
162
+ if attr == "g":
163
+ if hasattr(sys, "g_frame"):
164
+ cal.g_tensor = create_tensor(
165
+ sys.g, cal.phi, cal.theta, first_rotation=sys.g_frame
166
+ )
167
+ else:
168
+ cal.g_tensor = create_tensor(sys.g, cal.phi, cal.theta)
169
+ cal.g_iso = 1 / 3 * np.sum(sys.g)
170
+
171
+ elif attr == "g1":
172
+ if hasattr(sys, "g1_frame"):
173
+ cal.g1_tensor = create_tensor(sys.g1, cal.phi, cal.theta, sys.g1_frame)
174
+ else:
175
+ cal.g1_tensor = create_tensor(sys.g1, cal.phi, cal.theta)
176
+
177
+ elif attr == "g2":
178
+ if hasattr(sys, "g2_frame"):
179
+ cal.g2_tensor = create_tensor(sys.g2, cal.phi, cal.theta, sys.g2_frame)
180
+ else:
181
+ cal.g2_tensor = create_tensor(sys.g2, cal.phi, cal.theta)
182
+ cal.g_iso = 1 / 2 * (1 / 3 * np.sum(sys.g1) + 1 / 3 * np.sum(sys.g2))
183
+
184
+ elif attr == "g_tri":
185
+ if hasattr(sys, "g_tri_frame"):
186
+ cal.g_tri_tensor = create_tensor(
187
+ sys.g_tri, cal.phi, cal.theta, sys.g_tri_frame
188
+ )
189
+ else:
190
+ cal.g_tri_tensor = create_tensor(sys.g_tri, cal.phi, cal.theta)
191
+ if hasattr(sys, "g2"):
192
+ pass
193
+ elif hasattr(sys, "g"):
194
+ cal.g_iso = 1 / 2 * (1 / 3 * np.sum(sys.g_tri) + 1 / 3 * np.sum(sys.g))
195
+ else:
196
+ cal.g_iso = 1 / 3 * np.sum(sys.g_tri)
197
+
198
+ elif attr == "D_tri":
199
+ diag = create_zfs_tensor_diagonals(sys.D_tri, sys.E_tri)
200
+ if hasattr(sys, "D_tri_frame"):
201
+ cal.D_tri_tensor = create_tensor(
202
+ diag, cal.phi, cal.theta, sys.D_tri_frame
203
+ )
204
+ else:
205
+ cal.D_tri_tensor = create_tensor(diag, cal.phi, cal.theta)
206
+
207
+ elif attr == "D":
208
+ diag = create_dipol_tensor_diagonals(sys.D, sys.E)
209
+ if hasattr(sys, "D_frame"):
210
+ cal.D_tensor = create_tensor(diag, cal.phi, cal.theta, sys.D_frame)
211
+
212
+ else:
213
+ cal.D_tensor = create_tensor(diag, cal.phi, cal.theta)
214
+
215
+ return None
216
+
217
+
218
+ def set_up_spinoperator(sys: object, cal: object) -> None:
219
+ """
220
+ Set up the spin matrix operators for the spin system.
221
+
222
+ Parameters
223
+ ----------
224
+ sys : object
225
+ Contains parameters of the spin system. This function needs sys.s which
226
+ is a list of the quantum numbers of the spin system.
227
+ cal : object
228
+ Container for calculated spinoperators. This object may be empty.
229
+
230
+ Attributes
231
+ ----------
232
+ cal.s : object
233
+ Spinoperator of the spin system. This is a Spinoperator object.
234
+
235
+ Returns
236
+ -------
237
+ None.
238
+
239
+ """
240
+ try:
241
+ len(sys.s)
242
+ except TypeError:
243
+ sys.s = [sys.s]
244
+
245
+ if len(sys.s) == 1:
246
+ S = mt.Spinoperator(sys.s[0])
247
+ elif len(sys.s) == 2:
248
+ S = mt.Spinoperator(sys.s[0], sys.s[1])
249
+ S.matrix = S.matrix + S.matrix_coupling_spins[0]
250
+
251
+ cal.s = S
252
+
253
+ return None
254
+
255
+
256
+ def set_up_observable(sys: object, opt: object, cal: object) -> None:
257
+ """
258
+ Set up the observable operator for an EPR-Experiment in the singlet-triplet
259
+ basis of the radical pair. Observation direction is y-direction (in case
260
+ of the static magnetic field set in z-direction). The total spin operator
261
+ of a radical pair is taken and transforem to ST-basis. For calculations in
262
+ hilbert-space the operator is given back as a 4x4-matrix. For calculations
263
+ in liouville-space it is given back as a 16x1 vector.
264
+
265
+ Parameters
266
+ ----------
267
+ opt : object
268
+ Contains simulation option parameters. This function uses opt.space
269
+ (set either to 'hilbert' or 'liouville'), which defines the space in
270
+ which the calculation is carried out.
271
+ cal : object
272
+ Container for calculated results during the simulation. This function
273
+ uses the total spinoperator cal.s (e.g. built by the function
274
+ set_up_radical_pair_spinoperators).
275
+
276
+ Attributes
277
+ ----------
278
+ cal.observable : np.ndarray
279
+ Observable operator (S_y-operator in ST-basis) either as a matrix
280
+ (calculations in hilbert-space) or as a vector (calculations in
281
+ liouville space).
282
+
283
+ Returns
284
+ -------
285
+ None.
286
+
287
+ """
288
+
289
+ obs = mt.Operator(cal.s.dimension)
290
+ obs.matrix = cal.s.get("y")
291
+
292
+ if sys.spin_system == "rp":
293
+ st_transformation = np.array(
294
+ [
295
+ [1, 0, 0, 0],
296
+ [0, np.sqrt(1 / 2), np.sqrt(1 / 2), 0],
297
+ [0, -np.sqrt(1 / 2), np.sqrt(1 / 2), 0],
298
+ [0, 0, 0, 1],
299
+ ],
300
+ dtype=FLOAT_TYPE,
301
+ )
302
+ obs.matrix = st_transformation.T @ obs.matrix @ st_transformation
303
+
304
+ obs.matrix = np.conjugate(obs.matrix.T)
305
+
306
+ if opt.space == "hilbert":
307
+ cal.observable = obs.matrix
308
+ elif opt.space == "liouville":
309
+ obs.build_vector()
310
+ cal.observable = obs.vector
311
+ else:
312
+ print("opt.space has to be either lioville or hilbert")
313
+
314
+ return None