Skip to content
Merged
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
2 changes: 1 addition & 1 deletion CODE-OF-CONDUCT.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
sbillinge@ucsb.edu and cadenmyers13@columbia.edu. All complaints will be reviewed and investigated promptly and fairly.
sbillinge@ucsb.edu and cjm2304@columbia.edu. All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Before contributing, please read our `Code of Conduct <https://github.com/diffpy
Contact
-------

For more information on diffpy.cmirietveld please visit the project `web-page <https://diffpy.github.io/>`_ or email the maintainers ``Simon Billinge(sbillinge@ucsb.edu) and Caden Myers(cadenmyers13@columbia.edu)``.
For more information on diffpy.cmirietveld please visit the project `web-page <https://diffpy.github.io/>`_ or email the maintainers ``Simon Billinge(sbillinge@ucsb.edu) and Caden Myers(cjm2304@columbia.edu)``.

Acknowledgements
----------------
Expand Down
4 changes: 2 additions & 2 deletions cookiecutter.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"author_names": "Simon Billinge, Caden Myers",
"author_emails": "sbillinge@ucsb.edu, cadenmyers13@columbia.edu",
"author_emails": "sbillinge@ucsb.edu, cjm2304@columbia.edu",
"maintainer_names": "Simon Billinge, Caden Myers",
"maintainer_emails": "sbillinge@ucsb.edu, cadenmyers13@columbia.edu",
"maintainer_emails": "sbillinge@ucsb.edu, cjm2304@columbia.edu",
"maintainer_github_usernames": "sbillinge",
"contributors": "Caden Myers, members of the Billinge group, and contributors to the diffpy.cmirietveld project",
"project_name": "diffpy.cmirietveld",
Expand Down
23 changes: 23 additions & 0 deletions news/component-parameters.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* Add ``Component.create_parameters_from``, which creates a component's Parameters from a pyobjcryst model, with the model's default values and its limits as bounds, and ``Component.push_parameters``, which writes the values back and raises a ``ValueError`` for a value outside the limits instead of letting ObjCryst clamp it silently.

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
23 changes: 23 additions & 0 deletions news/peak-profiles.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* Add the ``PseudoVoigtProfile`` and ``TCHProfile`` peak profile components, which create their Parameters from pyobjcryst's ``ReflectionProfilePseudoVoigt`` and ``ReflectionProfilePseudoVoigtTCH`` and configure a pyobjcryst powder pattern with ``install`` and ``update``.

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ name = "diffpy.cmirietveld"
dynamic=['version', 'dependencies']
authors = [
{name='Simon Billinge', email='sbillinge@ucsb.edu'},
{name='Caden Myers', email='cadenmyers13@columbia.edu'},
{name='Caden Myers', email='cjm2304@columbia.edu'},
]
maintainers = [
{name='Simon Billinge', email='sbillinge@ucsb.edu'},
{name='Caden Myers', email='cadenmyers13@columbia.edu'},
{name='Caden Myers', email='cjm2304@columbia.edu'},
]
description = "A pack for diffpy.cmi that allows it to do Rietveld refinement of powder diffraction data"
keywords = ['diffraction', 'PDF', 'X-ray', 'neutron', 'Rietveld']
Expand Down
1 change: 1 addition & 0 deletions requirements/conda.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
numpy
diffpy.srfit
pyobjcryst
1 change: 1 addition & 0 deletions requirements/pip.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
numpy
diffpy.srfit
pyobjcryst
105 changes: 100 additions & 5 deletions src/diffpy/cmirietveld/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

__all__ = ["Component"]

from diffpy.srfit.fitbase.parameter import Parameter
from diffpy.srfit.fitbase.parameterset import ParameterSet


Expand All @@ -38,6 +39,10 @@ class Component(ParameterSet):
The name of the component. It must be a valid attribute name.
"""

def __init__(self, name):
ParameterSet.__init__(self, name)
self._refinable_par_names = {}

def install(self, pattern, diffraction):
"""Set the model up in one generator.

Expand Down Expand Up @@ -73,20 +78,81 @@ def create_parameters_from(self, refinable_obj, names):
model.

