advisor-scattering 0.5.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.
- advisor/__init__.py +3 -0
- advisor/__main__.py +7 -0
- advisor/app.py +40 -0
- advisor/controllers/__init__.py +6 -0
- advisor/controllers/app_controller.py +69 -0
- advisor/controllers/feature_controller.py +25 -0
- advisor/domain/__init__.py +23 -0
- advisor/domain/core/__init__.py +8 -0
- advisor/domain/core/lab.py +121 -0
- advisor/domain/core/lattice.py +79 -0
- advisor/domain/core/sample.py +101 -0
- advisor/domain/geometry.py +212 -0
- advisor/domain/unit_converter.py +82 -0
- advisor/features/__init__.py +6 -0
- advisor/features/scattering_geometry/controllers/__init__.py +5 -0
- advisor/features/scattering_geometry/controllers/scattering_geometry_controller.py +26 -0
- advisor/features/scattering_geometry/domain/__init__.py +5 -0
- advisor/features/scattering_geometry/domain/brillouin_calculator.py +410 -0
- advisor/features/scattering_geometry/domain/core.py +516 -0
- advisor/features/scattering_geometry/ui/__init__.py +5 -0
- advisor/features/scattering_geometry/ui/components/__init__.py +17 -0
- advisor/features/scattering_geometry/ui/components/angles_to_hkl_components.py +150 -0
- advisor/features/scattering_geometry/ui/components/hk_angles_components.py +430 -0
- advisor/features/scattering_geometry/ui/components/hkl_scan_components.py +526 -0
- advisor/features/scattering_geometry/ui/components/hkl_to_angles_components.py +315 -0
- advisor/features/scattering_geometry/ui/scattering_geometry_tab.py +725 -0
- advisor/features/structure_factor/controllers/__init__.py +6 -0
- advisor/features/structure_factor/controllers/structure_factor_controller.py +25 -0
- advisor/features/structure_factor/domain/__init__.py +6 -0
- advisor/features/structure_factor/domain/structure_factor_calculator.py +107 -0
- advisor/features/structure_factor/ui/__init__.py +6 -0
- advisor/features/structure_factor/ui/components/__init__.py +12 -0
- advisor/features/structure_factor/ui/components/customized_plane_components.py +358 -0
- advisor/features/structure_factor/ui/components/hkl_plane_components.py +391 -0
- advisor/features/structure_factor/ui/structure_factor_tab.py +273 -0
- advisor/resources/__init__.py +0 -0
- advisor/resources/config/app_config.json +14 -0
- advisor/resources/config/tips.json +4 -0
- advisor/resources/data/nacl.cif +111 -0
- advisor/resources/icons/bz_caculator.jpg +0 -0
- advisor/resources/icons/bz_calculator.png +0 -0
- advisor/resources/icons/minus.svg +3 -0
- advisor/resources/icons/placeholder.png +0 -0
- advisor/resources/icons/plus.svg +3 -0
- advisor/resources/icons/reset.png +0 -0
- advisor/resources/icons/sf_calculator.jpg +0 -0
- advisor/resources/icons/sf_calculator.png +0 -0
- advisor/resources/icons.qrc +6 -0
- advisor/resources/qss/styles.qss +348 -0
- advisor/resources/resources_rc.py +83 -0
- advisor/ui/__init__.py +7 -0
- advisor/ui/init_window.py +566 -0
- advisor/ui/main_window.py +174 -0
- advisor/ui/tab_interface.py +44 -0
- advisor/ui/tips.py +30 -0
- advisor/ui/utils/__init__.py +6 -0
- advisor/ui/utils/readcif.py +129 -0
- advisor/ui/visualizers/HKLScan2DVisualizer.py +224 -0
- advisor/ui/visualizers/__init__.py +8 -0
- advisor/ui/visualizers/coordinate_visualizer.py +203 -0
- advisor/ui/visualizers/scattering_visualizer.py +301 -0
- advisor/ui/visualizers/structure_factor_visualizer.py +426 -0
- advisor/ui/visualizers/structure_factor_visualizer_2d.py +235 -0
- advisor/ui/visualizers/unitcell_visualizer.py +518 -0
- advisor_scattering-0.5.0.dist-info/METADATA +122 -0
- advisor_scattering-0.5.0.dist-info/RECORD +69 -0
- advisor_scattering-0.5.0.dist-info/WHEEL +5 -0
- advisor_scattering-0.5.0.dist-info/entry_points.txt +3 -0
- advisor_scattering-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.optimize import fsolve
|
|
5
|
+
|
|
6
|
+
from advisor.domain import angle_to_matrix
|
|
7
|
+
from advisor.domain.core import Lab
|
|
8
|
+
from .core import (
|
|
9
|
+
_calculate_angles_factory,
|
|
10
|
+
_calculate_angles_tth_fixed,
|
|
11
|
+
_calculate_hkl,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_feasible(theta, tth):
|
|
16
|
+
"""Check if the given theta is feasible. criteria: theta>0 and theta<tth
|
|
17
|
+
"""
|
|
18
|
+
theta = np.array(theta)
|
|
19
|
+
tth = np.array(tth)
|
|
20
|
+
return (theta > 0) & (theta < tth)
|
|
21
|
+
|
|
22
|
+
class BrillouinCalculator:
|
|
23
|
+
"""Interface for the Brillouin zone calculator.
|
|
24
|
+
|
|
25
|
+
This class handles all the calculations required for the Brillouin zone
|
|
26
|
+
calculator tab. It's a pure Python implementation without PyQt dependencies.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
"""Initialize the calculator."""
|
|
31
|
+
self._initialized = False
|
|
32
|
+
|
|
33
|
+
# Physical constants
|
|
34
|
+
self.hPlanck = 6.62607015e-34 # Planck's constant [J·s]
|
|
35
|
+
self.c_light = 299792458 # Speed of light [m/s]
|
|
36
|
+
self.e = 1.602176634e-19 # Elementary charge [C]
|
|
37
|
+
|
|
38
|
+
# Initialize sample
|
|
39
|
+
self.lab = Lab()
|
|
40
|
+
self.roll = 0.0
|
|
41
|
+
self.pitch = 0.0
|
|
42
|
+
self.yaw = 0.0
|
|
43
|
+
|
|
44
|
+
# X-ray energy and derived quantities
|
|
45
|
+
self.energy = 930 # eV
|
|
46
|
+
self.lambda_A = None # wavelength in Angstroms
|
|
47
|
+
self.k_in = None # wavevector magnitude
|
|
48
|
+
|
|
49
|
+
# Reciprocal lattice vectors (calculated during initialization)
|
|
50
|
+
self.reciprocal_lattice = None
|
|
51
|
+
self.visualizer = None
|
|
52
|
+
|
|
53
|
+
def initialize(
|
|
54
|
+
self,
|
|
55
|
+
params: dict,
|
|
56
|
+
):
|
|
57
|
+
"""Initialize with lattice parameters.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
a, b, c (float): Lattice constants in Angstroms
|
|
61
|
+
alpha, beta, gamma (float): Lattice angles in degrees
|
|
62
|
+
energy (float): X-ray energy in eV
|
|
63
|
+
yaw, pitch, roll (float): lattice rotation in degrees
|
|
64
|
+
theta, phi, chi (float): sample rotation in degrees
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
bool: True if initialization was successful
|
|
68
|
+
"""
|
|
69
|
+
roll = params.get("roll", 0.0)
|
|
70
|
+
pitch = params.get("pitch", 0.0)
|
|
71
|
+
yaw = params.get("yaw", 0.0)
|
|
72
|
+
a, b, c = params.get("a", 4), params.get("b", 4), params.get("c", 12)
|
|
73
|
+
alpha, beta, gamma = (
|
|
74
|
+
params.get("alpha", 90.0),
|
|
75
|
+
params.get("beta", 90.0),
|
|
76
|
+
params.get("gamma", 90.0),
|
|
77
|
+
)
|
|
78
|
+
# the default sample rotation position
|
|
79
|
+
theta, phi, chi = 0.0, 0.0, 0.0
|
|
80
|
+
try:
|
|
81
|
+
# Store parameters
|
|
82
|
+
self.energy = params["energy"]
|
|
83
|
+
|
|
84
|
+
# Initialize sample
|
|
85
|
+
self.lab.initialize(
|
|
86
|
+
a,
|
|
87
|
+
b,
|
|
88
|
+
c,
|
|
89
|
+
alpha,
|
|
90
|
+
beta,
|
|
91
|
+
gamma,
|
|
92
|
+
roll,
|
|
93
|
+
pitch,
|
|
94
|
+
yaw,
|
|
95
|
+
theta,
|
|
96
|
+
phi,
|
|
97
|
+
chi,
|
|
98
|
+
)
|
|
99
|
+
# Calculate wavelength and wavevector
|
|
100
|
+
self.lambda_A = (
|
|
101
|
+
(self.hPlanck * self.c_light) / (self.energy * self.e) * 1e10
|
|
102
|
+
)
|
|
103
|
+
self.k_in = 2 * np.pi / self.lambda_A
|
|
104
|
+
|
|
105
|
+
self._initialized = True
|
|
106
|
+
return True
|
|
107
|
+
except Exception as e:
|
|
108
|
+
print(f"Error initializing calculator: {str(e)}")
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
def _sample_to_lab_conversion(self, a_vec, b_vec, c_vec):
|
|
112
|
+
"""Convert vectors from sample coordinate system to lab coordinate system."""
|
|
113
|
+
# For now, just return the same vectors
|
|
114
|
+
# This should be implemented based on the actual coordinate system conversion
|
|
115
|
+
return a_vec, b_vec, c_vec
|
|
116
|
+
|
|
117
|
+
def get_k_magnitude(self, tth):
|
|
118
|
+
return 2.0 * self.k_in * np.sin(np.radians(tth / 2.0))
|
|
119
|
+
|
|
120
|
+
def calculate_hkl(self, tth, theta, phi, chi):
|
|
121
|
+
"""Calculate HKL from scattering angles.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
tth (float): Scattering angle in degrees
|
|
125
|
+
theta (float): Sample theta rotation in degrees
|
|
126
|
+
phi (float): Sample phi rotation in degrees
|
|
127
|
+
chi (float): Sample chi rotation in degrees
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
if not self.is_initialized():
|
|
131
|
+
raise ValueError("Calculator not initialized")
|
|
132
|
+
|
|
133
|
+
a_vec_lab, b_vec_lab, c_vec_lab = self.lab.get_real_space_vectors()
|
|
134
|
+
return _calculate_hkl(
|
|
135
|
+
self.k_in, tth, theta, phi, chi, a_vec_lab, b_vec_lab, c_vec_lab
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def calculate_angles(
|
|
139
|
+
self,
|
|
140
|
+
H,
|
|
141
|
+
K,
|
|
142
|
+
L,
|
|
143
|
+
fixed_angle,
|
|
144
|
+
fixed_angle_name="chi",
|
|
145
|
+
):
|
|
146
|
+
"""Calculate scattering angles from HKL indices.
|
|
147
|
+
|
|
148
|
+
CURRENTLY THE CHI IS FIXED TO 0, TO BE EXTENDED
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
h, k, l (float): HKL indices
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
dict: Dictionary containing scattering angles and minimum energy
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
if not self.is_initialized():
|
|
158
|
+
raise ValueError("Calculator not initialized")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
calculate_angles = _calculate_angles_factory(fixed_angle_name)
|
|
162
|
+
a, b, c, alpha, beta, gamma = self.lab.get_lattice_parameters()
|
|
163
|
+
roll, pitch, yaw = self.lab.get_lattice_angles()
|
|
164
|
+
try:
|
|
165
|
+
tth_result, theta_result, phi_result, chi_result = calculate_angles(
|
|
166
|
+
self.k_in,
|
|
167
|
+
H,
|
|
168
|
+
K,
|
|
169
|
+
L,
|
|
170
|
+
a,
|
|
171
|
+
b,
|
|
172
|
+
c,
|
|
173
|
+
alpha,
|
|
174
|
+
beta,
|
|
175
|
+
gamma,
|
|
176
|
+
roll,
|
|
177
|
+
pitch,
|
|
178
|
+
yaw,
|
|
179
|
+
fixed_angle,
|
|
180
|
+
)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
return {
|
|
183
|
+
"success": False,
|
|
184
|
+
"error": "No solution found; The Q point is possible not reachable at this energy and/or scattering angle tth. System message:" + str(e),
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
"tth": tth_result,
|
|
188
|
+
"theta": theta_result,
|
|
189
|
+
"phi": phi_result,
|
|
190
|
+
"chi": chi_result,
|
|
191
|
+
"H": H,
|
|
192
|
+
"K": K,
|
|
193
|
+
"L": L,
|
|
194
|
+
"success": True,
|
|
195
|
+
"error": None,
|
|
196
|
+
"feasible": is_feasible(theta_result, tth_result),
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
def calculate_angles_tth_fixed(
|
|
200
|
+
self,
|
|
201
|
+
tth,
|
|
202
|
+
H=0.15,
|
|
203
|
+
K=0.1,
|
|
204
|
+
L=None,
|
|
205
|
+
fixed_angle_name="chi",
|
|
206
|
+
fixed_angle=0.0,
|
|
207
|
+
):
|
|
208
|
+
a, b, c, alpha, beta, gamma = self.lab.get_lattice_parameters()
|
|
209
|
+
roll, pitch, yaw = self.lab.get_lattice_angles()
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
tth_result, theta_result, phi_result, chi_result, momentum = (
|
|
213
|
+
_calculate_angles_tth_fixed(
|
|
214
|
+
self.k_in,
|
|
215
|
+
tth,
|
|
216
|
+
a,
|
|
217
|
+
b,
|
|
218
|
+
c,
|
|
219
|
+
alpha,
|
|
220
|
+
beta,
|
|
221
|
+
gamma,
|
|
222
|
+
roll,
|
|
223
|
+
pitch,
|
|
224
|
+
yaw,
|
|
225
|
+
H,
|
|
226
|
+
K,
|
|
227
|
+
L,
|
|
228
|
+
fixed_angle_name,
|
|
229
|
+
fixed_angle,
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
H = momentum if H is None else H
|
|
233
|
+
K = momentum if K is None else K
|
|
234
|
+
L = momentum if L is None else L
|
|
235
|
+
except Exception as e:
|
|
236
|
+
return {
|
|
237
|
+
"success": False,
|
|
238
|
+
"error": "No solution found; The Q point is possible not reachable at this energy and/or scattering angle tth. System message:" + str(e),
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
result = {
|
|
242
|
+
"tth": tth_result,
|
|
243
|
+
"theta": theta_result,
|
|
244
|
+
"phi": phi_result,
|
|
245
|
+
"chi": chi_result,
|
|
246
|
+
"H": H,
|
|
247
|
+
"K": K,
|
|
248
|
+
"L": L,
|
|
249
|
+
"success": True,
|
|
250
|
+
"error": None,
|
|
251
|
+
"feasible": is_feasible(theta_result, tth_result),
|
|
252
|
+
}
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
def calculate_angles_tth_fixed_scan(
|
|
256
|
+
self,
|
|
257
|
+
tth,
|
|
258
|
+
start_points,
|
|
259
|
+
end_points,
|
|
260
|
+
num_points,
|
|
261
|
+
deactivated_index,
|
|
262
|
+
fixed_angle_name="chi",
|
|
263
|
+
fixed_angle=0.0,
|
|
264
|
+
):
|
|
265
|
+
"""Calculate scattering angles for a range of HKL values with fixed tth.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
tth (float): Fixed scattering angle in degrees
|
|
269
|
+
start_points (tuple): Starting HKL values (the deactivated index will be ignored)
|
|
270
|
+
end_points (tuple): Ending HKL values (the deactivated index will be ignored)
|
|
271
|
+
num_points (int): Number of points to calculate
|
|
272
|
+
deactivated_index (str): Which index is deactivated ('H', 'K', or 'L')
|
|
273
|
+
fixed_angle_name (str): Name of the fixed angle ('chi' or 'phi')
|
|
274
|
+
fixed_angle (float): Value of the fixed angle in degrees
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
dict: Dictionary containing lists of all calculated values
|
|
278
|
+
"""
|
|
279
|
+
if not self.is_initialized():
|
|
280
|
+
return {"success": False, "error": "Calculator not initialized"}
|
|
281
|
+
|
|
282
|
+
if num_points < 2:
|
|
283
|
+
return {"success": False, "error": "Number of points must be at least 2"}
|
|
284
|
+
|
|
285
|
+
# Ensure we have valid inputs
|
|
286
|
+
if deactivated_index not in ["H", "K", "L"]:
|
|
287
|
+
return {
|
|
288
|
+
"success": False,
|
|
289
|
+
"error": f"Invalid deactivated index: {deactivated_index}",
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if fixed_angle_name not in ["chi", "phi"]:
|
|
293
|
+
return {
|
|
294
|
+
"success": False,
|
|
295
|
+
"error": f"Invalid fixed angle name: {fixed_angle_name}",
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
start_h, start_k, start_l = start_points
|
|
299
|
+
end_h, end_k, end_l = end_points
|
|
300
|
+
|
|
301
|
+
# Generate the list of points
|
|
302
|
+
h_values = (
|
|
303
|
+
np.linspace(start_h, end_h, num_points)
|
|
304
|
+
if deactivated_index != "H"
|
|
305
|
+
else [None] * num_points
|
|
306
|
+
)
|
|
307
|
+
k_values = (
|
|
308
|
+
np.linspace(start_k, end_k, num_points)
|
|
309
|
+
if deactivated_index != "K"
|
|
310
|
+
else [None] * num_points
|
|
311
|
+
)
|
|
312
|
+
l_values = (
|
|
313
|
+
np.linspace(start_l, end_l, num_points)
|
|
314
|
+
if deactivated_index != "L"
|
|
315
|
+
else [None] * num_points
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# Initialize result lists
|
|
319
|
+
all_tth = []
|
|
320
|
+
all_theta = []
|
|
321
|
+
all_phi = []
|
|
322
|
+
all_chi = []
|
|
323
|
+
all_h = []
|
|
324
|
+
all_k = []
|
|
325
|
+
all_l = []
|
|
326
|
+
|
|
327
|
+
# Calculate for each point
|
|
328
|
+
for i in range(num_points):
|
|
329
|
+
h = h_values[i]
|
|
330
|
+
k = k_values[i]
|
|
331
|
+
l = l_values[i]
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
result = self.calculate_angles_tth_fixed(
|
|
335
|
+
tth=tth,
|
|
336
|
+
H=h,
|
|
337
|
+
K=k,
|
|
338
|
+
L=l,
|
|
339
|
+
fixed_angle_name=fixed_angle_name,
|
|
340
|
+
fixed_angle=fixed_angle,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if not result.get("success", False):
|
|
344
|
+
# Skip points that fail to calculate but don't fail completely
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
# Each calculation can return multiple solutions
|
|
348
|
+
all_tth.append(result["tth"])
|
|
349
|
+
all_theta.append(result["theta"])
|
|
350
|
+
all_phi.append(result["phi"])
|
|
351
|
+
all_chi.append(result["chi"])
|
|
352
|
+
all_h.append(result["H"])
|
|
353
|
+
all_k.append(result["K"])
|
|
354
|
+
all_l.append(result["L"])
|
|
355
|
+
except Exception as e:
|
|
356
|
+
# Log the error but continue with other points
|
|
357
|
+
print(f"Error calculating point {(h, k, l)}: {str(e)}")
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
# Check if we have any results
|
|
361
|
+
if not all_tth:
|
|
362
|
+
return {
|
|
363
|
+
"success": False,
|
|
364
|
+
"error": "No valid solutions found for any point in the scan",
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
"tth": all_tth,
|
|
368
|
+
"theta": all_theta,
|
|
369
|
+
"phi": all_phi,
|
|
370
|
+
"chi": all_chi,
|
|
371
|
+
"H": all_h,
|
|
372
|
+
"K": all_k,
|
|
373
|
+
"L": all_l,
|
|
374
|
+
"deactivated_index": deactivated_index, # Store which index was deactivated
|
|
375
|
+
"success": True,
|
|
376
|
+
"error": None,
|
|
377
|
+
"feasible": is_feasible(all_theta, all_tth),
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
def is_initialized(self):
|
|
381
|
+
"""Check if the calculator is initialized.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
bool: True if the calculator is initialized
|
|
385
|
+
"""
|
|
386
|
+
return self._initialized
|
|
387
|
+
|
|
388
|
+
def get_lattice_parameters(self):
|
|
389
|
+
"""Get the current lattice parameters.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
dict: Dictionary containing lattice parameters
|
|
393
|
+
"""
|
|
394
|
+
a, b, c, alpha, beta, gamma = self.lab.get_lattice_parameters()
|
|
395
|
+
return {
|
|
396
|
+
"a": a,
|
|
397
|
+
"b": b,
|
|
398
|
+
"c": c,
|
|
399
|
+
"alpha": alpha,
|
|
400
|
+
"beta": beta,
|
|
401
|
+
"gamma": gamma,
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
def get_real_space_vectors(self):
|
|
405
|
+
"""Get the real space vectors.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
frame (str): "sample" or "lab"
|
|
409
|
+
"""
|
|
410
|
+
return self.lab.get_real_space_vectors()
|