Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions news/tch-pv-profile.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* Expose reflection profile ReflectionProfilePseudoVoigtTCH

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
20 changes: 20 additions & 0 deletions src/extensions/reflectionprofile_ext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,26 @@ void wrap_reflectionprofile()
bp::arg("eta1") = 0),
"Set the isotropic pseudo-Voigt profile parameters.");

class_<ReflectionProfilePseudoVoigtTCH, bases<ReflectionProfile> >(
"ReflectionProfilePseudoVoigtTCH", init<>())
.def(init<const ReflectionProfilePseudoVoigtTCH &>(bp::arg("old")))
.def("CreateCopy", &ReflectionProfilePseudoVoigtTCH::CreateCopy,
return_value_policy<manage_new_object>(),
"Return an independent copy of this profile.")
.def("SetProfilePar",
(void (ReflectionProfilePseudoVoigtTCH::*)(
const REAL, const REAL, const REAL, const REAL,
const REAL, const REAL, const REAL, const REAL))
&ReflectionProfilePseudoVoigtTCH::SetProfilePar,
(bp::arg("fwhmCagliotiW"), bp::arg("fwhmCagliotiU") = 0,
bp::arg("fwhmCagliotiV") = 0,
bp::arg("fwhmLorentzX") = 0,
bp::arg("fwhmLorentzY") = 0,
bp::arg("fwhmLorentzZ") = 0,
bp::arg("fwhmScherrerP") = 0,
bp::arg("scherrerLGmix") = 1),
"Set the TCH pseudo-Voigt U, V, W, X, Y, Z, P and LGmix parameters.");

class_<ReflectionProfilePseudoVoigtAnisotropic,
bases<ReflectionProfile> >(
"ReflectionProfilePseudoVoigtAnisotropic", init<>())
Expand Down
10 changes: 6 additions & 4 deletions src/pyobjcryst/reflectionprofile.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
``XMLOutput`` / ``XMLInput`` and ``CreateCopy``. ``GetProfile``
accepts a Python sequence or numpy array for ``x``.

Concrete isotropic and anisotropic pseudo-Voigt profiles can be installed
with ``PowderPatternDiffraction.SetProfile``. The diffraction component
stores an independent copy, so one configured profile can be reused as a
template for multiple phases::
Concrete empirical isotropic, TCH isotropic, and anisotropic pseudo-Voigt
profiles can be installed with ``PowderPatternDiffraction.SetProfile``. The
diffraction component stores an independent copy, so one configured profile
can be reused as a template for multiple phases::

from pyobjcryst.reflectionprofile import (
ReflectionProfilePseudoVoigtAnisotropic,
Expand Down Expand Up @@ -67,6 +67,7 @@
__all__ = [
"ReflectionProfile",
"ReflectionProfilePseudoVoigt",
"ReflectionProfilePseudoVoigtTCH",
"ReflectionProfilePseudoVoigtAnisotropic",
"ReflectionProfileType",
]
Expand All @@ -75,5 +76,6 @@
ReflectionProfile,
ReflectionProfilePseudoVoigt,
ReflectionProfilePseudoVoigtAnisotropic,
ReflectionProfilePseudoVoigtTCH,
ReflectionProfileType,
)
68 changes: 68 additions & 0 deletions tests/test_reflectionprofile.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
ReflectionProfile,
ReflectionProfilePseudoVoigt,
ReflectionProfilePseudoVoigtAnisotropic,
ReflectionProfilePseudoVoigtTCH,
)


Expand Down Expand Up @@ -73,6 +74,73 @@ def test_concrete_pseudo_voigt_profiles(self):
},
)

def test_tch_profile_width_and_limits(self):
"""TCH derives one common FWHM and reaches both pure limits."""
profile = ReflectionProfilePseudoVoigtTCH()
self.assertFalse(profile.IsAnisotropic())
self.assertEqual(
{"U", "V", "W", "X", "Y", "Z", "P", "LGmix"},
{profile.GetPar(i).GetName() for i in range(profile.GetNbPar())},
)

center = np.deg2rad(30.0)
hg = np.deg2rad(0.08)
hl = np.deg2rad(0.04)
profile.SetProfilePar(hg**2, fwhmLorentzZ=hl)
h = (
hg**5
+ 2.69269 * hg**4 * hl
+ 2.42843 * hg**3 * hl**2
+ 4.47163 * hg**2 * hl**3
+ 0.07842 * hg * hl**4
+ hl**5
) ** 0.2
self.assertAlmostEqual(
profile.GetFullProfileWidth(0.5, center, 1, 0, 0), h
)

x = np.array([center - h / 2, center, center + h / 2])
y = profile.GetProfile(x, center, 1, 0, 0)
np.testing.assert_allclose(y[[0, 2]] / y[1], 0.5, rtol=1e-6)

profile.SetProfilePar(hg**2)
self.assertAlmostEqual(
profile.GetFullProfileWidth(0.5, center, 1, 0, 0), hg
)
profile.SetProfilePar(0, fwhmLorentzZ=hl)
self.assertAlmostEqual(
profile.GetFullProfileWidth(0.5, center, 1, 0, 0), hl
)

size_width = np.deg2rad(0.03) / np.cos(center / 2)
profile.SetProfilePar(
0, fwhmScherrerP=np.deg2rad(0.03), scherrerLGmix=1
)
self.assertAlmostEqual(
profile.GetFullProfileWidth(0.5, center, 1, 0, 0), size_width
)
profile.SetProfilePar(
0, fwhmScherrerP=np.deg2rad(0.03), scherrerLGmix=0
)
self.assertAlmostEqual(
profile.GetFullProfileWidth(0.5, center, 1, 0, 0), size_width
)

self.ppd.SetProfile(profile)
installed = self.ppd.GetProfile()
self.assertIsInstance(installed, ReflectionProfilePseudoVoigtTCH)
self.assertAlmostEqual(
installed.GetPar("P").GetValue(), np.deg2rad(0.03)
)
self.assertAlmostEqual(installed.GetPar("LGmix").GetValue(), 0)

restored = ReflectionProfilePseudoVoigtTCH()
RefinableObj.XMLInput(restored, profile.xml())
self.assertAlmostEqual(
restored.GetPar("P").GetValue(), np.deg2rad(0.03)
)
self.assertAlmostEqual(restored.GetPar("LGmix").GetValue(), 0)

def test_anisotropic_set_profile_par(self):
"""SetProfilePar assigns widths and retains symmetric default
asymmetry."""
Expand Down
Loading