Each Parameter takes the default value of the matching
``RefinablePar`` and its limits as bounds.
``RefinablePar`` and its limits as bounds. ObjCryst silently clamps
a value outside its limits, so the bounds keep a refinement within
the values ObjCryst actually uses.

The Parameter names are mapped to the ``RefinablePar`` names
rather than copied, so the names users see are this package's,
independent of ObjCryst's. Some ObjCryst names are not valid
Parameter names (``2ThetaDispl``). The mapping keeps ObjCryst
parameters out of refinement scripts. The mapping is stored, so
``push_parameters`` writes each value back to the same
``RefinablePar``.

Parameters
----------
refinable_obj : pyobjcryst.refinableobj.RefinableObj
A fresh instance of the pyobjcryst model.
names : dict of str to str
The ``RefinablePar`` name for each Parameter name.
The ``RefinablePar`` name for each Parameter name, for example
``{"eta0": "Eta0"}``. Only the named ``RefinablePar``s get a
Parameter.

Raises
------
ValueError
If the model has no ``RefinablePar`` with one of the names.

Examples
--------
In the ``__init__`` of a peak profile:

>>> self.create_parameters_from(
... ReflectionProfilePseudoVoigt(),
... {"U": "U", "V": "V", "W": "W", "eta0": "Eta0"},
... )
"""
raise NotImplementedError
available_names = [
refinable_obj.GetPar(index).GetName()
for index in range(refinable_obj.GetNbPar())
]
for refinable_par_name in names.values():
if refinable_par_name not in available_names:
raise ValueError(
f"{type(refinable_obj).__name__} has no refinable "
f"parameter named '{refinable_par_name}'. Please use one "
f"of its parameter names: {', '.join(available_names)}."
)
for parameter_name, refinable_par_name in names.items():
refinable_par = refinable_obj.GetPar(refinable_par_name)
parameter = Parameter(
parameter_name, refinable_par.GetHumanValue()
)
if refinable_par.IsLimited():
parameter.bound_range(
refinable_par.GetHumanMin(), refinable_par.GetHumanMax()
)
self.add_parameter(parameter)
self._refinable_par_names.update(names)

def push_parameters(self, refinable_obj):
"""Push the Parameter values into a pyobjcryst model.

Each value is written to the ``RefinablePar`` that
``create_parameters_from`` mapped it to.

A value outside the limits of its ``RefinablePar`` raises an error
instead of being clamped, because ObjCryst would otherwise use a
different value from the one the Parameter reports. The check uses
ObjCryst's limits, not the Parameter's bounds: a value outside
narrowed bounds is still pushed, and widened bounds don't let a
value past a limit. An optimizer run without the recipe's bounds
stops with this error the first time it steps past a limit.

All the values are checked before any is pushed, so an error
leaves the model unchanged rather than half updated.

Parameters
----------
refinable_obj : pyobjcryst.refinableobj.RefinableObj
Expand All @@ -95,9 +161,38 @@ def push_parameters(self, refinable_obj):
Raises
------
ValueError
If a Parameter value is outside its bounds.
If a Parameter value is outside the limits of its
``RefinablePar``.
"""
raise NotImplementedError
values = {
parameter_name: self.get(parameter_name).value
for parameter_name in self._refinable_par_names
}
refinable_pars = {
parameter_name: refinable_obj.GetPar(refinable_par_name)
for parameter_name, refinable_par_name in (
self._refinable_par_names.items()
)
}
for parameter_name, refinable_par in refinable_pars.items():
if not refinable_par.IsLimited():
continue
value = values[parameter_name]
lower_limit = refinable_par.GetHumanMin()
upper_limit = refinable_par.GetHumanMax()
if not lower_limit <= value <= upper_limit:
used_value = min(max(value, lower_limit), upper_limit)
raise ValueError(
f"The value of '{parameter_name}', {value}, is outside "
f"its limits [{lower_limit}, {upper_limit}]. ObjCryst "
f"would silently use {used_value} instead, so the refined "
"value would not be the one used to calculate the "
"pattern. Please keep the value within the limits, for "
"example by passing the recipe's bounds to the optimizer: "
"least_squares(..., bounds=recipe.get_bounds_array())."
)
for parameter_name, refinable_par in refinable_pars.items():
refinable_par.SetHumanValue(values[parameter_name])


# End of file
115 changes: 115 additions & 0 deletions src/diffpy/cmirietveld/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"BackToBackExponentialProfile",
]

from pyobjcryst.reflectionprofile import (
ReflectionProfilePseudoVoigt,
ReflectionProfilePseudoVoigtTCH,
)

from diffpy.cmirietveld.component import Component


Expand Down Expand Up @@ -52,6 +57,62 @@ class PseudoVoigtProfile(PeakProfile):
The peak asymmetry terms.
"""

