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,315 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # pylint: disable=no-name-in-module, import-error
4
+ from PyQt5.QtWidgets import (
5
+ QWidget,
6
+ QVBoxLayout,
7
+ QHBoxLayout,
8
+ QFormLayout,
9
+ QGroupBox,
10
+ QLabel,
11
+ QPushButton,
12
+ QDoubleSpinBox,
13
+ QTableWidget,
14
+ QTableWidgetItem,
15
+ QHeaderView,
16
+ QRadioButton,
17
+ QButtonGroup,
18
+ )
19
+ from PyQt5.QtCore import Qt, pyqtSignal
20
+ from PyQt5.QtGui import QColor, QBrush
21
+ class HKLToAnglesControls(QWidget):
22
+ """Widget for HKL to Angles calculation controls."""
23
+
24
+ # Signal emitted when calculate button is clicked
25
+ calculateClicked = pyqtSignal()
26
+
27
+ def __init__(self, parent=None):
28
+ super().__init__(parent)
29
+
30
+ # Main layout
31
+ main_layout = QVBoxLayout(self)
32
+
33
+ # HKL Input form
34
+ form_group = QGroupBox("HKL Indices")
35
+ form_layout = QVBoxLayout(form_group)
36
+
37
+ # Create HKL inputs horizontally aligned
38
+ hkl_inputs_widget = QWidget()
39
+ hkl_inputs_layout = QHBoxLayout(hkl_inputs_widget)
40
+ hkl_inputs_layout.setContentsMargins(0, 0, 0, 0)
41
+
42
+ # H input
43
+ h_form = QWidget()
44
+ h_form_layout = QFormLayout(h_form)
45
+ h_form_layout.setContentsMargins(0, 0, 0, 0)
46
+ self.H_input = QDoubleSpinBox()
47
+ self.H_input.setRange(-100.0, 100)
48
+ self.H_input.setDecimals(4)
49
+ self.H_input.setValue(0.15)
50
+ h_form_layout.addRow("H:", self.H_input)
51
+ hkl_inputs_layout.addWidget(h_form)
52
+
53
+ # K input
54
+ k_form = QWidget()
55
+ k_form_layout = QFormLayout(k_form)
56
+ k_form_layout.setContentsMargins(0, 0, 0, 0)
57
+ self.K_input = QDoubleSpinBox()
58
+ self.K_input.setRange(-100.0, 100)
59
+ self.K_input.setDecimals(4)
60
+ self.K_input.setValue(0.1)
61
+ k_form_layout.addRow("K:", self.K_input)
62
+ hkl_inputs_layout.addWidget(k_form)
63
+
64
+ # L input
65
+ l_form = QWidget()
66
+ l_form_layout = QFormLayout(l_form)
67
+ l_form_layout.setContentsMargins(0, 0, 0, 0)
68
+ self.L_input = QDoubleSpinBox()
69
+ self.L_input.setRange(-100.0, 100)
70
+ self.L_input.setDecimals(4)
71
+ self.L_input.setValue(-0.5)
72
+ l_form_layout.addRow("L:", self.L_input)
73
+ hkl_inputs_layout.addWidget(l_form)
74
+
75
+ form_layout.addWidget(hkl_inputs_widget)
76
+
77
+ main_layout.addWidget(form_group)
78
+
79
+ # Constraints group - using the same style as other tabs
80
+ constraints_group = QGroupBox("Constraints")
81
+ constraints_layout = QVBoxLayout(constraints_group)
82
+
83
+ # Fixed angle selection buttons - top row like other tabs
84
+ angle_selection = QWidget()
85
+ angle_selection_layout = QHBoxLayout(angle_selection)
86
+ angle_selection_layout.setContentsMargins(0, 0, 0, 0)
87
+
88
+ self.fix_chi_btn = QPushButton("Fix χ")
89
+ self.fix_phi_btn = QPushButton("Fix φ")
90
+
91
+ # Make buttons checkable for toggle behavior
92
+ for btn in (self.fix_chi_btn, self.fix_phi_btn):
93
+ btn.setCheckable(True)
94
+
95
+ self.fix_chi_btn.setChecked(True) # Default to fixed chi
96
+
97
+ # Create a button group for mutual exclusion
98
+ self.angle_button_group = QButtonGroup(self)
99
+ self.angle_button_group.addButton(self.fix_chi_btn)
100
+ self.angle_button_group.addButton(self.fix_phi_btn)
101
+
102
+ angle_selection_layout.addWidget(self.fix_chi_btn)
103
+ angle_selection_layout.addWidget(self.fix_phi_btn)
104
+
105
+ constraints_layout.addWidget(angle_selection)
106
+
107
+ # Create angle value inputs - bottom row like other tabs
108
+ angles_row = QWidget()
109
+ angles_layout = QHBoxLayout(angles_row)
110
+ angles_layout.setContentsMargins(0, 0, 0, 0)
111
+
112
+ # Chi input
113
+ self.chi_widget = QWidget()
114
+ chi_layout = QFormLayout(self.chi_widget)
115
+ chi_layout.setContentsMargins(0, 0, 0, 0)
116
+ self.chi_input = QDoubleSpinBox()
117
+ self.chi_input.setRange(-180.0, 180.0)
118
+ self.chi_input.setValue(0.0)
119
+ self.chi_input.setSuffix(" °")
120
+ chi_layout.addRow("χ:", self.chi_input)
121
+ angles_layout.addWidget(self.chi_widget)
122
+
123
+ # Phi input
124
+ self.phi_widget = QWidget()
125
+ phi_layout = QFormLayout(self.phi_widget)
126
+ phi_layout.setContentsMargins(0, 0, 0, 0)
127
+ self.phi_input = QDoubleSpinBox()
128
+ self.phi_input.setRange(-180.0, 180.0)
129
+ self.phi_input.setValue(0.0)
130
+ self.phi_input.setSuffix(" °")
131
+ phi_layout.addRow("φ:", self.phi_input)
132
+ angles_layout.addWidget(self.phi_widget)
133
+
134
+ constraints_layout.addWidget(angles_row)
135
+ main_layout.addWidget(constraints_group)
136
+
137
+ # Calculate button
138
+ self.calculate_button = QPushButton("Calculate Angles")
139
+ self.calculate_button.clicked.connect(self.calculateClicked.emit)
140
+ self.calculate_button.setObjectName("calculateButton")
141
+ main_layout.addWidget(self.calculate_button)
142
+
143
+ # Connect signals
144
+ self.fix_chi_btn.clicked.connect(lambda: self._set_active_fixed_angle("chi"))
145
+ self.fix_phi_btn.clicked.connect(lambda: self._set_active_fixed_angle("phi"))
146
+
147
+ # Initialize widget states
148
+ self._set_active_fixed_angle("chi") # Set initial fixed angle and apply styling
149
+
150
+ def _update_fixed_angle_ui(self):
151
+ """Update UI based on which angle is fixed.
152
+
153
+ If chi is fixed: Show chi input, hide phi input
154
+ If phi is fixed: Show phi input, hide chi input
155
+ """
156
+ is_chi_fixed = self.fix_chi_btn.isChecked()
157
+ self.chi_widget.setVisible(is_chi_fixed)
158
+ self.phi_widget.setVisible(not is_chi_fixed)
159
+
160
+ def _update_fixed_angle_styles(self, active: str):
161
+ """Update fixed angle button colors based on active selection."""
162
+ mapping = {
163
+ "chi": self.fix_chi_btn,
164
+ "phi": self.fix_phi_btn,
165
+ }
166
+ for name, btn in mapping.items():
167
+ if name == active:
168
+ btn.setChecked(True)
169
+ btn.setProperty("class", "activeToggle")
170
+ else:
171
+ btn.setChecked(False)
172
+ btn.setProperty("class", "inactiveToggle")
173
+ # Force style refresh
174
+ btn.style().unpolish(btn)
175
+ btn.style().polish(btn)
176
+
177
+ def _set_active_fixed_angle(self, angle: str):
178
+ """Set the active fixed angle and update widget states and styling."""
179
+ angle = angle.lower()
180
+ self._update_fixed_angle_styles(angle)
181
+ self._update_fixed_angle_ui()
182
+
183
+ def get_calculation_parameters(self):
184
+ """Get parameters for angle calculation."""
185
+ # Get fixed angle
186
+ fixed_angle_name = "chi" if self.fix_chi_btn.isChecked() else "phi"
187
+ fixed_angle_value = (
188
+ self.chi_input.value()
189
+ if self.fix_chi_btn.isChecked()
190
+ else self.phi_input.value()
191
+ )
192
+
193
+ return {
194
+ "H": self.H_input.value(),
195
+ "K": self.K_input.value(),
196
+ "L": self.L_input.value(),
197
+ "fixed_angle_name": fixed_angle_name,
198
+ "fixed_angle_value": fixed_angle_value,
199
+ }
200
+
201
+ def set_hkl_values(self, H=None, K=None, L=None):
202
+ """Set HKL input values programmatically."""
203
+ if H is not None:
204
+ self.H_input.setValue(H)
205
+ if K is not None:
206
+ self.K_input.setValue(K)
207
+ if L is not None:
208
+ self.L_input.setValue(L)
209
+
210
+
211
+ class HKLToAnglesResultsTable(QTableWidget):
212
+ """Table to display HKL to Angles calculation results."""
213
+
214
+ # Signal emitted when a solution is selected
215
+ solutionSelected = pyqtSignal(dict)
216
+
217
+ def __init__(self, parent=None):
218
+ super().__init__(parent)
219
+ # Set up table
220
+ self.setColumnCount(4)
221
+ self.setHorizontalHeaderLabels(["tth (°)", "θ (°)", "φ (°)", "χ (°)"])
222
+ self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
223
+
224
+ # Hide vertical header (row numbers)
225
+ self.verticalHeader().setVisible(False)
226
+
227
+ # Connect selection change signal
228
+ self.itemSelectionChanged.connect(self.on_selection_changed)
229
+
230
+ # Store the last results for reference
231
+ self.last_results = None
232
+
233
+ def display_results(self, results):
234
+ """Append calculation results to the table."""
235
+ # Don't clear table - append new results
236
+ self.last_results = results
237
+
238
+ # Check if we have results
239
+ if not results or not results.get("success", False):
240
+ return
241
+
242
+
243
+
244
+ # Add rows for each new solution
245
+ row_position = self.rowCount()
246
+ self.insertRow(row_position)
247
+
248
+ # Add solution data
249
+ self.setItem(row_position, 0, QTableWidgetItem(f"{results['tth']:.1f}"))
250
+ self.setItem(row_position, 1, QTableWidgetItem(f"{results['theta']:.1f}"))
251
+ self.setItem(row_position, 2, QTableWidgetItem(f"{results['phi']:.1f}"))
252
+ self.setItem(row_position, 3, QTableWidgetItem(f"{results['chi']:.1f}"))
253
+
254
+ # Highlight new solutions with light blue background
255
+ feasible_brush = QBrush(QColor(198, 239, 206)) # light green
256
+ infeasible_brush = QBrush(QColor(255, 199, 206)) # light red
257
+ row_color = feasible_brush if results["feasible"] else infeasible_brush
258
+ for col in range(self.columnCount()):
259
+ item = self.item(row_position, col)
260
+ if item:
261
+ item.setBackground(row_color)
262
+
263
+ # Scroll to the bottom to show the new results
264
+ self.scrollToBottom()
265
+
266
+ def on_selection_changed(self):
267
+ """Handle selection change in the table."""
268
+ current_row = self.currentRow()
269
+ if current_row >= 0 and self.last_results:
270
+ if current_row < 1:
271
+ selected_solution = self.last_results
272
+ self.solutionSelected.emit(selected_solution)
273
+
274
+ def clear_results(self):
275
+ """Clear all results from the table."""
276
+ self.setRowCount(0)
277
+ self.last_results = None
278
+
279
+
280
+ class HKLToAnglesResultsWidget(QWidget):
281
+ """Complete results widget with table and clear button."""
282
+
283
+ # Signal emitted when a solution is selected
284
+ solutionSelected = pyqtSignal(dict)
285
+
286
+ def __init__(self, parent=None):
287
+ super().__init__(parent)
288
+
289
+ # Main layout
290
+ layout = QVBoxLayout(self)
291
+
292
+ # Results group
293
+ results_group = QGroupBox("Results")
294
+ results_layout = QVBoxLayout(results_group)
295
+
296
+ # Create table
297
+ self.results_table = HKLToAnglesResultsTable(self)
298
+ self.results_table.solutionSelected.connect(self.solutionSelected.emit)
299
+ results_layout.addWidget(self.results_table)
300
+
301
+ # Add clear button
302
+ self.clear_button = QPushButton("Clear Results")
303
+ self.clear_button.clicked.connect(self.clear_results)
304
+ self.clear_button.setObjectName("clearButton")
305
+ results_layout.addWidget(self.clear_button)
306
+
307
+ layout.addWidget(results_group)
308
+
309
+ def display_results(self, results):
310
+ """Display calculation results."""
311
+ self.results_table.display_results(results)
312
+
313
+ def clear_results(self):
314
+ """Clear all results."""
315
+ self.results_table.clear_results()