advisor-scattering 0.5.2__tar.gz → 0.9.1__tar.gz
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_scattering-0.5.2/advisor_scattering.egg-info → advisor_scattering-0.9.1}/PKG-INFO +4 -12
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/README.md +3 -11
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/controllers/app_controller.py +2 -1
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/domain/__init__.py +4 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/domain/core/lab.py +9 -2
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/domain/core/lattice.py +2 -5
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/domain/core/sample.py +9 -2
- advisor_scattering-0.9.1/advisor/domain/orientation.py +219 -0
- advisor_scattering-0.9.1/advisor/domain/orientation_calculator.py +174 -0
- advisor_scattering-0.9.1/advisor/features/__init__.py +9 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/domain/brillouin_calculator.py +133 -63
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/domain/core.py +187 -134
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/ui/components/hk_angles_components.py +175 -79
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/ui/components/hkl_scan_components.py +42 -17
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/ui/components/hkl_to_angles_components.py +175 -79
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/ui/scattering_geometry_tab.py +57 -48
- advisor_scattering-0.9.1/advisor/ui/dialogs/__init__.py +7 -0
- advisor_scattering-0.9.1/advisor/ui/dialogs/diffraction_test_dialog.py +264 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/init_window.py +33 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1/advisor_scattering.egg-info}/PKG-INFO +4 -12
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor_scattering.egg-info/SOURCES.txt +4 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/pyproject.toml +1 -1
- advisor_scattering-0.5.2/advisor/features/__init__.py +0 -6
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/MANIFEST.in +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/__main__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/app.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/controllers/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/controllers/feature_controller.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/domain/core/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/domain/geometry.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/domain/unit_converter.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/controllers/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/controllers/scattering_geometry_controller.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/domain/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/ui/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/ui/components/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/scattering_geometry/ui/components/angles_to_hkl_components.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/structure_factor/controllers/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/structure_factor/controllers/structure_factor_controller.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/structure_factor/domain/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/structure_factor/domain/structure_factor_calculator.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/structure_factor/ui/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/structure_factor/ui/components/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/structure_factor/ui/components/customized_plane_components.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/structure_factor/ui/components/hkl_plane_components.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/features/structure_factor/ui/structure_factor_tab.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/config/app_config.json +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/config/tips.json +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/data/nacl.cif +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/icons/bz_caculator.jpg +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/icons/bz_calculator.png +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/icons/minus.svg +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/icons/placeholder.png +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/icons/plus.svg +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/icons/reset.png +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/icons/sf_calculator.jpg +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/icons/sf_calculator.png +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/icons.qrc +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/qss/styles.qss +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/resources/resources_rc.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/main_window.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/tab_interface.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/tips.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/utils/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/utils/readcif.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/visualizers/HKLScan2DVisualizer.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/visualizers/__init__.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/visualizers/coordinate_visualizer.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/visualizers/scattering_visualizer.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/visualizers/structure_factor_visualizer.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/visualizers/structure_factor_visualizer_2d.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor/ui/visualizers/unitcell_visualizer.py +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor_scattering.egg-info/dependency_links.txt +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor_scattering.egg-info/entry_points.txt +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor_scattering.egg-info/requires.txt +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/advisor_scattering.egg-info/top_level.txt +0 -0
- {advisor_scattering-0.5.2 → advisor_scattering-0.9.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: advisor-scattering
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: Advisor-Scattering: Advanced Visual X-ray Scattering Toolkit for Reciprocal-space visualization and calculation
|
|
5
5
|
Author: Xunyang Hong
|
|
6
6
|
License: MIT
|
|
@@ -42,25 +42,17 @@ or use the link below to view the demo video.
|
|
|
42
42
|
## Install
|
|
43
43
|
- Python 3.8+ with PyQt5, numpy, scipy, matplotlib, Dans_Diffraction (see `requirements.txt`).
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
It is recommened to install from PyPI:
|
|
46
46
|
```bash
|
|
47
47
|
pip install advisor-scattering
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
From source:
|
|
51
|
-
```bash
|
|
52
|
-
python -m venv .venv
|
|
53
|
-
source .venv/bin/activate # .venv\Scripts\activate on Windows
|
|
54
|
-
pip install -r requirements.txt
|
|
55
|
-
pip install .
|
|
56
|
-
```
|
|
57
|
-
|
|
58
50
|
## Run
|
|
59
51
|
```bash
|
|
60
|
-
advisor-scattering
|
|
61
|
-
# or
|
|
62
52
|
advisor
|
|
63
53
|
# or
|
|
54
|
+
advisor-scattering
|
|
55
|
+
# or
|
|
64
56
|
python -m advisor
|
|
65
57
|
```
|
|
66
58
|
|
|
@@ -20,25 +20,17 @@ or use the link below to view the demo video.
|
|
|
20
20
|
## Install
|
|
21
21
|
- Python 3.8+ with PyQt5, numpy, scipy, matplotlib, Dans_Diffraction (see `requirements.txt`).
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
It is recommened to install from PyPI:
|
|
24
24
|
```bash
|
|
25
25
|
pip install advisor-scattering
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
From source:
|
|
29
|
-
```bash
|
|
30
|
-
python -m venv .venv
|
|
31
|
-
source .venv/bin/activate # .venv\Scripts\activate on Windows
|
|
32
|
-
pip install -r requirements.txt
|
|
33
|
-
pip install .
|
|
34
|
-
```
|
|
35
|
-
|
|
36
28
|
## Run
|
|
37
29
|
```bash
|
|
38
|
-
advisor-scattering
|
|
39
|
-
# or
|
|
40
30
|
advisor
|
|
41
31
|
# or
|
|
32
|
+
advisor-scattering
|
|
33
|
+
# or
|
|
42
34
|
python -m advisor
|
|
43
35
|
```
|
|
44
36
|
|
|
@@ -6,7 +6,8 @@ from typing import Optional
|
|
|
6
6
|
|
|
7
7
|
from PyQt5.QtWidgets import QApplication
|
|
8
8
|
|
|
9
|
-
from advisor.features import ScatteringGeometryController
|
|
9
|
+
from advisor.features.scattering_geometry.controllers import ScatteringGeometryController
|
|
10
|
+
from advisor.features.structure_factor.controllers import StructureFactorController
|
|
10
11
|
from advisor.ui.init_window import InitWindow
|
|
11
12
|
from advisor.ui.main_window import MainWindow
|
|
12
13
|
|
|
@@ -10,6 +10,8 @@ from .geometry import (
|
|
|
10
10
|
lab_to_sample_conversion,
|
|
11
11
|
)
|
|
12
12
|
from .unit_converter import UnitConverter
|
|
13
|
+
from .orientation import fit_orientation_from_diffraction_tests
|
|
14
|
+
from .orientation_calculator import OrientationCalculator
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
15
17
|
"get_real_space_vectors",
|
|
@@ -20,4 +22,6 @@ __all__ = [
|
|
|
20
22
|
"sample_to_lab_conversion",
|
|
21
23
|
"lab_to_sample_conversion",
|
|
22
24
|
"UnitConverter",
|
|
25
|
+
"fit_orientation_from_diffraction_tests",
|
|
26
|
+
"OrientationCalculator",
|
|
23
27
|
]
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
5
|
from advisor.domain import angle_to_matrix
|
|
6
|
-
from .sample import Sample
|
|
7
6
|
|
|
7
|
+
from .sample import Sample
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Lab:
|
|
@@ -113,9 +113,16 @@ class Lab:
|
|
|
113
113
|
return ex_lattice_in_lab, ey_lattice_in_lab, ez_lattice_in_lab
|
|
114
114
|
|
|
115
115
|
def rotate(self, theta, phi, chi):
|
|
116
|
-
"""Rotate the lab
|
|
116
|
+
"""Rotate the lab / goniometer"""
|
|
117
117
|
self.theta = theta
|
|
118
118
|
self.phi = phi
|
|
119
119
|
self.chi = chi
|
|
120
120
|
self.calculate_real_space_vectors()
|
|
121
121
|
self.calculate_reciprocal_space_vectors()
|
|
122
|
+
|
|
123
|
+
def reorient(self,roll, pitch, yaw):
|
|
124
|
+
"""reorient the sample with respect to the lab"""
|
|
125
|
+
self.sample.reorient(roll, pitch, yaw)
|
|
126
|
+
self.calculate_real_space_vectors()
|
|
127
|
+
self.calculate_reciprocal_space_vectors()
|
|
128
|
+
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
|
-
from advisor.domain import
|
|
6
|
-
get_real_space_vectors,
|
|
7
|
-
get_reciprocal_space_vectors,
|
|
8
|
-
)
|
|
5
|
+
from advisor.domain import get_real_space_vectors, get_reciprocal_space_vectors
|
|
9
6
|
|
|
10
7
|
|
|
11
8
|
class Lattice:
|
|
@@ -31,7 +28,7 @@ class Lattice:
|
|
|
31
28
|
self.c_star_vec_lattice = None
|
|
32
29
|
|
|
33
30
|
def initialize(self, a, b, c, alpha, beta, gamma):
|
|
34
|
-
"""Initialize the
|
|
31
|
+
"""Initialize by calculating the real and reciprocal space vectors.
|
|
35
32
|
|
|
36
33
|
Args:
|
|
37
34
|
a, b, c (float): Lattice constants in Angstroms
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
5
|
from advisor.domain import euler_to_matrix
|
|
6
|
+
|
|
6
7
|
from .lattice import Lattice
|
|
7
8
|
|
|
9
|
+
|
|
8
10
|
class Sample:
|
|
9
11
|
"""This is a class for the sample."""
|
|
10
12
|
|
|
@@ -97,5 +99,10 @@ class Sample:
|
|
|
97
99
|
self.b_star_vec_sample = rotation_matrix @ b_star_vec_lattice
|
|
98
100
|
self.c_star_vec_sample = rotation_matrix @ c_star_vec_lattice
|
|
99
101
|
|
|
100
|
-
def
|
|
101
|
-
"""
|
|
102
|
+
def reorient(self,roll, pitch, yaw):
|
|
103
|
+
"""reorient the sample"""
|
|
104
|
+
self.roll = roll
|
|
105
|
+
self.pitch = pitch
|
|
106
|
+
self.yaw = yaw
|
|
107
|
+
self.calculate_real_space_vectors()
|
|
108
|
+
self.calculate_reciprocal_space_vectors()
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Orientation fitting from diffraction tests.
|
|
4
|
+
|
|
5
|
+
This module provides functions to determine the optimal Euler angles (roll, pitch, yaw)
|
|
6
|
+
that align a crystal lattice orientation with observed diffraction data.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy.optimize import minimize
|
|
11
|
+
|
|
12
|
+
from .orientation_calculator import OrientationCalculator
|
|
13
|
+
|
|
14
|
+
# Default number of random restarts for optimization
|
|
15
|
+
DEFAULT_N_RESTARTS = 20
|
|
16
|
+
# Target residual error - stop early if achieved
|
|
17
|
+
TARGET_RESIDUAL = 1e-10
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def fit_orientation_from_diffraction_tests(
|
|
21
|
+
lattice_params: dict,
|
|
22
|
+
diffraction_tests: list,
|
|
23
|
+
initial_guess: tuple = (0.0, 0.0, 0.0),
|
|
24
|
+
n_restarts: int = DEFAULT_N_RESTARTS,
|
|
25
|
+
) -> dict:
|
|
26
|
+
"""Fit crystal orientation from diffraction test data.
|
|
27
|
+
|
|
28
|
+
Given lattice parameters and a list of diffraction tests (each containing
|
|
29
|
+
known HKL values and measured angles), find the Euler angles (roll, pitch, yaw)
|
|
30
|
+
that best explain the observations.
|
|
31
|
+
|
|
32
|
+
Uses multiple random restarts to avoid local minima.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
lattice_params: Dictionary containing lattice parameters:
|
|
36
|
+
- a, b, c (float): Lattice constants in Angstroms
|
|
37
|
+
- alpha, beta, gamma (float): Lattice angles in degrees
|
|
38
|
+
diffraction_tests: List of dictionaries, each containing:
|
|
39
|
+
- H, K, L (float): Expected Miller indices
|
|
40
|
+
- energy (float): X-ray energy in eV
|
|
41
|
+
- tth (float): Scattering angle 2θ in degrees
|
|
42
|
+
- theta (float): Sample theta rotation in degrees
|
|
43
|
+
- phi (float): Sample phi rotation in degrees
|
|
44
|
+
- chi (float): Sample chi rotation in degrees
|
|
45
|
+
initial_guess: Initial guess for (roll, pitch, yaw) in degrees
|
|
46
|
+
n_restarts: Number of random restarts to try (default: 20)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
dict: Dictionary containing:
|
|
50
|
+
- roll, pitch, yaw (float): Optimized Euler angles in degrees
|
|
51
|
+
- residual_error (float): Final residual error (sum of squared HKL differences)
|
|
52
|
+
- individual_errors (list): Per-test HKL errors
|
|
53
|
+
- success (bool): Whether optimization converged
|
|
54
|
+
- message (str): Status message
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
if not diffraction_tests:
|
|
58
|
+
return {
|
|
59
|
+
"success": False,
|
|
60
|
+
"message": "No diffraction tests provided",
|
|
61
|
+
"roll": 0.0,
|
|
62
|
+
"pitch": 0.0,
|
|
63
|
+
"yaw": 0.0,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Validate diffraction tests
|
|
67
|
+
required_keys = ["H", "K", "L", "energy", "tth", "theta", "phi", "chi"]
|
|
68
|
+
for i, test in enumerate(diffraction_tests):
|
|
69
|
+
missing = [k for k in required_keys if k not in test]
|
|
70
|
+
if missing:
|
|
71
|
+
return {
|
|
72
|
+
"success": False,
|
|
73
|
+
"message": f"Test {i+1} is missing required keys: {missing}",
|
|
74
|
+
"roll": 0.0,
|
|
75
|
+
"pitch": 0.0,
|
|
76
|
+
"yaw": 0.0,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Initialize calculator with lattice parameters (using initial roll, pitch, yaw = 0)
|
|
80
|
+
calculator = OrientationCalculator()
|
|
81
|
+
init_params = {
|
|
82
|
+
"a": lattice_params["a"],
|
|
83
|
+
"b": lattice_params["b"],
|
|
84
|
+
"c": lattice_params["c"],
|
|
85
|
+
"alpha": lattice_params["alpha"],
|
|
86
|
+
"beta": lattice_params["beta"],
|
|
87
|
+
"gamma": lattice_params["gamma"],
|
|
88
|
+
"energy": diffraction_tests[0]["energy"], # Will be updated per test
|
|
89
|
+
"roll": 0.0,
|
|
90
|
+
"pitch": 0.0,
|
|
91
|
+
"yaw": 0.0,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if not calculator.initialize(init_params):
|
|
95
|
+
return {
|
|
96
|
+
"success": False,
|
|
97
|
+
"message": "Failed to initialize calculator with given lattice parameters",
|
|
98
|
+
"roll": 0.0,
|
|
99
|
+
"pitch": 0.0,
|
|
100
|
+
"yaw": 0.0,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def objective(params):
|
|
104
|
+
"""Objective function: sum of squared HKL errors."""
|
|
105
|
+
roll, pitch, yaw = params
|
|
106
|
+
calculator.reorient_sample(roll, pitch, yaw)
|
|
107
|
+
|
|
108
|
+
total_error = 0.0
|
|
109
|
+
for test in diffraction_tests:
|
|
110
|
+
# Update energy for this test
|
|
111
|
+
calculator.change_energy(test["energy"])
|
|
112
|
+
|
|
113
|
+
# Calculate HKL from angles
|
|
114
|
+
result = calculator.calculate_hkl(
|
|
115
|
+
test["tth"], test["theta"], test["phi"], test["chi"]
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Compute squared error
|
|
119
|
+
dH = result["H"] - test["H"]
|
|
120
|
+
dK = result["K"] - test["K"]
|
|
121
|
+
dL = result["L"] - test["L"]
|
|
122
|
+
total_error += dH**2 + dK**2 + dL**2
|
|
123
|
+
|
|
124
|
+
return total_error
|
|
125
|
+
|
|
126
|
+
def run_optimization(start_point):
|
|
127
|
+
"""Run a single optimization from a starting point."""
|
|
128
|
+
return minimize(
|
|
129
|
+
objective,
|
|
130
|
+
start_point,
|
|
131
|
+
method="L-BFGS-B",
|
|
132
|
+
bounds=[(-180, 180), (-180, 180), (-180, 180)],
|
|
133
|
+
options={"ftol": 1e-12, "gtol": 1e-10, "maxiter": 1000},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Multiple random restarts to find global minimum
|
|
137
|
+
best_result = None
|
|
138
|
+
best_error = float("inf")
|
|
139
|
+
|
|
140
|
+
# First try the user-provided initial guess
|
|
141
|
+
initial_points = [initial_guess]
|
|
142
|
+
|
|
143
|
+
# Add random starting points
|
|
144
|
+
rng = np.random.default_rng(seed=42) # Reproducible results
|
|
145
|
+
for _ in range(n_restarts - 1):
|
|
146
|
+
# Random angles in [-180, 180]
|
|
147
|
+
random_point = tuple(rng.uniform(-180, 180, 3))
|
|
148
|
+
initial_points.append(random_point)
|
|
149
|
+
|
|
150
|
+
# Also add some structured starting points
|
|
151
|
+
structured_points = [
|
|
152
|
+
(0, 0, 0),
|
|
153
|
+
(90, 0, 0), (-90, 0, 0),
|
|
154
|
+
(0, 90, 0), (0, -90, 0),
|
|
155
|
+
(0, 0, 90), (0, 0, -90),
|
|
156
|
+
]
|
|
157
|
+
for pt in structured_points:
|
|
158
|
+
if pt not in initial_points:
|
|
159
|
+
initial_points.append(pt)
|
|
160
|
+
|
|
161
|
+
# Run optimization from each starting point
|
|
162
|
+
for start_point in initial_points:
|
|
163
|
+
try:
|
|
164
|
+
result = run_optimization(start_point)
|
|
165
|
+
if result.fun < best_error:
|
|
166
|
+
best_error = result.fun
|
|
167
|
+
best_result = result
|
|
168
|
+
|
|
169
|
+
# Early stopping if we found a very good solution
|
|
170
|
+
if best_error < TARGET_RESIDUAL:
|
|
171
|
+
break
|
|
172
|
+
except Exception:
|
|
173
|
+
# Skip failed optimizations
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
if best_result is None:
|
|
177
|
+
return {
|
|
178
|
+
"success": False,
|
|
179
|
+
"message": "All optimization attempts failed",
|
|
180
|
+
"roll": 0.0,
|
|
181
|
+
"pitch": 0.0,
|
|
182
|
+
"yaw": 0.0,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Extract optimized parameters
|
|
186
|
+
roll_opt, pitch_opt, yaw_opt = best_result.x
|
|
187
|
+
|
|
188
|
+
# Calculate individual errors at optimal orientation
|
|
189
|
+
calculator.reorient_sample(roll_opt, pitch_opt, yaw_opt)
|
|
190
|
+
individual_errors = []
|
|
191
|
+
for test in diffraction_tests:
|
|
192
|
+
calculator.change_energy(test["energy"])
|
|
193
|
+
calc_result = calculator.calculate_hkl(
|
|
194
|
+
test["tth"], test["theta"], test["phi"], test["chi"]
|
|
195
|
+
)
|
|
196
|
+
error = {
|
|
197
|
+
"H_expected": test["H"],
|
|
198
|
+
"K_expected": test["K"],
|
|
199
|
+
"L_expected": test["L"],
|
|
200
|
+
"H_calculated": calc_result["H"],
|
|
201
|
+
"K_calculated": calc_result["K"],
|
|
202
|
+
"L_calculated": calc_result["L"],
|
|
203
|
+
"H_error": calc_result["H"] - test["H"],
|
|
204
|
+
"K_error": calc_result["K"] - test["K"],
|
|
205
|
+
"L_error": calc_result["L"] - test["L"],
|
|
206
|
+
}
|
|
207
|
+
individual_errors.append(error)
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
"success": best_result.success,
|
|
211
|
+
"message": best_result.message if hasattr(best_result, "message") else "Optimization completed",
|
|
212
|
+
"roll": float(roll_opt),
|
|
213
|
+
"pitch": float(pitch_opt),
|
|
214
|
+
"yaw": float(yaw_opt),
|
|
215
|
+
"residual_error": float(best_result.fun),
|
|
216
|
+
"individual_errors": individual_errors,
|
|
217
|
+
"n_iterations": best_result.nit if hasattr(best_result, "nit") else None,
|
|
218
|
+
"n_restarts_used": len(initial_points),
|
|
219
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Lightweight calculator for orientation fitting.
|
|
4
|
+
|
|
5
|
+
This module provides a minimal calculator class that can compute HKL values
|
|
6
|
+
from scattering angles, used specifically for orientation fitting.
|
|
7
|
+
It avoids importing from feature modules to prevent circular dependencies.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from advisor.domain import angle_to_matrix
|
|
13
|
+
from advisor.domain.core import Lab
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OrientationCalculator:
|
|
17
|
+
"""Lightweight calculator for orientation fitting.
|
|
18
|
+
|
|
19
|
+
This class provides only the methods needed for fitting crystal orientation
|
|
20
|
+
from diffraction test data. It uses the Lab class directly and avoids
|
|
21
|
+
dependencies on feature-specific modules.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Physical constants
|
|
25
|
+
EV_TO_LAMBDA = 12398.42 # eV to Angstrom conversion
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
"""Initialize the calculator."""
|
|
29
|
+
self._initialized = False
|
|
30
|
+
self.lab = Lab()
|
|
31
|
+
self.energy = None
|
|
32
|
+
self.lambda_A = None
|
|
33
|
+
self.k_in = None
|
|
34
|
+
|
|
35
|
+
def initialize(self, params: dict) -> bool:
|
|
36
|
+
"""Initialize with lattice parameters.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
params: Dictionary containing:
|
|
40
|
+
- a, b, c (float): Lattice constants in Angstroms
|
|
41
|
+
- alpha, beta, gamma (float): Lattice angles in degrees
|
|
42
|
+
- energy (float): X-ray energy in eV
|
|
43
|
+
- roll, pitch, yaw (float, optional): Euler angles in degrees
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
bool: True if initialization was successful
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
a = params.get("a", 4.0)
|
|
50
|
+
b = params.get("b", 4.0)
|
|
51
|
+
c = params.get("c", 12.0)
|
|
52
|
+
alpha = params.get("alpha", 90.0)
|
|
53
|
+
beta = params.get("beta", 90.0)
|
|
54
|
+
gamma = params.get("gamma", 90.0)
|
|
55
|
+
roll = params.get("roll", 0.0)
|
|
56
|
+
pitch = params.get("pitch", 0.0)
|
|
57
|
+
yaw = params.get("yaw", 0.0)
|
|
58
|
+
self.energy = params["energy"]
|
|
59
|
+
|
|
60
|
+
# Initialize lab with default sample rotation (0, 0, 0)
|
|
61
|
+
self.lab.initialize(a, b, c, alpha, beta, gamma, roll, pitch, yaw, 0, 0, 0)
|
|
62
|
+
|
|
63
|
+
# Calculate wavelength and wavevector
|
|
64
|
+
self.lambda_A = self.EV_TO_LAMBDA / self.energy
|
|
65
|
+
self.k_in = 2 * np.pi / self.lambda_A
|
|
66
|
+
|
|
67
|
+
self._initialized = True
|
|
68
|
+
return True
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"Error initializing OrientationCalculator: {e}")
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def change_energy(self, energy: float) -> bool:
|
|
74
|
+
"""Change the X-ray energy.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
energy: X-ray energy in eV
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
bool: True if successful
|
|
81
|
+
"""
|
|
82
|
+
self.energy = energy
|
|
83
|
+
self.lambda_A = self.EV_TO_LAMBDA / self.energy
|
|
84
|
+
self.k_in = 2 * np.pi / self.lambda_A
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
def reorient_sample(self, roll: float, pitch: float, yaw: float) -> bool:
|
|
88
|
+
"""Reorient the sample (change Euler angles).
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
roll, pitch, yaw: Euler angles in degrees
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
bool: True if successful
|
|
95
|
+
"""
|
|
96
|
+
self.lab.reorient(roll, pitch, yaw)
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
def calculate_hkl(
|
|
100
|
+
self, tth: float, theta: float, phi: float, chi: float
|
|
101
|
+
) -> dict:
|
|
102
|
+
"""Calculate HKL from scattering angles.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
tth: Scattering angle 2θ in degrees
|
|
106
|
+
theta: Sample theta rotation in degrees
|
|
107
|
+
phi: Sample phi rotation in degrees
|
|
108
|
+
chi: Sample chi rotation in degrees
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
dict: Dictionary containing:
|
|
112
|
+
- H, K, L (float): Miller indices
|
|
113
|
+
- tth, theta, phi, chi (float): Input angles
|
|
114
|
+
- success (bool): Whether calculation succeeded
|
|
115
|
+
- error (str or None): Error message if any
|
|
116
|
+
"""
|
|
117
|
+
if not self._initialized:
|
|
118
|
+
return {
|
|
119
|
+
"H": None, "K": None, "L": None,
|
|
120
|
+
"tth": tth, "theta": theta, "phi": phi, "chi": chi,
|
|
121
|
+
"success": False,
|
|
122
|
+
"error": "Calculator not initialized",
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
# Get real space vectors in lab frame
|
|
127
|
+
a_vec_lab, b_vec_lab, c_vec_lab = self.lab.get_real_space_vectors()
|
|
128
|
+
|
|
129
|
+
# Calculate momentum transfer magnitude
|
|
130
|
+
k_magnitude = 2.0 * self.k_in * np.sin(np.radians(tth / 2.0))
|
|
131
|
+
|
|
132
|
+
# Calculate delta angle
|
|
133
|
+
delta = 90 - (tth / 2.0)
|
|
134
|
+
sin_delta = np.sin(np.radians(delta))
|
|
135
|
+
cos_delta = np.cos(np.radians(delta))
|
|
136
|
+
|
|
137
|
+
# Momentum transfer at theta, phi, chi = 0
|
|
138
|
+
k_vec_initial = np.array(
|
|
139
|
+
[-k_magnitude * sin_delta, -k_magnitude * cos_delta, 0.0]
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Rotation of the beam is the reverse rotation of the sample
|
|
143
|
+
rotation_matrix = angle_to_matrix(theta, phi, chi).T
|
|
144
|
+
|
|
145
|
+
# Momentum transfer at the given angles
|
|
146
|
+
k_vec_lab = rotation_matrix @ k_vec_initial
|
|
147
|
+
|
|
148
|
+
# Calculate HKL by projecting onto real space vectors
|
|
149
|
+
H = np.dot(k_vec_lab, a_vec_lab) / (2 * np.pi)
|
|
150
|
+
K = np.dot(k_vec_lab, b_vec_lab) / (2 * np.pi)
|
|
151
|
+
L = np.dot(k_vec_lab, c_vec_lab) / (2 * np.pi)
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
"H": H,
|
|
155
|
+
"K": K,
|
|
156
|
+
"L": L,
|
|
157
|
+
"tth": tth,
|
|
158
|
+
"theta": theta,
|
|
159
|
+
"phi": phi,
|
|
160
|
+
"chi": chi,
|
|
161
|
+
"success": True,
|
|
162
|
+
"error": None,
|
|
163
|
+
}
|
|
164
|
+
except Exception as e:
|
|
165
|
+
return {
|
|
166
|
+
"H": None, "K": None, "L": None,
|
|
167
|
+
"tth": tth, "theta": theta, "phi": phi, "chi": chi,
|
|
168
|
+
"success": False,
|
|
169
|
+
"error": str(e),
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
def is_initialized(self) -> bool:
|
|
173
|
+
"""Check if the calculator is initialized."""
|
|
174
|
+
return self._initialized
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Feature packages.
|
|
2
|
+
|
|
3
|
+
Note: Controllers are not exported here to avoid circular imports.
|
|
4
|
+
Import them directly from their modules:
|
|
5
|
+
from advisor.features.scattering_geometry.controllers import ScatteringGeometryController
|
|
6
|
+
from advisor.features.structure_factor.controllers import StructureFactorController
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__all__ = []
|