def __init__(self):
PeakProfile.__init__(self)
self.create_parameters_from(
ReflectionProfilePseudoVoigt(),
{
"U": "U",
"V": "V",
"W": "W",
"eta0": "Eta0",
"eta1": "Eta1",
"asym0": "Asym0",
"asym1": "Asym1",
"asym2": "Asym2",
},
)

def install(self, pattern, diffraction):
"""Install a fresh pyobjcryst pseudo-Voigt profile in one
generator.

The diffraction component gets a new
``ReflectionProfilePseudoVoigt``, replacing the profile installed
before. Its values are set by ``update``.

Parameters
----------
pattern : pyobjcryst.powderpattern.PowderPattern
The pattern of the generator.
diffraction : pyobjcryst.powderpattern.PowderPatternDiffraction
The diffraction component of the generator's structure.
"""
diffraction.SetProfile(ReflectionProfilePseudoVoigt())

def update(self, pattern, diffraction):
"""Push the Parameter values into the installed pseudo-Voigt
profile.

``SetProfile`` installs a copy, so the values are written into
``diffraction.GetProfile()``, not into the profile given to
``SetProfile``.

Parameters
----------
pattern : pyobjcryst.powderpattern.PowderPattern
The pattern of the generator.
diffraction : pyobjcryst.powderpattern.PowderPatternDiffraction
The diffraction component of the generator's structure.

Raises
------
ValueError
If a Parameter value is outside the limits of its
``RefinablePar``.
"""
self.push_parameters(diffraction.GetProfile())


class TCHProfile(PeakProfile):
"""Symmetric Thompson-Cox-Hastings pseudo-Voigt peak profile.
Expand All @@ -71,6 +132,60 @@ class TCHProfile(PeakProfile):
The mixing of the Gaussian and Lorentzian size broadening.
"""

def __init__(self):
PeakProfile.__init__(self)
self.create_parameters_from(
ReflectionProfilePseudoVoigtTCH(),
{
"U": "U",
"V": "V",
"W": "W",
"X": "X",
"Y": "Y",
"Z": "Z",
"P": "P",
"LGmix": "LGmix",
},
)

def install(self, pattern, diffraction):
"""Install a fresh pyobjcryst TCH profile in one generator.

The diffraction component gets a new
``ReflectionProfilePseudoVoigtTCH``, replacing the profile installed
before. Its values are set by ``update``.

Parameters
----------
pattern : pyobjcryst.powderpattern.PowderPattern
The pattern of the generator.
diffraction : pyobjcryst.powderpattern.PowderPatternDiffraction
The diffraction component of the generator's structure.
"""
diffraction.SetProfile(ReflectionProfilePseudoVoigtTCH())

def update(self, pattern, diffraction):
"""Push the Parameter values into the installed TCH profile.

``SetProfile`` installs a copy, so the values are written into
``diffraction.GetProfile()``, not into the profile given to
``SetProfile``.

Parameters
----------
pattern : pyobjcryst.powderpattern.PowderPattern
The pattern of the generator.
diffraction : pyobjcryst.powderpattern.PowderPatternDiffraction
The diffraction component of the generator's structure.

Raises
------
ValueError
If a Parameter value is outside the limits of its
``RefinablePar``.
"""
self.push_parameters(diffraction.GetProfile())


class BackToBackExponentialProfile(PeakProfile):
"""Peak profile of a time-of-flight pattern.
Expand Down
Loading
Loading