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.
Files changed (69) hide show
  1. advisor/__init__.py +3 -0
  2. advisor/__main__.py +7 -0
  3. advisor/app.py +40 -0
  4. advisor/controllers/__init__.py +6 -0
  5. advisor/controllers/app_controller.py +69 -0
  6. advisor/controllers/feature_controller.py +25 -0
  7. advisor/domain/__init__.py +23 -0
  8. advisor/domain/core/__init__.py +8 -0
  9. advisor/domain/core/lab.py +121 -0
  10. advisor/domain/core/lattice.py +79 -0
  11. advisor/domain/core/sample.py +101 -0
  12. advisor/domain/geometry.py +212 -0
  13. advisor/domain/unit_converter.py +82 -0
  14. advisor/features/__init__.py +6 -0
  15. advisor/features/scattering_geometry/controllers/__init__.py +5 -0
  16. advisor/features/scattering_geometry/controllers/scattering_geometry_controller.py +26 -0
  17. advisor/features/scattering_geometry/domain/__init__.py +5 -0
  18. advisor/features/scattering_geometry/domain/brillouin_calculator.py +410 -0
  19. advisor/features/scattering_geometry/domain/core.py +516 -0
  20. advisor/features/scattering_geometry/ui/__init__.py +5 -0
  21. advisor/features/scattering_geometry/ui/components/__init__.py +17 -0
  22. advisor/features/scattering_geometry/ui/components/angles_to_hkl_components.py +150 -0
  23. advisor/features/scattering_geometry/ui/components/hk_angles_components.py +430 -0
  24. advisor/features/scattering_geometry/ui/components/hkl_scan_components.py +526 -0
  25. advisor/features/scattering_geometry/ui/components/hkl_to_angles_components.py +315 -0
  26. advisor/features/scattering_geometry/ui/scattering_geometry_tab.py +725 -0
  27. advisor/features/structure_factor/controllers/__init__.py +6 -0
  28. advisor/features/structure_factor/controllers/structure_factor_controller.py +25 -0
  29. advisor/features/structure_factor/domain/__init__.py +6 -0
  30. advisor/features/structure_factor/domain/structure_factor_calculator.py +107 -0
  31. advisor/features/structure_factor/ui/__init__.py +6 -0
  32. advisor/features/structure_factor/ui/components/__init__.py +12 -0
  33. advisor/features/structure_factor/ui/components/customized_plane_components.py +358 -0
  34. advisor/features/structure_factor/ui/components/hkl_plane_components.py +391 -0
  35. advisor/features/structure_factor/ui/structure_factor_tab.py +273 -0
  36. advisor/resources/__init__.py +0 -0
  37. advisor/resources/config/app_config.json +14 -0
  38. advisor/resources/config/tips.json +4 -0
  39. advisor/resources/data/nacl.cif +111 -0
  40. advisor/resources/icons/bz_caculator.jpg +0 -0
  41. advisor/resources/icons/bz_calculator.png +0 -0
  42. advisor/resources/icons/minus.svg +3 -0
  43. advisor/resources/icons/placeholder.png +0 -0
  44. advisor/resources/icons/plus.svg +3 -0
  45. advisor/resources/icons/reset.png +0 -0
  46. advisor/resources/icons/sf_calculator.jpg +0 -0
  47. advisor/resources/icons/sf_calculator.png +0 -0
  48. advisor/resources/icons.qrc +6 -0
  49. advisor/resources/qss/styles.qss +348 -0
  50. advisor/resources/resources_rc.py +83 -0
  51. advisor/ui/__init__.py +7 -0
  52. advisor/ui/init_window.py +566 -0
  53. advisor/ui/main_window.py +174 -0
  54. advisor/ui/tab_interface.py +44 -0
  55. advisor/ui/tips.py +30 -0
  56. advisor/ui/utils/__init__.py +6 -0
  57. advisor/ui/utils/readcif.py +129 -0
  58. advisor/ui/visualizers/HKLScan2DVisualizer.py +224 -0
  59. advisor/ui/visualizers/__init__.py +8 -0
  60. advisor/ui/visualizers/coordinate_visualizer.py +203 -0
  61. advisor/ui/visualizers/scattering_visualizer.py +301 -0
  62. advisor/ui/visualizers/structure_factor_visualizer.py +426 -0
  63. advisor/ui/visualizers/structure_factor_visualizer_2d.py +235 -0
  64. advisor/ui/visualizers/unitcell_visualizer.py +518 -0
  65. advisor_scattering-0.5.0.dist-info/METADATA +122 -0
  66. advisor_scattering-0.5.0.dist-info/RECORD +69 -0
  67. advisor_scattering-0.5.0.dist-info/WHEEL +5 -0
  68. advisor_scattering-0.5.0.dist-info/entry_points.txt +3 -0
  69. 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()