Skip to content

Commit f601993

Browse files
committed
Fixes multiphase fit and adds test
1 parent 1190b17 commit f601993

File tree

6 files changed

+1033
-17
lines changed

6 files changed

+1033
-17
lines changed

.github/workflows/testing-code.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262

6363
- name: Install Python dependencies
6464
shell: bash
65-
run: python -m pip install -r requirements.txt
65+
run: python -m pip install .
6666

6767
- name: Run Python unit tests
6868
shell: bash

src/easydiffraction/analysis/calculators/calculator_cryspy.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
from .calculator_base import CalculatorBase
55
from easydiffraction.utils.formatting import warning
66

7-
from easydiffraction.sample_models.sample_models import SampleModels
8-
from easydiffraction.experiments.experiments import Experiments
7+
from easydiffraction.sample_models.sample_model import SampleModel
98
from easydiffraction.experiments.experiment import Experiment
109

1110
try:
@@ -33,19 +32,21 @@ def __init__(self) -> None:
3332
super().__init__()
3433
self._cryspy_dicts: Dict[str, Dict[str, Any]] = {}
3534

36-
def calculate_structure_factors(self, sample_models: SampleModels, experiments: Experiments) -> None:
35+
def calculate_structure_factors(self,
36+
sample_model: SampleModel,
37+
experiment: Experiment) -> None:
3738
"""
3839
Raises a NotImplementedError as HKL calculation is not implemented.
3940
4041
Args:
41-
sample_models: The sample models to calculate structure factors for.
42-
experiments: The experiments associated with the sample models.
42+
sample_model: The sample model to calculate structure factors for.
43+
experiment: The experiment associated with the sample models.
4344
"""
4445
raise NotImplementedError("HKL calculation is not implemented for CryspyCalculator.")
4546

