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/iteratepars-behavior.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* <news item>

**Changed:**

* Changed `iterPars` method to match all equal-type atoms to have same ADPs

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
54 changes: 46 additions & 8 deletions src/diffpy/srfit/fitbase/recipeorganizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,32 +116,70 @@ def _iter_managed(self):
"""Get iterator over managed objects."""
return chain(*(d.values() for d in self.__managed))

def iterPars(self, pattern="", recurse=True):
def iterPars(self, pattern="", recurse=True, fullnames=False):
"""Iterate over the Parameters contained in this object.

Parameters
----------
pattern : str
Iterate over parameters with names matching this regular
expression (all parameters by default).
Iterate over parameters with names matching this regular expression
(all parameters by default).

When `fullnames` is True, the regular expression is matched against
dotted parameter names relative to this object, e.g. ``Ni0.Biso``.
recurse : bool
Recurse into managed objects when True (default).
fullnames : bool
Match against hierarchical dotted names relative to this object
when True. Match only leaf parameter names when False (default).
"""
regexp = re.compile(pattern)
if not fullnames:
for par in list(self._parameters.values()):
if regexp.search(par.name):
yield par
if not recurse:
return
managed = self.__managed[:]
managed.remove(self._parameters)
for m in managed:
for obj in m.values():
if hasattr(obj, "iterPars"):
for par in obj.iterPars(pattern=pattern, recurse=True):
yield par
return
for par in self._iterpars_fullnames(
regexp, recurse=recurse, prefix=""
):
yield par

def _iterpars_fullnames(self, regexp, recurse=True, prefix=""):
"""Internal helper for iterPars(fullnames=True)."""
for par in list(self._parameters.values()):
if regexp.search(par.name):
name = f"{prefix}{par.name}"
if regexp.search(name):
yield par

if not recurse:
return
# Iterate over objects within the managed dictionaries.

managed = self.__managed[:]
managed.remove(self._parameters)
for m in managed:
for obj in m.values():
if hasattr(obj, "iterPars"):
for par in obj.iterPars(pattern=pattern):
if hasattr(obj, "_iterpars_fullnames"):
childprefix = f"{prefix}{obj.name}."
for par in obj._iterpars_fullnames(
regexp,
recurse=True,
prefix=childprefix,
):
yield par
elif hasattr(obj, "iterPars"):
for par in obj.iterPars(
pattern=regexp.pattern, recurse=True
):
yield par
return

def __iter__(self):
"""Iterate over top-level parameters."""
Expand Down
74 changes: 74 additions & 0 deletions tests/test_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import pytest

from diffpy.srfit.exceptions import SrFitError
from diffpy.srfit.fitbase.parameter import Parameter
from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer
from diffpy.srfit.pdf import PDFContribution, PDFGenerator, PDFParser

# ----------------------------------------------------------------------------
Expand Down Expand Up @@ -302,3 +304,75 @@ def test_pickling(diffpy_srreal_available, datafile):

if __name__ == "__main__":
unittest.main()


def _make_iterpars_tree():
"""Build a small hierarchy for iterPars tests."""
root = RecipeContainer("root")
root._containers = {}
root._manage(root._containers)

root_biso = Parameter("Biso", 10)
root._add_object(root_biso, root._parameters)

ni0 = RecipeContainer("Ni0")
ni0_biso = Parameter("Biso", 20)
ni0_uiso = Parameter("Uiso", 30)
ni0._add_object(ni0_biso, ni0._parameters)
ni0._add_object(ni0_uiso, ni0._parameters)

ni1 = RecipeContainer("Ni1")
ni1_biso = Parameter("Biso", 40)
ni1._add_object(ni1_biso, ni1._parameters)

o0 = RecipeContainer("O0")
o0_biso = Parameter("Biso", 50)
o0._add_object(o0_biso, o0._parameters)

root._add_object(ni0, root._containers)
root._add_object(ni1, root._containers)
root._add_object(o0, root._containers)

return {
"root": root,
"root_biso": root_biso,
"ni0": ni0,
"ni0_biso": ni0_biso,
"ni0_uiso": ni0_uiso,
"ni1": ni1,
"ni1_biso": ni1_biso,
"o0": o0,
"o0_biso": o0_biso,
}


@pytest.mark.parametrize(
("pattern", "kwargs", "expected_values"),
[
(r"^Biso$", {}, [10, 20, 40, 50]),
(r"^Ni\d+\.Biso$", {}, []),
(r"^Ni\d+\.Biso$", {"fullnames": True}, [20, 40]),
(r"^Ni0\.Uiso$", {"fullnames": True}, [30]),
(r"^O0\.Biso$", {"fullnames": True}, [50]),
(r"^Ni\d+\.Biso$", {"fullnames": True, "recurse": False}, []),
(r"^Biso$", {"fullnames": True, "recurse": False}, [10]),
],
)
def test_iterpars_fullname_matching(pattern, kwargs, expected_values):
"""Verify leaf-name and fullname matching in iterPars."""
objs = _make_iterpars_tree()
root = objs["root"]

values = [par.value for par in root.iterPars(pattern, **kwargs)]

assert values == expected_values


def test_iterpars_fullnames_are_relative_to_called_container():
"""Verify fullname matching is relative to the called container."""
objs = _make_iterpars_tree()
ni0 = objs["ni0"]

assert list(ni0.iterPars(r"^Biso$", fullnames=True)) == [objs["ni0_biso"]]
assert list(ni0.iterPars(r"^Uiso$", fullnames=True)) == [objs["ni0_uiso"]]
assert list(ni0.iterPars(r"^Ni0\.Biso$", fullnames=True)) == []
Loading