advisor-scattering 0.5.2__py3-none-any.whl → 0.5.3__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.
@@ -15,9 +15,12 @@ from PyQt5.QtWidgets import (
15
15
  QHeaderView,
16
16
  QButtonGroup,
17
17
  QMessageBox,
18
+ QDialog,
19
+ QScrollArea,
20
+ QFrame,
18
21
  )
19
22
  from PyQt5.QtCore import Qt, pyqtSignal
20
- from PyQt5.QtGui import QColor, QBrush
23
+ from PyQt5.QtGui import QColor, QBrush, QFont
21
24
 
22
25
 
23
26
  class HKAnglesControls(QWidget):
@@ -323,83 +326,102 @@ class HKAnglesControls(QWidget):
323
326
  }
324
327
 
325
328
 
326
- class HKAnglesResultsTable(QTableWidget):
327
- """Table to display HK to Angles calculation results."""
329
+ class HKAnglesHistoryDialog(QDialog):
330
+ """Dialog to display history of all HK angles calculation results."""
328
331
 
329
- # Signal emitted when a solution is selected
330
- solutionSelected = pyqtSignal(dict)
331
-
332
- def __init__(self, parent=None):
332
+ def __init__(self, history, parent=None):
333
333
  super().__init__(parent)
334
-
335
- # Set up table
336
- self.setColumnCount(4)
337
- self.setHorizontalHeaderLabels(["tth (°)", (°)", "φ (°)", "χ (°)"])
338
- self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
339
-
340
- # Hide vertical header (row numbers)
341
- self.verticalHeader().setVisible(False)
342
-
343
- # Connect selection change signal
344
- self.itemSelectionChanged.connect(self.on_selection_changed)
345
-
346
- # Store the last results for reference
347
- self.last_results = None
348
-
349
- def display_results(self, result):
350
- """Append calculation results to the table."""
351
- # Don't clear table - append new results
352
- self.last_results = result
353
-
354
- # Check if we have results
355
- if not result or not result.get("success", False):
356
- return
357
-
358
-
359
- # Add rows for each new solution
360
- row_position = self.rowCount()
361
- self.insertRow(row_position)
362
-
363
- # Add solution data
364
- self.setItem(row_position, 0, QTableWidgetItem(f"{result['tth']:.1f}"))
365
- self.setItem(row_position, 1, QTableWidgetItem(f"{result['theta']:.1f}"))
366
- self.setItem(row_position, 2, QTableWidgetItem(f"{result['phi']:.1f}"))
367
- self.setItem(row_position, 3, QTableWidgetItem(f"{result['chi']:.1f}"))
368
-
369
- # Highlight new solutions with light blue background
370
- feasible_brush = QBrush(QColor(198, 239, 206)) # light green
371
- infeasible_brush = QBrush(QColor(255, 199, 206)) # light red
372
- row_color = feasible_brush if result["feasible"] else infeasible_brush
373
- for col in range(self.columnCount()):
374
- item = self.item(row_position, col)
375
- if item:
376
- item.setBackground(row_color)
377
-
378
- # Scroll to the bottom to show the new results
379
- self.scrollToBottom()
380
-
381
- def on_selection_changed(self):
382
- """Handle selection change in the table."""
383
- current_row = self.currentRow()
384
- if current_row >= 0 and self.last_results:
385
- if current_row < 1:
386
- selected_solution = self.last_results
387
- self.solutionSelected.emit(selected_solution)
388
-
389
- def clear_results(self):
390
- """Clear all results from the table."""
391
- self.setRowCount(0)
392
- self.last_results = None
334
+ self.setWindowTitle("Calculation History (tth fixed)")
335
+ self.setMinimumSize(650, 400)
336
+
337
+ layout = QVBoxLayout(self)
338
+
339
+ # Create scroll area
340
+ scroll = QScrollArea()
341
+ scroll.setWidgetResizable(True)
342
+ scroll_content = QWidget()
343
+ scroll_layout = QVBoxLayout(scroll_content)
344
+
345
+ if not history:
346
+ no_history_label = QLabel("No history available.")
347
+ no_history_label.setAlignment(Qt.AlignCenter)
348
+ scroll_layout.addWidget(no_history_label)
349
+ else:
350
+ # Display history in reverse order (newest first)
351
+ for run_idx, entry in enumerate(reversed(history)):
352
+ run_num = len(history) - run_idx
353
+ run_frame = QFrame()
354
+ run_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
355
+ run_layout = QVBoxLayout(run_frame)
356
+
357
+ # Header with HKL values
358
+ header_label = QLabel(f"Run #{run_num}: H={entry['H']:.4f}, K={entry['K']:.4f}, L={entry['L']:.4f}")
359
+ header_font = QFont()
360
+ header_font.setBold(True)
361
+ header_label.setFont(header_font)
362
+ run_layout.addWidget(header_label)
363
+
364
+ # Number of solutions
365
+ num_solutions = entry.get('number_of_solutions', 1)
366
+ solutions_label = QLabel(f"Solutions found: {num_solutions}")
367
+ run_layout.addWidget(solutions_label)
368
+
369
+ # Create table for solutions
370
+ table = QTableWidget()
371
+ table.setColumnCount(5)
372
+ table.setHorizontalHeaderLabels(["#", "tth (°)", (°)", "φ (°)", "χ (°)"])
373
+ table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
374
+ table.verticalHeader().setVisible(False)
375
+ table.setMaximumHeight(100)
376
+
377
+ # Add rows for each solution
378
+ tth_list = entry.get('tth', [])
379
+ theta_list = entry.get('theta', [])
380
+ phi_list = entry.get('phi', [])
381
+ chi_list = entry.get('chi', [])
382
+ feasible_list = entry.get('feasible', [])
383
+
384
+ for i in range(len(tth_list)):
385
+ table.insertRow(i)
386
+ table.setItem(i, 0, QTableWidgetItem(f"{i + 1}"))
387
+ table.setItem(i, 1, QTableWidgetItem(f"{tth_list[i]:.2f}"))
388
+ table.setItem(i, 2, QTableWidgetItem(f"{theta_list[i]:.2f}"))
389
+ table.setItem(i, 3, QTableWidgetItem(f"{phi_list[i]:.2f}"))
390
+ table.setItem(i, 4, QTableWidgetItem(f"{chi_list[i]:.2f}"))
391
+
392
+ # Color based on feasibility
393
+ feasible = feasible_list[i] if i < len(feasible_list) else True
394
+ color = QColor(198, 239, 206) if feasible else QColor(255, 199, 206)
395
+ for col in range(5):
396
+ item = table.item(i, col)
397
+ if item:
398
+ item.setBackground(QBrush(color))
399
+
400
+ run_layout.addWidget(table)
401
+ scroll_layout.addWidget(run_frame)
402
+
403
+ scroll_layout.addStretch()
404
+ scroll.setWidget(scroll_content)
405
+ layout.addWidget(scroll)
406
+
407
+ # Close button
408
+ close_btn = QPushButton("Close")
409
+ close_btn.clicked.connect(self.accept)
410
+ layout.addWidget(close_btn)
393
411
 
394
412
 
395
413
  class HKAnglesResultsWidget(QWidget):
396
- """Complete results widget with table and clear button."""
414
+ """Results widget showing current solutions and history button."""
397
415
 
398
416
  # Signal emitted when a solution is selected
399
417
  solutionSelected = pyqtSignal(dict)
400
418
 
401
419
  def __init__(self, parent=None):
402
420
  super().__init__(parent)
421
+
422
+ # Store history of all results
423
+ self.history = []
424
+ self.current_result = None
403
425
 
404
426
  # Main layout
405
427
  layout = QVBoxLayout(self)
@@ -408,23 +430,97 @@ class HKAnglesResultsWidget(QWidget):
408
430
  results_group = QGroupBox("Results")
409
431
  results_layout = QVBoxLayout(results_group)
410
432
 
411
- # Create table
412
- self.results_table = HKAnglesResultsTable(self)
413
- self.results_table.solutionSelected.connect(self.solutionSelected.emit)
433
+ # Number of solutions label
434
+ self.solutions_label = QLabel("No results yet")
435
+ self.solutions_label.setAlignment(Qt.AlignCenter)
436
+ results_layout.addWidget(self.solutions_label)
437
+
438
+ # Current solutions display (table for up to 2 solutions)
439
+ self.results_table = QTableWidget()
440
+ self.results_table.setColumnCount(4)
441
+ self.results_table.setHorizontalHeaderLabels(["tth (°)", "θ (°)", "φ (°)", "χ (°)"])
442
+ self.results_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
443
+ self.results_table.verticalHeader().setVisible(False)
444
+ self.results_table.setMaximumHeight(90)
445
+ self.results_table.itemSelectionChanged.connect(self._on_selection_changed)
414
446
  results_layout.addWidget(self.results_table)
415
447
 
416
- # Add clear button
417
- self.clear_button = QPushButton("Clear Results")
418
- self.clear_button.clicked.connect(self.clear_results)
419
- self.clear_button.setObjectName("clearButton")
420
- results_layout.addWidget(self.clear_button)
448
+ # Show history button
449
+ self.history_button = QPushButton("Show History")
450
+ self.history_button.clicked.connect(self._show_history)
451
+ self.history_button.setObjectName("historyButton")
452
+ results_layout.addWidget(self.history_button)
421
453
 
422
454
  layout.addWidget(results_group)
423
455
 
424
456
  def display_results(self, results):
425
- """Display calculation results."""
426
- self.results_table.display_results(results)
457
+ """Display current calculation results and add to history."""
458
+ if not results or not results.get("success", False):
459
+ return
460
+
461
+ self.current_result = results
462
+
463
+ # Add to history
464
+ self.history.append(results)
465
+
466
+ # Update solutions label
467
+ num_solutions = results.get('number_of_solutions', 1)
468
+ self.solutions_label.setText(f"Found {num_solutions} solution(s)")
469
+
470
+ # Clear and populate table
471
+ self.results_table.setRowCount(0)
472
+
473
+ tth_list = results.get('tth', [])
474
+ theta_list = results.get('theta', [])
475
+ phi_list = results.get('phi', [])
476
+ chi_list = results.get('chi', [])
477
+ feasible_list = results.get('feasible', [])
478
+
479
+ for i in range(len(tth_list)):
480
+ row = self.results_table.rowCount()
481
+ self.results_table.insertRow(row)
482
+
483
+ self.results_table.setItem(row, 0, QTableWidgetItem(f"{tth_list[i]:.2f}"))
484
+ self.results_table.setItem(row, 1, QTableWidgetItem(f"{theta_list[i]:.2f}"))
485
+ self.results_table.setItem(row, 2, QTableWidgetItem(f"{phi_list[i]:.2f}"))
486
+ self.results_table.setItem(row, 3, QTableWidgetItem(f"{chi_list[i]:.2f}"))
487
+
488
+ # Color based on feasibility
489
+ feasible = feasible_list[i] if i < len(feasible_list) else True
490
+ color = QColor(198, 239, 206) if feasible else QColor(255, 199, 206)
491
+ for col in range(4):
492
+ item = self.results_table.item(row, col)
493
+ if item:
494
+ item.setBackground(QBrush(color))
495
+
496
+ def _on_selection_changed(self):
497
+ """Handle selection change in the table."""
498
+ current_row = self.results_table.currentRow()
499
+ if current_row >= 0 and self.current_result:
500
+ tth_list = self.current_result.get('tth', [])
501
+ theta_list = self.current_result.get('theta', [])
502
+ phi_list = self.current_result.get('phi', [])
503
+ chi_list = self.current_result.get('chi', [])
504
+
505
+ if current_row < len(tth_list):
506
+ selected_solution = {
507
+ 'tth': tth_list[current_row],
508
+ 'theta': theta_list[current_row],
509
+ 'phi': phi_list[current_row],
510
+ 'chi': chi_list[current_row],
511
+ 'H': self.current_result.get('H'),
512
+ 'K': self.current_result.get('K'),
513
+ 'L': self.current_result.get('L'),
514
+ }
515
+ self.solutionSelected.emit(selected_solution)
516
+
517
+ def _show_history(self):
518
+ """Show history dialog."""
519
+ dialog = HKAnglesHistoryDialog(self.history, self)
520
+ dialog.exec_()
427
521
 
428
522
  def clear_results(self):
429
- """Clear all results."""
430
- self.results_table.clear_results()
523
+ """Clear current results (but keep history)."""
524
+ self.results_table.setRowCount(0)
525
+ self.solutions_label.setText("No results yet")
526
+ self.current_result = None
@@ -373,25 +373,29 @@ class HKLScanControls(QWidget):
373
373
 
374
374
 
375
375
  class HKLScanResultsTable(QTableWidget):
376
- """Table to display HKL scan results with multiple solutions."""
376
+ """Table to display HKL scan results with multiple solutions per HKL point."""
377
377
 
378
378
  def __init__(self, parent=None):
379
379
  super().__init__(parent)
380
380
 
381
- # Set up table
382
- self.setColumnCount(7)
383
- self.setHorizontalHeaderLabels(["H", "K", "L", "θ (°)", "φ (°)", "χ (°)", "β (°)"])
381
+ # Set up table - added Sol# column to show solution 1 or 2
382
+ self.setColumnCount(8)
383
+ self.setHorizontalHeaderLabels(["H", "K", "L", "Sol#", "θ (°)", "φ (°)", "χ (°)", "β (°)"])
384
384
  self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
385
385
 
386
386
  # Hide vertical header (row numbers)
387
387
  self.verticalHeader().setVisible(False)
388
388
 
389
- # Define colors for alternating groups white and gray
389
+ # Define colors for alternating HKL groups and solutions
390
390
  self.group_colors = [
391
- QColor(255, 255, 255), # White
392
- QColor(230, 230, 240), # Gray
393
- QColor(166, 45, 45), # Red
391
+ QColor(255, 255, 255), # White - group 0
392
+ QColor(230, 230, 240), # Light gray - group 1
394
393
  ]
394
+ self.solution_colors = [
395
+ QColor(240, 248, 255), # Alice blue - solution 1
396
+ QColor(255, 250, 240), # Floral white - solution 2
397
+ ]
398
+ self.infeasible_color = QColor(255, 199, 206) # Light red
395
399
 
396
400
  # Enable sorting
397
401
  self.setSortingEnabled(True)
@@ -411,7 +415,7 @@ class HKLScanResultsTable(QTableWidget):
411
415
  self.last_results = None
412
416
 
413
417
  def display_results(self, results):
414
- """Display results in the table."""
418
+ """Display results in the table with visual separation for solution groups."""
415
419
  self.setSortingEnabled(False) # Temporarily disable sorting
416
420
  self.setRowCount(0) # Clear table
417
421
 
@@ -423,6 +427,7 @@ class HKLScanResultsTable(QTableWidget):
423
427
 
424
428
  # Store results for later export
425
429
  self.last_results = results
430
+
426
431
  # Get data from results
427
432
  h_values = results["H"]
428
433
  k_values = results["K"]
@@ -431,8 +436,11 @@ class HKLScanResultsTable(QTableWidget):
431
436
  theta_values = results["theta"]
432
437
  phi_values = results["phi"]
433
438
  chi_values = results["chi"]
439
+ feasible_values = results.get("feasible", [True] * len(h_values))
440
+ solution_groups = results.get("solution_group", list(range(len(h_values))))
441
+ solution_indices = results.get("solution_index", [1] * len(h_values))
434
442
 
435
- # Add a row for each result with alternating colors
443
+ # Add a row for each result
436
444
  for i in range(len(h_values)):
437
445
  row_position = self.rowCount()
438
446
  self.insertRow(row_position)
@@ -441,15 +449,25 @@ class HKLScanResultsTable(QTableWidget):
441
449
  self.setItem(row_position, 0, QTableWidgetItem(f"{h_values[i]:.4f}"))
442
450
  self.setItem(row_position, 1, QTableWidgetItem(f"{k_values[i]:.4f}"))
443
451
  self.setItem(row_position, 2, QTableWidgetItem(f"{l_values[i]:.4f}"))
452
+
453
+ # Add solution number
454
+ sol_idx = solution_indices[i] if i < len(solution_indices) else 1
455
+ self.setItem(row_position, 3, QTableWidgetItem(f"{sol_idx}"))
444
456
 
445
457
  # Add angle values
446
- self.setItem(row_position, 3, QTableWidgetItem(f"{theta_values[i]:.1f}"))
447
- self.setItem(row_position, 4, QTableWidgetItem(f"{phi_values[i]:.1f}"))
448
- self.setItem(row_position, 5, QTableWidgetItem(f"{chi_values[i]:.1f}"))
449
- self.setItem(row_position, 6, QTableWidgetItem(f"{tth_values[0]-theta_values[i]:.1f}"))
450
-
451
- # Apply alternating row colors
452
- row_color = self.group_colors[i % 2] if results["feasible"][i] else self.group_colors[2]
458
+ self.setItem(row_position, 4, QTableWidgetItem(f"{theta_values[i]:.1f}"))
459
+ self.setItem(row_position, 5, QTableWidgetItem(f"{phi_values[i]:.1f}"))
460
+ self.setItem(row_position, 6, QTableWidgetItem(f"{chi_values[i]:.1f}"))
461
+ self.setItem(row_position, 7, QTableWidgetItem(f"{tth_values[0]-theta_values[i]:.1f}"))
462
+
463
+ # Determine row color based on feasibility and group
464
+ if not feasible_values[i]:
465
+ row_color = self.infeasible_color
466
+ else:
467
+ # Alternate colors by HKL group
468
+ group_idx = solution_groups[i] if i < len(solution_groups) else i
469
+ row_color = self.group_colors[group_idx % 2]
470
+
453
471
  for col in range(self.columnCount()):
454
472
  item = self.item(row_position, col)
455
473
  if item:
@@ -489,24 +507,31 @@ class HKLScanResultsTable(QTableWidget):
489
507
  "H",
490
508
  "K",
491
509
  "L",
510
+ "Solution#",
492
511
  "tth (deg)",
493
512
  "theta (deg)",
494
513
  "phi (deg)",
495
514
  "chi (deg)",
515
+ "feasible",
496
516
  ]
497
517
  )
498
518
 
499
519
  # Write data
520
+ solution_indices = self.last_results.get("solution_index", [1] * len(self.last_results["tth"]))
521
+ feasible_values = self.last_results.get("feasible", [True] * len(self.last_results["tth"]))
522
+
500
523
  for i in range(len(self.last_results["tth"])):
501
524
  writer.writerow(
502
525
  [
503
526
  f"{self.last_results['H'][i]:.6f}",
504
527
  f"{self.last_results['K'][i]:.6f}",
505
528
  f"{self.last_results['L'][i]:.6f}",
529
+ f"{solution_indices[i]}",
506
530
  f"{self.last_results['tth'][i]:.6f}",
507
531
  f"{self.last_results['theta'][i]:.6f}",
508
532
  f"{self.last_results['phi'][i]:.6f}",
509
533
  f"{self.last_results['chi'][i]:.6f}",
534
+ f"{feasible_values[i]}",
510
535
  ]
511
536
  )
512
537