4647
def _calculate_single_model_pattern(
4748
self,
48-
sample_model: SampleModels,
49+
sample_model: SampleModel,
4950
experiment: Experiment,
5051
called_by_minimizer: bool = False
5152
) -> Union[np.ndarray, List[float]]:
@@ -65,8 +66,10 @@ def _calculate_single_model_pattern(
6566
Returns:
6667
The calculated diffraction pattern as a NumPy array or a list of floats.
6768
"""
69+
combined_name = f"{sample_model.name}_{experiment.name}"
70+
6871
if called_by_minimizer:
69-
if self._cryspy_dicts and experiment.name in self._cryspy_dicts:
72+
if self._cryspy_dicts and combined_name in self._cryspy_dicts:
7073
cryspy_dict = self._recreate_cryspy_dict(sample_model, experiment)
7174
else:
7275
cryspy_obj = self._recreate_cryspy_obj(sample_model, experiment)
@@ -75,7 +78,7 @@ def _calculate_single_model_pattern(
7578
cryspy_obj = self._recreate_cryspy_obj(sample_model, experiment)
7679
cryspy_dict = cryspy_obj.get_dictionary()
7780

78-
self._cryspy_dicts[experiment.name] = copy.deepcopy(cryspy_dict)
81+
self._cryspy_dicts[combined_name] = copy.deepcopy(cryspy_dict)
7982

8083
cryspy_in_out_dict: Dict[str, Any] = {}
8184
rhochi_calc_chi_sq_by_dictionary(
@@ -99,14 +102,16 @@ def _calculate_single_model_pattern(
99102
try:
100103
signal_plus = cryspy_in_out_dict[cryspy_block_name]['signal_plus']
101104
signal_minus = cryspy_in_out_dict[cryspy_block_name]['signal_minus']
102-
y_calc_total = signal_plus + signal_minus
105+
y_calc = signal_plus + signal_minus
103106
except KeyError:
104107
print(f"[CryspyCalculator] Error: No calculated data for {cryspy_block_name}")
105108
return []
106109

107-
return y_calc_total
110+
return y_calc
108111

109-
def _recreate_cryspy_dict(self, sample_model: SampleModels, experiment: Experiment) -> Dict[str, Any]:
112+
def _recreate_cryspy_dict(self,
113+
sample_model: SampleModel,
114+
experiment: Experiment) -> Dict[str, Any]:
110115
"""
111116
Recreates the Cryspy dictionary for the given sample model and experiment.
112117
@@ -117,7 +122,8 @@ def _recreate_cryspy_dict(self, sample_model: SampleModels, experiment: Experime
117122
Returns:
118123
The updated Cryspy dictionary.
119124
"""
120-
cryspy_dict = copy.deepcopy(self._cryspy_dicts[experiment.name])
125+
combined_name = f"{sample_model.name}_{experiment.name}"
126+
cryspy_dict = copy.deepcopy(self._cryspy_dicts[combined_name])
121127

122128
cryspy_model_id = f'crystal_{sample_model.name}'
123129
cryspy_model_dict = cryspy_dict[cryspy_model_id]
@@ -185,7 +191,9 @@ def _recreate_cryspy_dict(self, sample_model: SampleModels, experiment: Experime
185191

186192
return cryspy_dict
187193

188-
def _recreate_cryspy_obj(self, sample_model: SampleModels, experiment: Experiment) -> Any:
194+
def _recreate_cryspy_obj(self,
195+
sample_model: SampleModel,
196+
experiment: Experiment) -> Any:
189197
"""
190198
Recreates the Cryspy object for the given sample model and experiment.
191199
@@ -212,7 +220,8 @@ def _recreate_cryspy_obj(self, sample_model: SampleModels, experiment: Experimen
212220

213221
return cryspy_obj
214222

215-
def _convert_sample_model_to_cryspy_cif(self, sample_model: SampleModels) -> str:
223+
def _convert_sample_model_to_cryspy_cif(self,
224+
sample_model: SampleModel) -> str:
216225
"""
217226
Converts a sample model to a Cryspy CIF string.
218227
@@ -224,7 +233,9 @@ def _convert_sample_model_to_cryspy_cif(self, sample_model: SampleModels) -> str
224233
"""
225234
return sample_model.as_cif()
226235

227-
def _convert_experiment_to_cryspy_cif(self, experiment: Experiment, linked_phase: Any) -> str:
236+
def _convert_experiment_to_cryspy_cif(self,
237+
experiment: Experiment,
238+
linked_phase: Any) -> str:
228239
"""
229240
Converts an experiment to a Cryspy CIF string.
230241
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import os
2+
import tempfile
3+
from numpy.testing import assert_almost_equal
4+
5+
from easydiffraction import (
6+
Project,
7+
SampleModel,
8+
Experiment,
9+
download_from_repository
10+
)
11+
12+
TEMP_DIR = tempfile.gettempdir()
13+
14+
15+
def test_single_fit_neutron_pd_tof_mcstas_lbco_si() -> None:
16+
# Set sample models
17+
model_1 = SampleModel('lbco')
18+
model_1.space_group.name_h_m = 'P m -3 m'
19+
model_1.space_group.it_coordinate_system_code = '1'
20+
model_1.cell.length_a = 3.8909
21+
model_1.atom_sites.add('La', 'La', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5)
22+
model_1.atom_sites.add('Ba', 'Ba', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5)
23+
model_1.atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, wyckoff_letter='b', b_iso=0.2567)
24+
model_1.atom_sites.add('O', 'O', 0, 0.5, 0.5, wyckoff_letter='c', b_iso=1.4041)
25+
26+
model_2 = SampleModel('si')
27+
model_2.space_group.name_h_m = 'F d -3 m'
28+
model_2.space_group.it_coordinate_system_code = '2'
29+
model_2.cell.length_a = 5.43146
30+
model_2.atom_sites.add('Si', 'Si', 0.0, 0.0, 0.0, wyckoff_letter='a', b_iso=0.0)
31+
32+
# Set experiment
33+
data_file = 'mcstas_lbco-si.xys'
34+
download_from_repository(data_file, branch='fix-multiphase-fit', destination=TEMP_DIR)
35+
expt = Experiment('mcstas', beam_mode='time-of-flight', data_path=os.path.join(TEMP_DIR, data_file))
36+
expt.instrument.setup_twotheta_bank = 94.90931761529106
37+
expt.instrument.calib_d_to_tof_offset = 0.0
38+
expt.instrument.calib_d_to_tof_linear = 58724.76869981215
39+
expt.instrument.calib_d_to_tof_quad = -0.00001
40+
expt.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'
41+
expt.peak.broad_gauss_sigma_0 = 45137
42+
expt.peak.broad_gauss_sigma_1 = -52394
43+
expt.peak.broad_gauss_sigma_2 = 22998
44+
expt.peak.broad_mix_beta_0 = 0.0055
45+
expt.peak.broad_mix_beta_1 = 0.0041
46+
expt.peak.asym_alpha_0 = 0.0
47+
expt.peak.asym_alpha_1 = 0.0097
48+
expt.linked_phases.add('lbco', scale=4.0)
49+
expt.linked_phases.add('si', scale=0.2)
50+
for x in range(45000, 115000, 5000):
51+
expt.background.add(x=x, y=0.2)
52+
53+
# Create project
54+
project = Project()
55+
project.sample_models.add(model_1)
56+
project.sample_models.add(model_2)
57+
project.experiments.add(expt)
58+
59+
# Prepare for fitting
60+
project.analysis.current_calculator = 'cryspy'
61+
project.analysis.current_minimizer = 'lmfit (leastsq)'
62+
63+
# Select fitting parameters
64+
model_1.cell.length_a.free = True
65+
model_1.atom_sites['La'].b_iso.free = True
66+
model_1.atom_sites['Ba'].b_iso.free = True
67+
model_1.atom_sites['Co'].b_iso.free = True
68+
model_1.atom_sites['O'].b_iso.free = True
69+
model_2.cell.length_a.free = True
70+
model_2.atom_sites['Si'].b_iso.free = True
71+
expt.linked_phases['lbco'].scale.free = True
72+
expt.linked_phases['si'].scale.free = True
73+
expt.peak.broad_gauss_sigma_0.free = True
74+
expt.peak.broad_gauss_sigma_1.free = True
75+
expt.peak.broad_gauss_sigma_2.free = True
76+
expt.peak.asym_alpha_1.free = True
77+
expt.peak.broad_mix_beta_0.free = True
78+
expt.peak.broad_mix_beta_1.free = True
79+
for point in expt.background:
80+
point.y.free = True
81+
82+
# Perform fit
83+
project.analysis.fit()
84+
85+
# Compare fit quality
86+
assert_almost_equal(project.analysis.fit_results.reduced_chi_square,
87+
desired=2.87,
88+
decimal=1)
89+
90+
91+
if __name__ == '__main__':
92+
test_single_fit_neutron_pd_tof_mcstas_lbco_si()

tests/unit_tests/analysis/calculators/test_calculator_cryspy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def test_calculate_single_model_pattern(mock_rhochi_calc, mock_sample_model, moc
6868
def test_recreate_cryspy_dict(mock_sample_model, mock_experiment):
6969
calculator = CryspyCalculator()
7070
calculator._cryspy_dicts = {
71-
"experiment1": {
71+
"sample1_experiment1": {
7272
"pd_experiment1": {
7373
"offset_ttheta": [0.1],
7474
"wavelength": [1.54],

0 commit comments

Comments
 (0)