diff --git a/hexrdgui/calibration/wppf_options_dialog.py b/hexrdgui/calibration/wppf_options_dialog.py
index 9d5cb77dc..df6ca4ef7 100644
--- a/hexrdgui/calibration/wppf_options_dialog.py
+++ b/hexrdgui/calibration/wppf_options_dialog.py
@@ -45,7 +45,7 @@
from hexrd.wppf.tds import (
VALID_SGNUMS as VALID_TDS_SGNUMS,
)
-from hexrd.wppf.texture import HarmonicModel
+from hexrd.wppf.texture import HarmonicModel, MarchDollaseModel
from hexrd.wppf.WPPF import peakshape_dict
from hexrd.wppf.wppfsupport import (
_generate_default_parameters_LeBail,
@@ -81,6 +81,13 @@
DEFAULT_PEAK_SHAPE = 'pvtch'
+AXIS_DISTRIBUTION_MODEL = 'axis_distribution'
+MARCH_DOLLASE_MODEL = 'march_dollase'
+TEXTURE_MODEL_LABELS = {
+ AXIS_DISTRIBUTION_MODEL: 'General Axis Distribution Function',
+ MARCH_DOLLASE_MODEL: 'March-Dollase',
+}
+
class WppfOptionsDialog(QObject):
run = Signal()
@@ -462,7 +469,7 @@ def begin_run(self) -> None:
self.save_settings()
self.push_undo_stack()
- if self.varying_texture_params:
+ if self.varying_harmonic_texture_params:
# Ensure texture data is set on the WPPF object.
# This might be time-consuming.
# We also have to ensure there is a WPPF object
@@ -515,8 +522,8 @@ def validate(self) -> None:
if self.varying_texture_and_non_texture_params:
msg = (
- 'Texture parameters cannot be varied at the same time as '
- 'non-texture parameters.'
+ 'Harmonic texture parameters cannot be varied at the same '
+ 'time as other parameters.'
)
raise Exception(msg)
@@ -1577,6 +1584,14 @@ def recursively_format_mat(
for mat_name in self.textured_materials:
# Look for param names that match
mat_name_sanitized = mat_name.replace('-', '_')
+ model_type = self.texture_model_type(mat_name)
+ if model_type == MARCH_DOLLASE_MODEL:
+ name = f'{mat_name_sanitized}_p_md'
+ if name in params:
+ mat_config = texture_dict.setdefault(mat_name, {})
+ mat_config['P_md'] = create_param_item(params[name])
+ continue
+
prefix = f'{mat_name_sanitized}_c_'
matching_names = [k for k in params if k.startswith(prefix)]
if not matching_names:
@@ -2114,8 +2129,18 @@ def update_undo_enable_state(self) -> None:
def texture_material_setting_widgets(self) -> list:
return [
self.ui.include_texture_model,
+ self.ui.texture_model_type,
self.ui.texture_ell_max,
self.ui.texture_sample_symmetry,
+ *self.texture_preferred_axis_widgets,
+ ]
+
+ @property
+ def texture_preferred_axis_widgets(self) -> list:
+ return [
+ self.ui.texture_preferred_axis_h,
+ self.ui.texture_preferred_axis_k,
+ self.ui.texture_preferred_axis_l,
]
@property
@@ -2155,8 +2180,23 @@ def update_texture_model_gui(self) -> None:
with block_signals(*self.texture_material_setting_widgets):
self.ui.include_texture_model.setChecked(checked)
- self.ui.texture_sample_symmetry.setCurrentText(settings['ssym'])
- self.ui.texture_ell_max.setValue(settings['ell_max'])
+ model_type = self.texture_model_type(mat_name)
+ self.ui.texture_model_type.setCurrentText(
+ TEXTURE_MODEL_LABELS.get(
+ model_type,
+ TEXTURE_MODEL_LABELS[AXIS_DISTRIBUTION_MODEL],
+ )
+ )
+ defaults = self._default_texture_model_settings
+ self.ui.texture_sample_symmetry.setCurrentText(
+ settings.get('ssym', defaults['ssym'])
+ )
+ self.ui.texture_ell_max.setValue(
+ settings.get('ell_max', defaults['ell_max'])
+ )
+ hkl = self.texture_preferred_axes.get(mat_name, [1, 1, 1])
+ for widget, value in zip(self.texture_preferred_axis_widgets, hkl):
+ widget.setValue(value)
self.update_texture_model_enable_states()
self.update_texture_index_label()
@@ -2175,17 +2215,42 @@ def update_texture_model_enable_states(self) -> None:
# Now enable/disable all the other widgets
enable = w.isChecked() and not has_object
- widgets = [
+ for child in [
+ self.ui.texture_model_type_label,
+ self.ui.texture_model_type,
+ ]:
+ child.setEnabled(enable)
+
+ is_march_dollase = self.current_texture_model_type == MARCH_DOLLASE_MODEL
+ harmonic_widgets = [
self.ui.texture_sample_symmetry_label,
self.ui.texture_sample_symmetry,
self.ui.texture_ell_max_label,
self.ui.texture_ell_max,
]
+ march_dollase_widgets = [
+ self.ui.texture_preferred_axis_label,
+ self.ui.texture_preferred_axis_h_label,
+ self.ui.texture_preferred_axis_k_label,
+ self.ui.texture_preferred_axis_l_label,
+ *self.texture_preferred_axis_widgets,
+ ]
- for w in widgets:
- w.setEnabled(enable)
+ for child in harmonic_widgets:
+ child.setVisible(not is_march_dollase)
+ child.setEnabled(enable)
- self.ui.spectrum_binning_group.setEnabled(is_rietveld)
+ for child in march_dollase_widgets:
+ child.setVisible(is_march_dollase)
+ child.setEnabled(enable)
+
+ only_march_dollase = self.includes_texture and all(
+ self.texture_model_type(name) == MARCH_DOLLASE_MODEL
+ for name in self.textured_materials
+ )
+ self.ui.spectrum_binning_group.setEnabled(
+ is_rietveld and not only_march_dollase
+ )
# Now figure out if we should enable pole figure plotting
self.ui.texture_plot_pole_figures.setEnabled(self.can_plot_pole_figures)
@@ -2194,28 +2259,58 @@ def on_texture_material_setting_changed(self) -> None:
mat_name = self.ui.selected_texture_material.currentText()
checked = self.ui.include_texture_model.isChecked()
model_kwargs = self.texture_model_kwargs
+ model_types = self.texture_model_types
+ preferred_axes = self.texture_preferred_axes
if not checked:
- if mat_name in model_kwargs:
- del model_kwargs[mat_name]
+ model_kwargs.pop(mat_name, None)
+ model_types.pop(mat_name, None)
+ preferred_axes.pop(mat_name, None)
else:
- ssym = self.ui.texture_sample_symmetry.currentText()
- # Force ell_max to be an even number
- ell_max = self.ui.texture_ell_max.value()
- if ell_max % 2 != 0:
- msg = 'Spherical harmonic max must be an even number'
- QMessageBox.critical(self.ui, 'HEXRD', msg)
- print(msg, file=sys.stderr)
-
- ell_max += 1
- w = self.ui.texture_ell_max
- with block_signals(w):
- w.setValue(ell_max)
-
- model_kwargs[mat_name] = {
- 'ssym': ssym,
- 'ell_max': ell_max,
- }
+ model_type = self.current_texture_model_type
+ model_types[mat_name] = model_type
+ if model_type == MARCH_DOLLASE_MODEL:
+ hkl = [widget.value() for widget in self.texture_preferred_axis_widgets]
+ if not any(hkl):
+ QMessageBox.critical(
+ self.ui,
+ 'Invalid Preferred Axis',
+ 'Preferred axis cannot be 0 0 0.',
+ )
+ with block_signals(*self.texture_preferred_axis_widgets):
+ for widget, value in zip(
+ self.texture_preferred_axis_widgets,
+ preferred_axes.get(mat_name, [1, 1, 1]),
+ ):
+ widget.setValue(value)
+ return
+
+ preferred_axes[mat_name] = hkl
+ model_kwargs.setdefault(
+ mat_name,
+ {
+ 'ssym': self._default_texture_model_settings['ssym'],
+ 'ell_max': self._default_texture_model_settings['ell_max'],
+ },
+ )
+ else:
+ ssym = self.ui.texture_sample_symmetry.currentText()
+ # Force ell_max to be an even number
+ ell_max = self.ui.texture_ell_max.value()
+ if ell_max % 2 != 0:
+ msg = 'Spherical harmonic max must be an even number'
+ QMessageBox.critical(self.ui, 'HEXRD', msg)
+ print(msg, file=sys.stderr)
+
+ ell_max += 1
+ w = self.ui.texture_ell_max
+ with block_signals(w):
+ w.setValue(ell_max)
+
+ model_kwargs[mat_name] = {
+ 'ssym': ssym,
+ 'ell_max': ell_max,
+ }
self.update_texture_model_enable_states()
@@ -2326,6 +2421,18 @@ def polar_extent(self) -> list[float] | None:
@property
def varying_texture_params(self) -> bool:
+ if self.varying_harmonic_texture_params:
+ return True
+
+ for mat_name in self.textured_materials_sanitized:
+ name = f'{mat_name}_p_md'
+ if name in self.params and self.params[name].vary:
+ return True
+
+ return False
+
+ @property
+ def varying_harmonic_texture_params(self) -> bool:
for mat_name in self.textured_materials_sanitized:
prefix = f'{mat_name}_c_'
for param in self.params.values():
@@ -2336,7 +2443,7 @@ def varying_texture_params(self) -> bool:
@property
def varying_texture_and_non_texture_params(self) -> bool:
- if not self.varying_texture_params:
+ if not self.varying_harmonic_texture_params:
return False
prefixes = [f'{mat_name}_c_' for mat_name in self.textured_materials_sanitized]
@@ -2360,7 +2467,7 @@ def is_zero(v: float) -> bool:
# If we find any non-zero parameters, we can plot pole figures
for model in obj.texture_model.values():
- if model is None:
+ if not isinstance(model, HarmonicModel):
continue
for name in model.parameter_names:
@@ -2395,7 +2502,7 @@ def clear_texture_data(self) -> None:
return
for model in obj.texture_model.values():
- if model is None or not model.pfdata:
+ if not isinstance(model, HarmonicModel) or not model.pfdata:
continue
# Clear it
@@ -2406,7 +2513,12 @@ def ensure_texture_data(self) -> None:
if not isinstance(obj, Rietveld):
raise Exception('Cannot make texture data without Rietveld object')
- if obj.texture_models_have_pfdata:
+ harmonic_models = [
+ model
+ for model in obj.texture_model.values()
+ if isinstance(model, HarmonicModel)
+ ]
+ if all(model.pfdata for model in harmonic_models):
# Nothing to do
return
@@ -2419,7 +2531,7 @@ def on_error() -> None:
self.async_runner.progress_title = 'Generating texture data...'
self.async_runner.error_callback = on_error
self.async_runner.run(self.update_texture_data)
- while not obj.texture_models_have_pfdata and not had_error:
+ while not all(model.pfdata for model in harmonic_models) and not had_error:
# Process events until we have pfdata. This will allows the
# progress dialog to animate.
QCoreApplication.processEvents()
@@ -2467,16 +2579,28 @@ def on_texture_plot_pole_figures_clicked(self) -> None:
if not isinstance(obj, Rietveld):
return
- if len(self.textured_materials) > 1:
+ plottable_materials = [
+ name
+ for name, model in obj.texture_model.items()
+ if isinstance(model, HarmonicModel)
+ ]
+ if not plottable_materials:
+ return
+
+ if len(plottable_materials) > 1:
# Get the user to pick a material
- mat_items = self.textured_materials
mat_name, ok = QInputDialog.getItem(
- self.ui, 'Pole Figures', 'Select material', mat_items, 0, False
+ self.ui,
+ 'Pole Figures',
+ 'Select material',
+ plottable_materials,
+ 0,
+ False,
)
if not ok:
return
else:
- mat_name = self.textured_materials[0]
+ mat_name = plottable_materials[0]
mat = HexrdConfig().material(mat_name)
assert mat is not None
@@ -2528,7 +2652,7 @@ def update_pole_figure_plots(self) -> None:
return
for model in obj.texture_model.values():
- if model is None:
+ if not isinstance(model, HarmonicModel):
continue
if model.new_pf_plots_visible:
@@ -2544,6 +2668,25 @@ def texture_settings(self) -> dict:
@texture_settings.setter
def texture_settings(self, v: dict) -> None:
+ # GUI-only model metadata used to be stored inside model_kwargs.
+ # Move it out so model_kwargs retains its legacy, constructor-safe
+ # shape and state files remain usable by older HEXRDGUI versions.
+ v.setdefault('model_types', {})
+ v.setdefault('preferred_axes', {})
+ for name, kwargs in v.setdefault('model_kwargs', {}).items():
+ model_type = kwargs.pop('model_type', None)
+ if model_type is not None:
+ v['model_types'].setdefault(name, model_type)
+
+ hkl = kwargs.pop('hkl', None)
+ if hkl is not None:
+ v['preferred_axes'].setdefault(name, hkl)
+
+ kwargs.setdefault('ssym', self._default_texture_model_settings['ssym'])
+ kwargs.setdefault(
+ 'ell_max', self._default_texture_model_settings['ell_max']
+ )
+
self._texture_settings = v
# Validate materials in the texture model dict are selected
@@ -2553,14 +2696,22 @@ def texture_settings(self, v: dict) -> None:
def prune_invalid_texture_materials(self) -> None:
valid_mats = self.selected_materials
- for name in list(self.texture_model_kwargs):
- if name not in valid_mats:
- self.texture_model_kwargs.pop(name)
+ settings_maps = [
+ self.texture_model_kwargs,
+ self.texture_model_types,
+ self.texture_preferred_axes,
+ ]
+ for settings in settings_maps:
+ for name in list(settings):
+ if name not in valid_mats:
+ settings.pop(name)
@property
def _default_texture_settings(self) -> dict:
return {
'model_kwargs': {},
+ 'model_types': {},
+ 'preferred_axes': {},
'azimuthal_interval': 5,
'integration_range': 1,
}
@@ -2592,7 +2743,23 @@ def texture_model_kwargs(self) -> dict[str, Any]:
return self.texture_settings['model_kwargs']
@property
- def texture_model_dict(self) -> dict[str, HarmonicModel]:
+ def texture_model_types(self) -> dict[str, str]:
+ return self.texture_settings.setdefault('model_types', {})
+
+ @property
+ def texture_preferred_axes(self) -> dict[str, list[int]]:
+ return self.texture_settings.setdefault('preferred_axes', {})
+
+ @property
+ def current_texture_model_type(self) -> str:
+ label = self.ui.texture_model_type.currentText()
+ return next(k for k, v in TEXTURE_MODEL_LABELS.items() if v == label)
+
+ def texture_model_type(self, mat_name: str) -> str:
+ return self.texture_model_types.get(mat_name, AXIS_DISTRIBUTION_MODEL)
+
+ @property
+ def texture_model_dict(self) -> dict[str, HarmonicModel | MarchDollaseModel]:
if self.method != 'Rietveld':
return {}
@@ -2600,15 +2767,22 @@ def texture_model_dict(self) -> dict[str, HarmonicModel]:
settings = self.texture_settings
for k, kwargs in settings['model_kwargs'].items():
mat = HexrdConfig().material(k)
- ret[k] = HarmonicModel(
- **{
- 'material': Material_Rietveld(material_obj=mat),
- 'bvec': HexrdConfig().beam_vector,
- 'evec': ct.eta_vec,
- 'sample_rmat': HexrdConfig().sample_rmat,
- **kwargs,
- }
- )
+ material = Material_Rietveld(material_obj=mat)
+ model_type = self.texture_model_type(k)
+ if model_type == MARCH_DOLLASE_MODEL:
+ ret[k] = MarchDollaseModel(
+ material=material,
+ HKL=self.texture_preferred_axes.get(k, [1, 1, 1]),
+ )
+ else:
+ ret[k] = HarmonicModel(
+ material=material,
+ bvec=HexrdConfig().beam_vector,
+ evec=ct.eta_vec,
+ sample_rmat=HexrdConfig().sample_rmat,
+ ssym=kwargs.get('ssym', 'axial'),
+ ell_max=kwargs.get('ell_max', 16),
+ )
return ret
def update_texture_index_label(self) -> None:
@@ -2619,7 +2793,7 @@ def update_texture_index_label(self) -> None:
if not isinstance(obj, Rietveld) or obj.texture_model.get(mat_name) is None:
v = 'None'
else:
- j = obj.texture_model[mat_name].J(self.params)
+ j = obj.texture_model[mat_name].texture_index(self.params)
v = f'{j:.2f}'
w.setText(f'Texture index: {v}')
diff --git a/hexrdgui/calibration/wppf_runner.py b/hexrdgui/calibration/wppf_runner.py
index e62d4771a..af3a2bbb0 100644
--- a/hexrdgui/calibration/wppf_runner.py
+++ b/hexrdgui/calibration/wppf_runner.py
@@ -67,10 +67,11 @@ def run_wppf(self) -> None:
assert dialog is not None
self.wppf_object = dialog.wppf_object
varying_texture = dialog.varying_texture_params
+ varying_harmonic_texture = dialog.varying_harmonic_texture_params
# Work around differences in WPPF objects
if isinstance(self.wppf_object, Rietveld):
- if varying_texture:
+ if varying_harmonic_texture:
refine_func = self.wppf_object.RefineTexture
else:
refine_func = self.wppf_object.Refine
diff --git a/hexrdgui/resources/ui/wppf_options_dialog.ui b/hexrdgui/resources/ui/wppf_options_dialog.ui
index e7fcdeb2b..b0f998556 100644
--- a/hexrdgui/resources/ui/wppf_options_dialog.ui
+++ b/hexrdgui/resources/ui/wppf_options_dialog.ui
@@ -587,7 +587,7 @@
- -
+
-
monoclinic
@@ -644,6 +644,27 @@
-
+
+
+ Texture model:
+
+
+
+ -
+
+
-
+
+ General Axis Distribution Function
+
+
+ -
+
+ March-Dollase
+
+
+
+
+ -
Sample symmetry:
@@ -664,6 +685,86 @@
+ -
+
+
+ Preferred axis:
+
+
+
+ -
+
+
-
+
+
+ h:
+
+
+
+ -
+
+
+ false
+
+
+ -1000
+
+
+ 1000
+
+
+ 1
+
+
+
+ -
+
+
+ k:
+
+
+
+ -
+
+
+ false
+
+
+ -1000
+
+
+ 1000
+
+
+ 1
+
+
+
+ -
+
+
+ l:
+
+
+
+ -
+
+
+ false
+
+
+ -1000
+
+
+ 1000
+
+
+ 1
+
+
+
+
+
@@ -1037,8 +1138,12 @@
amorphous_expt_smoothing
selected_texture_material
include_texture_model
+ texture_model_type
texture_sample_symmetry
texture_ell_max
+ texture_preferred_axis_h
+ texture_preferred_axis_k
+ texture_preferred_axis_l
texture_azimuthal_interval
texture_integration_range
texture_show_simulated_spectrum
diff --git a/tests/test_wppf_options_dialog.py b/tests/test_wppf_options_dialog.py
index 9827768c8..dbcaf7251 100644
--- a/tests/test_wppf_options_dialog.py
+++ b/tests/test_wppf_options_dialog.py
@@ -3,12 +3,15 @@
from types import SimpleNamespace
import h5py
+import lmfit
import numpy as np
import pytest
from pytestqt.qtbot import QtBot
from hexrdgui import state
+from hexrdgui.calibration import wppf_options_dialog
from hexrdgui.calibration.wppf_options_dialog import WppfOptionsDialog
+from hexrdgui.calibration.wppf_runner import WppfRunner
from hexrdgui.hexrd_config import HexrdConfig
from hexrdgui.image_canvas import ImageCanvas
@@ -213,3 +216,162 @@ def test_phase_fractions(qtbot: QtBot, monkeypatch: pytest.MonkeyPatch) -> None:
dialog.validate()
finally:
HexrdConfig().remove_materials(names)
+
+
+def test_march_dollase_settings_and_parameter_files(
+ qtbot: QtBot,
+ monkeypatch: pytest.MonkeyPatch,
+ tmp_path,
+) -> None:
+ calibration = HexrdConfig().config['calibration']
+ monkeypatch.setitem(calibration, 'wppf', {})
+
+ dialog = WppfOptionsDialog()
+ qtbot.addWidget(dialog.ui)
+
+ # The setting-change callbacks normally regenerate all WPPF parameters.
+ # Keep this test focused on the texture GUI and persistence paths.
+ monkeypatch.setattr(dialog, 'update_params', lambda *args, **kwargs: None)
+ dialog.method = 'Rietveld'
+ dialog.ui.selected_texture_material.clear()
+ dialog.ui.selected_texture_material.addItem('Ni')
+ dialog.ui.include_texture_model.setChecked(True)
+ dialog.ui.texture_model_type.setCurrentText('March-Dollase')
+ dialog.ui.texture_preferred_axis_h.setValue(1)
+ dialog.ui.texture_preferred_axis_k.setValue(0)
+ dialog.ui.texture_preferred_axis_l.setValue(-1)
+
+ expected_settings = {
+ 'model_types': {'Ni': wppf_options_dialog.MARCH_DOLLASE_MODEL},
+ 'preferred_axes': {'Ni': [1, 0, -1]},
+ }
+ settings = dialog.texture_settings
+ assert {k: settings[k] for k in expected_settings} == expected_settings
+ assert dialog.ui.texture_sample_symmetry.isHidden()
+ assert not dialog.ui.texture_preferred_axis_h.isHidden()
+ assert not dialog.ui.texture_preferred_axis_k.isHidden()
+ assert not dialog.ui.texture_preferred_axis_l.isHidden()
+ assert not dialog.ui.texture_azimuthal_interval.isEnabled()
+ assert not dialog.ui.texture_integration_range.isEnabled()
+
+ constructed = {}
+
+ class DummyMarchDollaseModel:
+ def __init__(self, **kwargs):
+ constructed.update(kwargs)
+
+ monkeypatch.setattr(
+ wppf_options_dialog, 'MarchDollaseModel', DummyMarchDollaseModel
+ )
+ monkeypatch.setattr(
+ wppf_options_dialog,
+ 'Material_Rietveld',
+ lambda material_obj: f'rietveld-{material_obj}',
+ )
+ monkeypatch.setattr(HexrdConfig, 'material', lambda self, name: name)
+ assert isinstance(dialog.texture_model_dict['Ni'], DummyMarchDollaseModel)
+ assert constructed == {'material': 'rietveld-Ni', 'HKL': [1, 0, -1]}
+
+ dialog.params = lmfit.Parameters()
+ dialog.params.add('Ni_p_md', value=1.25, min=0.0, vary=True)
+ tree = dialog.tree_view_dict_of_params
+ assert tree['Texture']['Ni']['P_md']['_param'] is dialog.params['Ni_p_md']
+
+ params_file = tmp_path / 'params.h5'
+ dialog.save_params(params_file)
+ dialog.params['Ni_p_md'].value = 2.0
+ dialog.load_params(params_file)
+ assert dialog.params['Ni_p_md'].value == pytest.approx(1.25)
+
+ dialog.save_settings()
+ state_file = tmp_path / 'state.h5'
+ snapshot = {'config_calibration': {'wppf': calibration['wppf']}}
+ with h5py.File(state_file, 'w') as f:
+ state._save_config(f, snapshot)
+ with h5py.File(state_file, 'r') as f:
+ loaded = state._load_config(f)
+
+ loaded_wppf = loaded['config_calibration']['wppf']
+ settings = loaded_wppf['texture_settings']
+ assert {k: settings[k] for k in expected_settings} == expected_settings
+ assert loaded_wppf['params_dict']['Ni_p_md']['value'] == pytest.approx(1.25)
+
+
+def test_saved_plot_contains_march_dollase_parameter(
+ qtbot: QtBot,
+ monkeypatch: pytest.MonkeyPatch,
+ tmp_path,
+) -> None:
+ calibration = HexrdConfig().config['calibration']
+ monkeypatch.setitem(calibration, 'wppf', {})
+
+ dialog = WppfOptionsDialog()
+ qtbot.addWidget(dialog.ui)
+ dialog.params = lmfit.Parameters()
+ dialog.params.add('Ni_p_md', value=1.25)
+ dialog._wppf_object = SimpleNamespace(
+ spectrum_sim=SimpleNamespace(x=np.arange(3), y=np.arange(3)),
+ weights=SimpleNamespace(y=np.ones(3)),
+ background=SimpleNamespace(y=np.zeros(3)),
+ amorphous_model=None,
+ )
+ monkeypatch.setattr(
+ HexrdConfig(),
+ 'last_unscaled_azimuthal_integral_data',
+ (np.arange(3), np.ma.array(np.arange(3))),
+ )
+
+ output = tmp_path / 'plot.h5'
+ dialog.write_data(output)
+
+ with h5py.File(output, 'r') as f:
+ assert f['params/Ni_p_md/value'][()] == pytest.approx(1.25)
+
+
+def test_march_dollase_refinement(
+ qtbot: QtBot,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ # Refine P_MD on a pattern simulated with P_MD = 1.5
+ monkeypatch.setitem(HexrdConfig().config['calibration'], 'wppf', {})
+ tth = np.linspace(2, 30, 1400)
+
+ def set_lineout(y: np.ndarray) -> None:
+ data = (tth, np.ma.array(y))
+ monkeypatch.setattr(
+ HexrdConfig(), 'last_unscaled_azimuthal_integral_data', data
+ )
+
+ set_lineout(np.full_like(tth, 100.0))
+ dialog = WppfOptionsDialog()
+ qtbot.addWidget(dialog.ui)
+ dialog.use_statistical_weights = False
+ dialog.spline_points = [[3.0, 100.0], [29.0, 100.0]]
+ dialog.selected_materials = ['CeO2']
+ dialog.method = 'Rietveld'
+ dialog.ui.include_texture_model.setChecked(True)
+ dialog.ui.texture_model_type.setCurrentText('March-Dollase')
+ dialog.ui.texture_preferred_axis_k.setValue(0)
+ dialog.ui.texture_preferred_axis_l.setValue(0)
+ for param in dialog.params.values():
+ param.vary = False
+
+ dialog.params['CeO2_p_md'].value = 1.5
+ obj = dialog.wppf_object
+ assert obj.texture_model['CeO2'].HKL.tolist() == [1, 0, 0]
+ obj._set_params_vals_to_class(obj.params, force=True)
+ obj.computespectrum()
+ sim = obj.spectrum_sim
+ set_lineout(np.interp(tth, sim.x, np.nan_to_num(sim.y)))
+ dialog.reset_object()
+
+ dialog.params['CeO2_p_md'].set(value=1.0, vary=True)
+ runner = WppfRunner()
+ runner.wppf_options_dialog = dialog
+ dialog.run.connect(runner.run_wppf)
+ dialog.begin_run()
+ assert dialog.params['CeO2_p_md'].value == pytest.approx(1.5, abs=1e-3)
+ assert dialog.ui.texture_index_label.text() == 'Texture index: 1.50'
+
+ dialog.pop_undo_stack()
+ assert dialog.params['CeO2_p_md'].value == 1.0