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,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()
|