From 6da5a923a9b7a0127bd96a186262d7a6a0694c53 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:02:33 -0400 Subject: [PATCH 01/30] basepdfgenerator.py --- src/diffpy/cmipdf/basepdfgenerator.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index e3ab3e8..6965b56 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -109,7 +109,7 @@ def __init__(self, name="pdf"): _parnames = ["delta1", "delta2", "qbroad", "scale", "qdamp"] - def _setCalculator(self, calc): + def _set_calculator(self, calc): """Set the SrReal calculator instance. Setting the calculator creates Parameters from the variable @@ -118,13 +118,13 @@ def _setCalculator(self, calc): self._calc = calc for pname in self.__class__._parnames: self.addParameter(ParameterAdapter(pname, self._calc, attr=pname)) - self.processMetaData() + self._process_metadata() return def parallel(self, ncpu, mapfunc=None): """Run calculation in parallel. - Attributes + Parameters ---------- ncpu Number of parallel processes. Revert to serial mode when 1. @@ -155,9 +155,9 @@ def parallel(self, ncpu, mapfunc=None): self._calc = createParallelCalculator(calc_serial, ncpu, mapfunc) return - def processMetaData(self): + def _process_metadata(self): """Process the metadata once it gets set.""" - ProfileGenerator.processMetaData(self) + ProfileGenerator._process_metadata(self) stype = self.meta.get("stype") if stype is not None: @@ -175,14 +175,14 @@ def processMetaData(self): val = self.meta.get(name) if val is not None: par = self.get(name) - par.setValue(val) + par.set_value(val) return def setScatteringType(self, stype="X"): """Set the scattering type. - Attributes + Parameters ---------- stype "X" for x-ray, "N" for neutron, "E" for electrons, @@ -231,7 +231,7 @@ def setStructure(self, stru, name="phase", periodic=True): See those classes (located in diffpy.srfit.structure) for how they are used. The resulting ParameterSet will be managed by this generator. - Attributes + Parameters ---------- stru diffpy.structure.Structure, pyobjcryst.crystal.Crystal or @@ -261,7 +261,7 @@ def setPhase(self, parset, periodic=True): object (from diffpy or pyobjcryst). The passed ParameterSet will be managed by this generator. - Attributes + Parameters ---------- parset A SrRealParSet that holds the structural information. @@ -278,7 +278,7 @@ def setPhase(self, parset, periodic=True): self.stru = self._phase.stru # Put this ParameterSet in the ProfileGenerator. - self.addParameterSet(parset) + self.add_parameter_set(parset) # Set periodicity self._phase.useSymmetry(periodic) @@ -321,7 +321,7 @@ def __call__(self, r): if not numpy.array_equal(r, self._lastr): self._prepare(r) - rcalc, y = self._calc(self._phase._getSrRealStructure()) + rcalc, y = self._calc(self._phase._get_srreal_structure()) if numpy.isnan(y).any(): y = numpy.zeros_like(r) From 090409e058bcb5f74506e952c0105a75905866e2 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:03:18 -0400 Subject: [PATCH 02/30] characteristicfunctions.py --- src/diffpy/cmipdf/characteristicfunctions.py | 33 ++++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/diffpy/cmipdf/characteristicfunctions.py b/src/diffpy/cmipdf/characteristicfunctions.py index 8ad43b4..19f8364 100644 --- a/src/diffpy/cmipdf/characteristicfunctions.py +++ b/src/diffpy/cmipdf/characteristicfunctions.py @@ -1,16 +1,15 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2025 Simon Billinge. -# All rights reserved. +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. # -# File coded by: Caden Myers, Simon Billinge, and members of the Billinge -# group. +# File coded by: Chris Farrow # -# See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.cmipdf/graphs/contributors -# -# See LICENSE.rst for license information. +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. # ############################################################################## """Form factors (characteristic functions) used in PDF nanoshape @@ -22,7 +21,7 @@ function and Gcryst(f) is the crystal PDF. These functions are meant to be imported and added to a FitContribution -using the 'registerFunction' method of that class. +using the 'register_function' method of that class. """ __all__ = [ @@ -49,7 +48,7 @@ def sphericalCF(r, psize): """Spherical nanoparticle characteristic function. - Attributes + Parameters ---------- r distance of interaction @@ -74,7 +73,7 @@ def spheroidalCF(r, erad, prad): Spheroid with radii (erad, erad, prad) - Attributes + Parameters ---------- prad polar radius @@ -96,7 +95,7 @@ def spheroidalCF2(r, psize, axrat): Form factor for ellipsoid with radii (psize/2, psize/2, axrat*psize/2) - Attributes + Parameters ---------- r distance of interaction @@ -207,7 +206,7 @@ def lognormalSphericalCF(r, psize, psig): """Spherical nanoparticle characteristic function with lognormal size distribution. - Attributes + Parameters ---------- r distance of interaction @@ -264,7 +263,7 @@ def lognormalSphericalCF(r, psize, psig): def sheetCF(r, sthick): """Nanosheet characteristic function. - Attributes + Parameters ---------- r distance of interaction @@ -294,7 +293,7 @@ def sheetCF(r, sthick): def shellCF(r, radius, thickness): """Spherical shell characteristic function. - Attributes + Parameters ---------- radius Inner radius @@ -314,7 +313,7 @@ def shellCF(r, radius, thickness): def shellCF2(r, a, delta): """Spherical shell characteristic function. - Attributes + Parameters ---------- a Central radius @@ -379,7 +378,7 @@ class SASCF(Calculator): def __init__(self, name, model): """Initialize the generator. - Attributes + Parameters ---------- name A name for the SASCF From 4118a1ce17f2da959dff2e464854c50e9737ce4f Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:11:14 -0400 Subject: [PATCH 03/30] copy rest of modified srfit files here --- src/diffpy/cmipdf/characteristicfunctions.py | 15 ++--- src/diffpy/cmipdf/debyepdfgenerator.py | 6 +- src/diffpy/cmipdf/pdfcontribution.py | 66 ++++++++------------ src/diffpy/cmipdf/pdfgenerator.py | 2 +- 4 files changed, 39 insertions(+), 50 deletions(-) diff --git a/src/diffpy/cmipdf/characteristicfunctions.py b/src/diffpy/cmipdf/characteristicfunctions.py index 19f8364..2fae86c 100644 --- a/src/diffpy/cmipdf/characteristicfunctions.py +++ b/src/diffpy/cmipdf/characteristicfunctions.py @@ -1,15 +1,16 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2025 Simon Billinge. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Caden Myers, Simon Billinge, and members of the Billinge +# group. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmipdf/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## """Form factors (characteristic functions) used in PDF nanoshape diff --git a/src/diffpy/cmipdf/debyepdfgenerator.py b/src/diffpy/cmipdf/debyepdfgenerator.py index e5cdf63..f60404e 100644 --- a/src/diffpy/cmipdf/debyepdfgenerator.py +++ b/src/diffpy/cmipdf/debyepdfgenerator.py @@ -93,7 +93,7 @@ def setStructure(self, stru, name="phase", periodic=False): See those classes (located in diffpy.srfit.structure) for how they are used. The resulting ParameterSet will be managed by this generator. - Attributes + Parameters ---------- stru diffpy.structure.Structure, pyobjcryst.crystal.Crystal or @@ -117,7 +117,7 @@ def setPhase(self, parset, periodic=False): object (from diffpy or pyobjcryst). The passed ParameterSet will be managed by this generator. - Attributes + Parameters ---------- parset A SrRealParSet that holds the structural information. @@ -136,7 +136,7 @@ def __init__(self, name="pdf"): from diffpy.srreal.pdfcalculator import DebyePDFCalculator BasePDFGenerator.__init__(self, name) - self._setCalculator(DebyePDFCalculator()) + self._set_calculator(DebyePDFCalculator()) return diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index 6cb273b..e73243a 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -21,7 +21,7 @@ __all__ = ["PDFContribution"] -from diffpy.srfit.fitbase import FitContribution, Profile +from diffpy.srfit.fitbase import FitContribution, Profile, ProfileParser class PDFContribution(FitContribution): @@ -85,7 +85,7 @@ class PDFContribution(FitContribution): def __init__(self, name): """Create the PDFContribution. - Attributes + Parameters ---------- name The name of the contribution. @@ -94,7 +94,7 @@ def __init__(self, name): self._meta = {} # Add the profile profile = Profile() - self.setProfile(profile, xname="r") + self.set_profile(profile, xname="r") # Need a parameter for the overall scale, in the case that this is a # multi-phase fit. @@ -106,31 +106,19 @@ def __init__(self, name): # Data methods - def loadData(self, data): - """Load the data in various formats. - - This uses the PDFParser to load the data and then passes it to the - built-in profile with loadParsedData. + def loadData(self, datafile): + """Load the data from a datafile. - Attributes + Parameters ---------- - data - An open file-like object, name of a file that contains data - or a string containing the data. + data : str or Path + The path to the data file. """ - # Get the data into a string - from diffpy.srfit.util.inpututils import inputToString - - datstr = inputToString(data) - - # Load data with a PDFParser - from diffpy.srfit.pdf.pdfparser import PDFParser - - parser = PDFParser() - parser.parseString(datstr) + parser = ProfileParser() + parser.parse_file(datafile) # Pass it to the profile - self.profile.loadParsedData(parser) + self.profile.load_parsed_data(parser) return def setCalculationRange(self, xmin=None, xmax=None, dx=None): @@ -165,7 +153,7 @@ def setCalculationRange(self, xmin=None, xmax=None, dx=None): ValueError When xmin > xmax or if dx <= 0. Also if dx > xmax - xmin. """ - return self.profile.setCalculationRange(xmin, xmax, dx) + return self.profile.set_calculation_range(xmin, xmax, dx) def savetxt(self, fname, **kwargs): """Call numpy.savetxt with x, ycalc, y, dy. @@ -181,7 +169,7 @@ def savetxt(self, fname, **kwargs): def addStructure(self, name, stru, periodic=True): """Add a phase that goes into the PDF calculation. - Attributes + Parameters ---------- name A name to give the generator that will manage the PDF @@ -218,14 +206,14 @@ def addStructure(self, name, stru, periodic=True): # Set up the generator gen.setStructure(stru, "phase", periodic) - self._setupGenerator(gen) + self._setup_generator(gen) return gen.phase def addPhase(self, name, parset, periodic=True): """Add a phase that goes into the PDF calculation. - Attributes + Parameters ---------- name A name to give the generator that will manage the PDF @@ -263,38 +251,38 @@ def addPhase(self, name, parset, periodic=True): # Set up the generator gen.setPhase(parset, periodic) - self._setupGenerator(gen) + self._setup_generator(gen) return gen.phase - def _setupGenerator(self, gen): + def _setup_generator(self, gen): """Setup a generator. The generator must already have a managed SrRealParSet, added with setStructure or setPhase. """ # Add the generator to this FitContribution - self.addProfileGenerator(gen) + self.add_profile_generator(gen) # Set the proper equation for the fit, depending on the number of # phases we have. gnames = self._generators.keys() eqstr = " + ".join(gnames) eqstr = "scale * (%s)" % eqstr - self.setEquation(eqstr) + self.set_equation(eqstr) # Update with our metadata gen.meta.update(self._meta) - gen.processMetaData() + gen._process_metadata() # Constrain the shared parameters - self.constrain(gen.qdamp, self.qdamp) - self.constrain(gen.qbroad, self.qbroad) + self.add_constraint(gen.qdamp, self.qdamp) + self.add_constraint(gen.qbroad, self.qbroad) return # Calculation setup methods - def _getMetaValue(self, kwd): + def _get_meta_value(self, kwd): """Get metadata according to object hierarchy.""" # Check self, then generators then profile if kwd in self._meta: @@ -308,7 +296,7 @@ def _getMetaValue(self, kwd): def setScatteringType(self, type="X"): """Set the scattering type. - Attributes + Parameters ---------- type "X" for x-ray or "N" for neutron @@ -325,7 +313,7 @@ def getScatteringType(self): See 'setScatteringType'. """ - return self._getMetaValue("stype") + return self._get_meta_value("stype") def setQmax(self, qmax): """Set the qmax value.""" @@ -336,7 +324,7 @@ def setQmax(self, qmax): def getQmax(self): """Get the qmax value.""" - return self._getMetaValue("qmax") + return self._get_meta_value("qmax") def setQmin(self, qmin): """Set the qmin value.""" @@ -347,7 +335,7 @@ def setQmin(self, qmin): def getQmin(self): """Get the qmin value.""" - return self._getMetaValue("qmin") + return self._get_meta_value("qmin") # End of file diff --git a/src/diffpy/cmipdf/pdfgenerator.py b/src/diffpy/cmipdf/pdfgenerator.py index eb9054b..4b190a6 100644 --- a/src/diffpy/cmipdf/pdfgenerator.py +++ b/src/diffpy/cmipdf/pdfgenerator.py @@ -89,7 +89,7 @@ def __init__(self, name="pdf"): from diffpy.srreal.pdfcalculator import PDFCalculator BasePDFGenerator.__init__(self, name) - self._setCalculator(PDFCalculator()) + self._set_calculator(PDFCalculator()) return From 67d8e7a0ad03bb5b1d2075a4c79571abece48be9 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:11:55 -0400 Subject: [PATCH 04/30] remove pdfparser in favor of ProfileParser --- src/diffpy/cmipdf/pdfparser.py | 260 --------------------------------- 1 file changed, 260 deletions(-) delete mode 100644 src/diffpy/cmipdf/pdfparser.py diff --git a/src/diffpy/cmipdf/pdfparser.py b/src/diffpy/cmipdf/pdfparser.py deleted file mode 100644 index e1eceff..0000000 --- a/src/diffpy/cmipdf/pdfparser.py +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# (c) 2025 Simon Billinge. -# All rights reserved. -# -# File coded by: Caden Myers, Simon Billinge, and members of the Billinge -# group. -# -# See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.cmipdf/graphs/contributors -# -# See LICENSE.rst for license information. -# -############################################################################## -"""This module contains parsers for PDF data. - -PDFParser is suitable for parsing data generated from PDFGetN and -PDFGetX. - -See the class documentation for more information. -""" - -__all__ = ["PDFParser"] - -import re - -import numpy - -from diffpy.srfit.exceptions import ParseError -from diffpy.srfit.fitbase.profileparser import ProfileParser - - -class PDFParser(ProfileParser): - """Class for holding a diffraction pattern. - - Attributes - - Attributes - ---------- - _format - Name of the data format that this parses (string, default - ""). The format string is a unique identifier for the data - format handled by the parser. - _banks - The data from each bank. Each bank contains a - (x, y, dx, dy) tuple: - x - A numpy array containing the independent - variable read from the file. - y - A numpy array containing the profile - from the file. - dx - A numpy array containing the uncertainty in x - read from the file. This is 0 if the - uncertainty cannot be read. - dy - A numpy array containing the uncertainty read - from the file. This is 0 if the uncertainty - cannot be read. - _x - Independent variable from the chosen bank - _y - Profile from the chosen bank - _dx - Uncertainty in independent variable from the chosen bank - _dy - Uncertainty in profile from the chosen bank - _meta - A dictionary containing metadata read from the file. - - General Metadata - - Attributes - ---------- - filename - The name of the file from which data was parsed. This key - will not exist if data was not read from file. - nbanks - The number of banks parsed. - bank - The chosen bank number. - - Metadata - ---------- - stype - The scattering type ("X", "N") - qmin - Minimum scattering vector (float) - qmax - Maximum scattering vector (float) - qdamp - Resolution damping factor (float) - qbroad - Resolution broadening factor (float) - spdiameter - Nanoparticle diameter (float) - scale - Data scale (float) - temperature - Temperature (float) - doping - Doping (float) - - - These may appear in the metadata dictionary. - """ - - _format = "PDF" - - def parseString(self, patstring): - """Parse a string and set the _x, _y, _dx, _dy and _meta - variables. - - When _dx or _dy cannot be obtained in the data format it is set to 0. - - This wipes out the currently loaded data and selected bank number. - - Parameters - ---------- - patstring - A string containing the pattern - - Raises ParseError if the string cannot be parsed - """ - # useful regex patterns: - rx = {"f": r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?"} - # find where does the data start - res = re.search(r"^#+ start data\s*(?:#.*\s+)*", patstring, re.M) - # start_data is position where the first data line starts - if res: - start_data = res.end() - else: - # find line that starts with a floating point number - regexp = r"^\s*%(f)s" % rx - res = re.search(regexp, patstring, re.M) - if res: - start_data = res.start() - else: - start_data = 0 - header = patstring[:start_data] - databody = patstring[start_data:].strip() - - # find where the metadata starts - metadata = "" - res = re.search(r"^#+\ +metadata\b\n", header, re.M) - if res: - metadata = header[res.end() :] - header = header[: res.start()] - - # parse header - meta = self._meta - # stype - if re.search("(x-?ray|PDFgetX)", header, re.I): - meta["stype"] = "X" - elif re.search("(neutron|PDFgetN)", header, re.I): - meta["stype"] = "N" - # qmin - regexp = r"\bqmin *= *(%(f)s)\b" % rx - res = re.search(regexp, header, re.I) - if res: - meta["qmin"] = float(res.groups()[0]) - # qmax - regexp = r"\bqmax *= *(%(f)s)\b" % rx - res = re.search(regexp, header, re.I) - if res: - meta["qmax"] = float(res.groups()[0]) - # qdamp - regexp = r"\b(?:qdamp|qsig) *= *(%(f)s)\b" % rx - res = re.search(regexp, header, re.I) - if res: - meta["qdamp"] = float(res.groups()[0]) - # qbroad - regexp = r"\b(?:qbroad|qalp) *= *(%(f)s)\b" % rx - res = re.search(regexp, header, re.I) - if res: - meta["qbroad"] = float(res.groups()[0]) - # spdiameter - regexp = r"\bspdiameter *= *(%(f)s)\b" % rx - res = re.search(regexp, header, re.I) - if res: - meta["spdiameter"] = float(res.groups()[0]) - # dscale - regexp = r"\bdscale *= *(%(f)s)\b" % rx - res = re.search(regexp, header, re.I) - if res: - meta["scale"] = float(res.groups()[0]) - # temperature - regexp = r"\b(?:temp|temperature|T)\ *=\ *(%(f)s)\b" % rx - res = re.search(regexp, header) - if res: - meta["temperature"] = float(res.groups()[0]) - # doping - regexp = r"\b(?:x|doping)\ *=\ *(%(f)s)\b" % rx - res = re.search(regexp, header) - if res: - meta["doping"] = float(res.groups()[0]) - - # parsing general metadata - if metadata: - regexp = r"\b(\w+)\ *=\ *(%(f)s)\b" % rx - while True: - res = re.search(regexp, metadata, re.M) - if res: - meta[res.groups()[0]] = float(res.groups()[1]) - metadata = metadata[res.end() :] - else: - break - - # read actual data - robs, Gobs, drobs, dGobs - inf_or_nan = re.compile("(?i)^[+-]?(NaN|Inf)\\b") - has_drobs = True - has_dGobs = True - # raise ParseError if something goes wrong - robs = [] - Gobs = [] - drobs = [] - dGobs = [] - try: - for line in databody.split("\n"): - v = line.split() - # there should be at least 2 value in the line - robs.append(float(v[0])) - Gobs.append(float(v[1])) - # drobs is valid if all values are defined and positive - has_drobs = ( - has_drobs and len(v) > 2 and not inf_or_nan.match(v[2]) - ) - if has_drobs: - v2 = float(v[2]) - has_drobs = v2 > 0.0 - drobs.append(v2) - # dGobs is valid if all values are defined and positive - has_dGobs = ( - has_dGobs and len(v) > 3 and not inf_or_nan.match(v[3]) - ) - if has_dGobs: - v3 = float(v[3]) - has_dGobs = v3 > 0.0 - dGobs.append(v3) - except (ValueError, IndexError) as err: - raise ParseError(err) - if has_drobs: - drobs = numpy.asarray(drobs) - else: - drobs = None - if has_dGobs: - dGobs = numpy.asarray(dGobs) - else: - dGobs = None - - robs = numpy.asarray(robs) - Gobs = numpy.asarray(Gobs) - - self._banks.append([robs, Gobs, drobs, dGobs]) - return - - -# End of PDFParser From a0f55cd83518c8bb324a28789ceed6f37d4cb92e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:13:03 -0400 Subject: [PATCH 05/30] setScatteringType --> set_scattering_type --- src/diffpy/cmipdf/basepdfgenerator.py | 8 ++++---- src/diffpy/cmipdf/debyepdfgenerator.py | 2 +- src/diffpy/cmipdf/pdfcontribution.py | 6 +++--- src/diffpy/cmipdf/pdfgenerator.py | 2 +- tests/test_pdf.py | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index 6965b56..b5fcea6 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -74,7 +74,7 @@ class BasePDFGenerator(ProfileGenerator): --------------- stype The scattering type "X" for x-ray, "N" for neutron (see - 'setScatteringType'). + 'set_scattering_type'). qmax The maximum scattering vector used to generate the PDF (see setQmax). @@ -161,7 +161,7 @@ def _process_metadata(self): stype = self.meta.get("stype") if stype is not None: - self.setScatteringType(stype) + self.set_scattering_type(stype) qmax = self.meta.get("qmax") if qmax is not None: @@ -179,7 +179,7 @@ def _process_metadata(self): return - def setScatteringType(self, stype="X"): + def set_scattering_type(self, stype="X"): """Set the scattering type. Parameters @@ -199,7 +199,7 @@ def setScatteringType(self, stype="X"): def getScatteringType(self): """Get the scattering type. - See 'setScatteringType'. + See 'set_scattering_type'. """ return self._calc.getRadiationType() diff --git a/src/diffpy/cmipdf/debyepdfgenerator.py b/src/diffpy/cmipdf/debyepdfgenerator.py index f60404e..61b6c53 100644 --- a/src/diffpy/cmipdf/debyepdfgenerator.py +++ b/src/diffpy/cmipdf/debyepdfgenerator.py @@ -66,7 +66,7 @@ class DebyePDFGenerator(BasePDFGenerator): --------------- stype The scattering type "X" for x-ray, "N" for neutron (see - 'setScatteringType'). + 'set_scattering_type'). qmax The maximum scattering vector used to generate the PDF (see setQmax). diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index e73243a..95c957a 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -293,7 +293,7 @@ def _get_meta_value(self, kwd): val = self.profile.meta.get(kwd) return val - def setScatteringType(self, type="X"): + def set_scattering_type(self, type="X"): """Set the scattering type. Parameters @@ -305,13 +305,13 @@ def setScatteringType(self, type="X"): """ self._meta["stype"] = type for gen in self._generators.values(): - gen.setScatteringType(type) + gen.set_scattering_type(type) return def getScatteringType(self): """Get the scattering type. - See 'setScatteringType'. + See 'set_scattering_type'. """ return self._get_meta_value("stype") diff --git a/src/diffpy/cmipdf/pdfgenerator.py b/src/diffpy/cmipdf/pdfgenerator.py index 4b190a6..a6de704 100644 --- a/src/diffpy/cmipdf/pdfgenerator.py +++ b/src/diffpy/cmipdf/pdfgenerator.py @@ -65,7 +65,7 @@ class PDFGenerator(BasePDFGenerator): --------------- stype The scattering type "X" for x-ray, "N" for neutron (see - 'setScatteringType'). + 'set_scattering_type'). qmax The maximum scattering vector used to generate the PDF (see setQmax). diff --git a/tests/test_pdf.py b/tests/test_pdf.py index b64071d..600e52a 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -154,7 +154,7 @@ def testGenerator( qmax = 27.0 gen = PDFGenerator() - gen.setScatteringType("N") + gen.set_scattering_type("N") assert "N" == gen.getScatteringType() gen.setQmax(qmax) assert qmax == pytest.approx(gen.getQmax()) From f0521283aad43d2eec951f9665fd1e0ffa0693a2 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:14:02 -0400 Subject: [PATCH 06/30] getScatteringType --> get_scattering_type --- src/diffpy/cmipdf/basepdfgenerator.py | 4 ++-- src/diffpy/cmipdf/pdfcontribution.py | 2 +- tests/test_pdf.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index b5fcea6..b6dfcbb 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -193,10 +193,10 @@ def set_scattering_type(self, stype="X"): """ self._calc.setScatteringFactorTableByType(stype) # update the meta dictionary only if there was no exception - self.meta["stype"] = self.getScatteringType() + self.meta["stype"] = self.get_scattering_type() return - def getScatteringType(self): + def get_scattering_type(self): """Get the scattering type. See 'set_scattering_type'. diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index 95c957a..1c909ef 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -308,7 +308,7 @@ def set_scattering_type(self, type="X"): gen.set_scattering_type(type) return - def getScatteringType(self): + def get_scattering_type(self): """Get the scattering type. See 'set_scattering_type'. diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 600e52a..abde938 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -155,7 +155,7 @@ def testGenerator( qmax = 27.0 gen = PDFGenerator() gen.set_scattering_type("N") - assert "N" == gen.getScatteringType() + assert "N" == gen.get_scattering_type() gen.setQmax(qmax) assert qmax == pytest.approx(gen.getQmax()) From 7fe7f08e86e6d9761a800e5185d8cf3e35182941 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:14:39 -0400 Subject: [PATCH 07/30] setQmax --> set_qmax --- src/diffpy/cmipdf/basepdfgenerator.py | 6 +++--- src/diffpy/cmipdf/debyepdfgenerator.py | 2 +- src/diffpy/cmipdf/pdfcontribution.py | 4 ++-- src/diffpy/cmipdf/pdfgenerator.py | 2 +- tests/test_pdf.py | 14 +++++++------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index b6dfcbb..e3b1cc9 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -77,7 +77,7 @@ class BasePDFGenerator(ProfileGenerator): 'set_scattering_type'). qmax The maximum scattering vector used to generate the PDF (see - setQmax). + set_qmax). qmin The minimum scattering vector used to generate the PDF (see setQmin). @@ -165,7 +165,7 @@ def _process_metadata(self): qmax = self.meta.get("qmax") if qmax is not None: - self.setQmax(qmax) + self.set_qmax(qmax) qmin = self.meta.get("qmin") if qmin is not None: @@ -203,7 +203,7 @@ def get_scattering_type(self): """ return self._calc.getRadiationType() - def setQmax(self, qmax): + def set_qmax(self, qmax): """Set the qmax value.""" self._calc.qmax = qmax self.meta["qmax"] = self.getQmax() diff --git a/src/diffpy/cmipdf/debyepdfgenerator.py b/src/diffpy/cmipdf/debyepdfgenerator.py index 61b6c53..97e44ed 100644 --- a/src/diffpy/cmipdf/debyepdfgenerator.py +++ b/src/diffpy/cmipdf/debyepdfgenerator.py @@ -69,7 +69,7 @@ class DebyePDFGenerator(BasePDFGenerator): 'set_scattering_type'). qmax The maximum scattering vector used to generate the PDF (see - setQmax). + set_qmax). qmin The minimum scattering vector used to generate the PDF (see setQmin). diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index 1c909ef..740ef17 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -315,11 +315,11 @@ def get_scattering_type(self): """ return self._get_meta_value("stype") - def setQmax(self, qmax): + def set_qmax(self, qmax): """Set the qmax value.""" self._meta["qmax"] = qmax for gen in self._generators.values(): - gen.setQmax(qmax) + gen.set_qmax(qmax) return def getQmax(self): diff --git a/src/diffpy/cmipdf/pdfgenerator.py b/src/diffpy/cmipdf/pdfgenerator.py index a6de704..3b0ed63 100644 --- a/src/diffpy/cmipdf/pdfgenerator.py +++ b/src/diffpy/cmipdf/pdfgenerator.py @@ -68,7 +68,7 @@ class PDFGenerator(BasePDFGenerator): 'set_scattering_type'). qmax The maximum scattering vector used to generate the PDF (see - setQmax). + set_qmax). qmin The minimum scattering vector used to generate the PDF (see setQmin). diff --git a/tests/test_pdf.py b/tests/test_pdf.py index abde938..b474f88 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -156,7 +156,7 @@ def testGenerator( gen = PDFGenerator() gen.set_scattering_type("N") assert "N" == gen.get_scattering_type() - gen.setQmax(qmax) + gen.set_qmax(qmax) assert qmax == pytest.approx(gen.getQmax()) stru = PDFFitStructure() @@ -215,8 +215,8 @@ def test_setQmin(diffpy_structure_available, diffpy_srreal_available): return -def test_setQmax(diffpy_structure_available, diffpy_srreal_available): - """Check PDFContribution.setQmax()""" +def test_set_qmax(diffpy_structure_available, diffpy_srreal_available): + """Check PDFContribution.set_qmax()""" if not diffpy_structure_available: pytest.skip("diffpy.structure package not available") from diffpy.structure import Structure @@ -225,10 +225,10 @@ def test_setQmax(diffpy_structure_available, diffpy_srreal_available): pytest.skip("diffpy.srreal package not available") pc = PDFContribution("pdf") - pc.setQmax(21) + pc.set_qmax(21) pc.addStructure("empty", Structure()) assert 21 == pc.empty.getQmax() - pc.setQmax(22) + pc.set_qmax(22) assert 22 == pc.getQmax() assert 22 == pc.empty.getQmax() return @@ -247,12 +247,12 @@ def test_getQmax(diffpy_structure_available, diffpy_srreal_available): # (1) contribution metadata pc1 = PDFContribution("pdf") assert pc1.getQmax() is None - pc1.setQmax(17) + pc1.set_qmax(17) assert 17 == pc1.getQmax() # (2) contribution metadata pc2 = PDFContribution("pdf") pc2.addStructure("empty", Structure()) - pc2.empty.setQmax(18) + pc2.empty.set_qmax(18) assert 18 == pc2.getQmax() # (3) profile metadata pc3 = PDFContribution("pdf") From efae5b262acc2481aaf825e164a39a829bdcbe8e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:18:31 -0400 Subject: [PATCH 08/30] copy over test_pdf.py --- tests/test_pdf.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/tests/test_pdf.py b/tests/test_pdf.py index b474f88..614b110 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -25,6 +25,7 @@ from diffpy.cmipdf import PDFContribution, PDFGenerator, PDFParser from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase import ProfileParser # ---------------------------------------------------------------------------- @@ -47,7 +48,7 @@ def testParser1(datafile): assert meta.get("scale") is None assert meta.get("doping") is None - x, y, dx, dy = parser.getData() + x, y, dx, dy = parser.get_data() assert dx is None assert dy is None @@ -79,12 +80,12 @@ def testParser1(datafile): def testParser2(datafile): data = datafile("si-q27r60-xray.gr") - parser = PDFParser() - parser.parseFile(data) + parser = ProfileParser() + parser.parse_file(data) meta = parser._meta - assert data == meta["filename"] + assert str(data) == meta["filename"] assert 1 == meta["nbanks"] assert "X" == meta["stype"] assert 27 == meta["qmax"] @@ -95,7 +96,7 @@ def testParser2(datafile): assert meta.get("scale") is None assert meta.get("doping") is None - x, y, dx, dy = parser.getData() + x, y, dx, dy = parser.get_data() testx = numpy.linspace(0.01, 60, 5999, endpoint=False) diff = testx - x res = numpy.dot(diff, diff) @@ -137,7 +138,7 @@ def testParser2(datafile): res = numpy.dot(diff, diff) assert 0 == pytest.approx(res) - assert dx is None + assert dx.tolist() == [0] * len(dx) return @@ -154,9 +155,9 @@ def testGenerator( qmax = 27.0 gen = PDFGenerator() - gen.set_scattering_type("N") - assert "N" == gen.get_scattering_type() - gen.set_qmax(qmax) + gen.setScatteringType("N") + assert "N" == gen.getScatteringType() + gen.setQmax(qmax) assert qmax == pytest.approx(gen.getQmax()) stru = PDFFitStructure() @@ -174,9 +175,9 @@ def testGenerator( defval = calc._getDoubleAttr(pname) assert defval == par.getValue() # Test setting values - par.setValue(1.0) + par.set_value(1.0) assert 1.0 == par.getValue() - par.setValue(defval) + par.set_value(defval) assert defval == par.getValue() r = numpy.arange(0, 10, 0.1) @@ -215,8 +216,8 @@ def test_setQmin(diffpy_structure_available, diffpy_srreal_available): return -def test_set_qmax(diffpy_structure_available, diffpy_srreal_available): - """Check PDFContribution.set_qmax()""" +def test_setQmax(diffpy_structure_available, diffpy_srreal_available): + """Check PDFContribution.setQmax()""" if not diffpy_structure_available: pytest.skip("diffpy.structure package not available") from diffpy.structure import Structure @@ -225,10 +226,10 @@ def test_set_qmax(diffpy_structure_available, diffpy_srreal_available): pytest.skip("diffpy.srreal package not available") pc = PDFContribution("pdf") - pc.set_qmax(21) + pc.setQmax(21) pc.addStructure("empty", Structure()) assert 21 == pc.empty.getQmax() - pc.set_qmax(22) + pc.setQmax(22) assert 22 == pc.getQmax() assert 22 == pc.empty.getQmax() return @@ -243,16 +244,16 @@ def test_getQmax(diffpy_structure_available, diffpy_srreal_available): if not diffpy_srreal_available: pytest.skip("diffpy.srreal package not available") - # cover all code branches in PDFContribution._getMetaValue + # cover all code branches in PDFContribution._get_meta_value # (1) contribution metadata pc1 = PDFContribution("pdf") assert pc1.getQmax() is None - pc1.set_qmax(17) + pc1.setQmax(17) assert 17 == pc1.getQmax() # (2) contribution metadata pc2 = PDFContribution("pdf") pc2.addStructure("empty", Structure()) - pc2.empty.set_qmax(18) + pc2.empty.setQmax(18) assert 18 == pc2.getQmax() # (3) profile metadata pc3 = PDFContribution("pdf") @@ -309,7 +310,9 @@ def test_pickling( pc2 = pickle.loads(pickle.dumps(pc)) res0 = pc.residual() assert numpy.array_equal(res0, pc2.residual()) - for p in chain(pc.iterPars("Uiso"), pc2.iterPars("Uiso")): + for p in chain( + pc.iterate_over_parameters("Uiso"), pc2.iterate_over_parameters("Uiso") + ): p.value = 0.004 res1 = pc.residual() assert not numpy.allclose(res0, res1) From f45e5cf189f830908fd3889f2ac7292e22d4222e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:20:29 -0400 Subject: [PATCH 09/30] docstring improvement --- src/diffpy/cmipdf/basepdfgenerator.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index e3b1cc9..e6e3439 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -72,7 +72,7 @@ class BasePDFGenerator(ProfileGenerator): Usable Metadata --------------- - stype + scattering_type : str The scattering type "X" for x-ray, "N" for neutron (see 'set_scattering_type'). qmax @@ -159,9 +159,9 @@ def _process_metadata(self): """Process the metadata once it gets set.""" ProfileGenerator._process_metadata(self) - stype = self.meta.get("stype") - if stype is not None: - self.set_scattering_type(stype) + scattering_type = self.meta.get("scattering_type") + if scattering_type is not None: + self.set_scattering_type(scattering_type) qmax = self.meta.get("qmax") if qmax is not None: @@ -179,21 +179,25 @@ def _process_metadata(self): return - def set_scattering_type(self, stype="X"): + def set_scattering_type(self, scattering_type="X"): """Set the scattering type. Parameters ---------- - stype - "X" for x-ray, "N" for neutron, "E" for electrons, + scattering_type : str, optional + The scattering type. Default is `"X"`. + `"X"` for x-ray, `"N"` for neutron, `"E"` for electrons, or any registered type from diffpy.srreal from ScatteringFactorTable.getRegisteredTypes(). - Raises ValueError for unknown scattering type. + Raises + ------ + ValueError + If the scattering type is unknown. """ - self._calc.setScatteringFactorTableByType(stype) + self._calc.setScatteringFactorTableByType(scattering_type) # update the meta dictionary only if there was no exception - self.meta["stype"] = self.get_scattering_type() + self.meta["scattering_type"] = self.get_scattering_type() return def get_scattering_type(self): From dba885267b5c75f996d8b892481eca159fa7425b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:22:40 -0400 Subject: [PATCH 10/30] getQmax --> get_qmax --- src/diffpy/cmipdf/basepdfgenerator.py | 20 ++++++++++++++++---- src/diffpy/cmipdf/pdfcontribution.py | 2 +- tests/test_pdf.py | 20 ++++++++++---------- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index e6e3439..c6970b7 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -208,13 +208,25 @@ def get_scattering_type(self): return self._calc.getRadiationType() def set_qmax(self, qmax): - """Set the qmax value.""" + """Set the qmax value. + + Parameters + ---------- + qmax : float + The maximum scattering vector used to generate the PDF. + """ self._calc.qmax = qmax - self.meta["qmax"] = self.getQmax() + self.meta["qmax"] = self.get_qmax() return - def getQmax(self): - """Get the qmax value.""" + def get_qmax(self): + """Get the qmax value. + + Returns + ------- + float + The maximum scattering vector used to generate the PDF. + """ return self._calc.qmax def setQmin(self, qmin): diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index 740ef17..e022ca8 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -322,7 +322,7 @@ def set_qmax(self, qmax): gen.set_qmax(qmax) return - def getQmax(self): + def get_qmax(self): """Get the qmax value.""" return self._get_meta_value("qmax") diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 614b110..cd22b01 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -158,7 +158,7 @@ def testGenerator( gen.setScatteringType("N") assert "N" == gen.getScatteringType() gen.setQmax(qmax) - assert qmax == pytest.approx(gen.getQmax()) + assert qmax == pytest.approx(gen.get_qmax()) stru = PDFFitStructure() ciffile = datafile("ni.cif") @@ -228,15 +228,15 @@ def test_setQmax(diffpy_structure_available, diffpy_srreal_available): pc = PDFContribution("pdf") pc.setQmax(21) pc.addStructure("empty", Structure()) - assert 21 == pc.empty.getQmax() + assert 21 == pc.empty.get_qmax() pc.setQmax(22) - assert 22 == pc.getQmax() - assert 22 == pc.empty.getQmax() + assert 22 == pc.get_qmax() + assert 22 == pc.empty.get_qmax() return -def test_getQmax(diffpy_structure_available, diffpy_srreal_available): - """Check PDFContribution.getQmax()""" +def test_get_qmax(diffpy_structure_available, diffpy_srreal_available): + """Check PDFContribution.get_qmax()""" if not diffpy_structure_available: pytest.skip("diffpy.structure package not available") from diffpy.structure import Structure @@ -247,18 +247,18 @@ def test_getQmax(diffpy_structure_available, diffpy_srreal_available): # cover all code branches in PDFContribution._get_meta_value # (1) contribution metadata pc1 = PDFContribution("pdf") - assert pc1.getQmax() is None + assert pc1.get_qmax() is None pc1.setQmax(17) - assert 17 == pc1.getQmax() + assert 17 == pc1.get_qmax() # (2) contribution metadata pc2 = PDFContribution("pdf") pc2.addStructure("empty", Structure()) pc2.empty.setQmax(18) - assert 18 == pc2.getQmax() + assert 18 == pc2.get_qmax() # (3) profile metadata pc3 = PDFContribution("pdf") pc3.profile.meta["qmax"] = 19 - assert 19 == pc3.getQmax() + assert 19 == pc3.get_qmax() return From e27133a7143c5cf1dbdabc342750eccdea7e579e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:24:33 -0400 Subject: [PATCH 11/30] setQmin --> set_qmin --- src/diffpy/cmipdf/basepdfgenerator.py | 16 +++++++++++----- src/diffpy/cmipdf/debyepdfgenerator.py | 2 +- src/diffpy/cmipdf/pdfcontribution.py | 6 +++--- src/diffpy/cmipdf/pdfgenerator.py | 2 +- tests/test_pdf.py | 8 ++++---- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index c6970b7..0090fbc 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -80,7 +80,7 @@ class BasePDFGenerator(ProfileGenerator): set_qmax). qmin The minimum scattering vector used to generate the PDF (see - setQmin). + set_qmin). scale See Managed Parameters. delta1 @@ -169,7 +169,7 @@ def _process_metadata(self): qmin = self.meta.get("qmin") if qmin is not None: - self.setQmin(qmin) + self.set_qmin(qmin) for name in self.__class__._parnames: val = self.meta.get(name) @@ -229,10 +229,16 @@ def get_qmax(self): """ return self._calc.qmax - def setQmin(self, qmin): - """Set the qmin value.""" + def set_qmin(self, qmin): + """Set the qmin value. + + Parameters + ---------- + qmin : float + The minimum scattering vector used to generate the PDF. + """ self._calc.qmin = qmin - self.meta["qmin"] = self.getQmin() + self.meta["qmin"] = self.get_qmin() return def getQmin(self): diff --git a/src/diffpy/cmipdf/debyepdfgenerator.py b/src/diffpy/cmipdf/debyepdfgenerator.py index 97e44ed..e43c573 100644 --- a/src/diffpy/cmipdf/debyepdfgenerator.py +++ b/src/diffpy/cmipdf/debyepdfgenerator.py @@ -72,7 +72,7 @@ class DebyePDFGenerator(BasePDFGenerator): set_qmax). qmin The minimum scattering vector used to generate the PDF (see - setQmin). + set_qmin). scale See Managed Parameters. delta1 diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index e022ca8..e4eccf8 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -326,14 +326,14 @@ def get_qmax(self): """Get the qmax value.""" return self._get_meta_value("qmax") - def setQmin(self, qmin): + def set_qmin(self, qmin): """Set the qmin value.""" self._meta["qmin"] = qmin for gen in self._generators.values(): - gen.setQmin(qmin) + gen.set_qmin(qmin) return - def getQmin(self): + def get_qmin(self): """Get the qmin value.""" return self._get_meta_value("qmin") diff --git a/src/diffpy/cmipdf/pdfgenerator.py b/src/diffpy/cmipdf/pdfgenerator.py index 3b0ed63..ebfe2e6 100644 --- a/src/diffpy/cmipdf/pdfgenerator.py +++ b/src/diffpy/cmipdf/pdfgenerator.py @@ -71,7 +71,7 @@ class PDFGenerator(BasePDFGenerator): set_qmax). qmin The minimum scattering vector used to generate the PDF (see - setQmin). + set_qmin). scale See Managed Parameters. delta1 diff --git a/tests/test_pdf.py b/tests/test_pdf.py index cd22b01..abbd6b0 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -202,16 +202,16 @@ def testGenerator( return -def test_setQmin(diffpy_structure_available, diffpy_srreal_available): +def test_set_qmin(diffpy_structure_available, diffpy_srreal_available): """Verify qmin is propagated to the calculator object.""" if not diffpy_srreal_available: pytest.skip("diffpy.srreal package not available") gen = PDFGenerator() - assert 0 == gen.getQmin() + assert 0 == gen.get_qmin() assert 0 == gen._calc.qmin - gen.setQmin(0.93) - assert 0.93 == gen.getQmin() + gen.set_qmin(0.93) + assert 0.93 == gen.get_qmin() assert 0.93 == gen._calc.qmin return From 4b41c42c4296397007533aea19601d0dbfc31308 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:26:08 -0400 Subject: [PATCH 12/30] getQmin --> get_qmin --- src/diffpy/cmipdf/basepdfgenerator.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index 0090fbc..a1a4ea6 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -241,8 +241,14 @@ def set_qmin(self, qmin): self.meta["qmin"] = self.get_qmin() return - def getQmin(self): - """Get the qmin value.""" + def get_qmin(self): + """Get the qmin value. + + Returns + ------- + float + The minimum scattering vector used to generate the PDF. + """ return self._calc.qmin def setStructure(self, stru, name="phase", periodic=True): From fc568957cdca3dcb9ac181d15f2747bcedc3181e Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:28:40 -0400 Subject: [PATCH 13/30] change stru to structure everywhere --- src/diffpy/cmipdf/basepdfgenerator.py | 17 +++++++++-------- src/diffpy/cmipdf/debyepdfgenerator.py | 13 +++++++------ src/diffpy/cmipdf/pdfcontribution.py | 12 ++++++------ tests/test_pdf.py | 10 +++++----- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index a1a4ea6..bca8ec2 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -46,7 +46,7 @@ class BasePDFGenerator(ProfileGenerator): the PDF. _phase The structure ParameterSet used to calculate the profile. - stru + structure The structure objected adapted by _phase. _lastr The last value of r over which the PDF was calculated. This is @@ -98,7 +98,7 @@ def __init__(self, name="pdf"): ProfileGenerator.__init__(self, name) self._phase = None - self.stru = None + self.structure = None self.meta = {} self._lastr = numpy.empty(0) self._calc = None @@ -251,21 +251,22 @@ def get_qmin(self): """ return self._calc.qmin - def setStructure(self, stru, name="phase", periodic=True): + def set_structure(self, structure, name="phase", periodic=True): """Set the structure that will be used to calculate the PDF. This creates a DiffpyStructureParSet, ObjCrystCrystalParSet or - ObjCrystMoleculeParSet that adapts stru to a ParameterSet interface. + ObjCrystMoleculeParSet that adapts structure to a ParameterSet + interface. See those classes (located in diffpy.srfit.structure) for how they are used. The resulting ParameterSet will be managed by this generator. Parameters ---------- - stru + structure diffpy.structure.Structure, pyobjcryst.crystal.Crystal or pyobjcryst.molecule.Molecule instance. Default None. name - A name to give to the managed ParameterSet that adapts stru + A name to give to the managed ParameterSet that adapts structure (default "phase"). periodic The structure should be treated as periodic (default @@ -275,7 +276,7 @@ def setStructure(self, stru, name="phase", periodic=True): """ # Create the ParameterSet - parset = struToParameterSet(name, stru) + parset = struToParameterSet(name, structure) # Set the phase self.setPhase(parset, periodic) @@ -303,7 +304,7 @@ def setPhase(self, parset, periodic=True): """ # Store the ParameterSet for easy access self._phase = parset - self.stru = self._phase.stru + self.structure = self._phase.structure # Put this ParameterSet in the ProfileGenerator. self.add_parameter_set(parset) diff --git a/src/diffpy/cmipdf/debyepdfgenerator.py b/src/diffpy/cmipdf/debyepdfgenerator.py index e43c573..8ebe77f 100644 --- a/src/diffpy/cmipdf/debyepdfgenerator.py +++ b/src/diffpy/cmipdf/debyepdfgenerator.py @@ -39,7 +39,7 @@ class DebyePDFGenerator(BasePDFGenerator): DebyePDFCalculator instance for calculating the PDF _phase The structure ParameterSets used to calculate the profile. - stru + structure The structure objected adapted by _phase. _lastr The last value of r over which the PDF was calculated. This is @@ -85,21 +85,22 @@ class DebyePDFGenerator(BasePDFGenerator): See Managed Parameters. """ - def setStructure(self, stru, name="phase", periodic=False): + def set_structure(self, structure, name="phase", periodic=False): """Set the structure that will be used to calculate the PDF. This creates a DiffpyStructureParSet, ObjCrystCrystalParSet or - ObjCrystMoleculeParSet that adapts stru to a ParameterSet interface. + ObjCrystMoleculeParSet that adapts structure to a ParameterSet + interface. See those classes (located in diffpy.srfit.structure) for how they are used. The resulting ParameterSet will be managed by this generator. Parameters ---------- - stru + structure diffpy.structure.Structure, pyobjcryst.crystal.Crystal or pyobjcryst.molecule.Molecule instance. Default None. name - A name to give to the managed ParameterSet that adapts stru + A name to give to the managed ParameterSet that adapts structure (default "phase"). periodic The structure should be treated as periodic (default @@ -107,7 +108,7 @@ def setStructure(self, stru, name="phase", periodic=False): periodicity, in which case this will have no effect on the PDF calculation. """ - return BasePDFGenerator.setStructure(self, stru, name, periodic) + return BasePDFGenerator.set_structure(self, structure, name, periodic) def setPhase(self, parset, periodic=False): """Set the phase that will be used to calculate the PDF. diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index e4eccf8..6f42f85 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -166,7 +166,7 @@ def savetxt(self, fname, **kwargs): # Phase methods - def addStructure(self, name, stru, periodic=True): + def addStructure(self, name, structure, periodic=True): """Add a phase that goes into the PDF calculation. Parameters @@ -179,7 +179,7 @@ def addStructure(self, name, stru, periodic=True): contribution.name.phase, where 'contribution' is this contribution and 'name' is passed name. (default), then the name will be set as "phase". - stru + structure diffpy.structure.Structure, pyobjcryst.crystal.Crystal or pyobjcryst.molecule.Molecule instance. Default None. periodic @@ -192,7 +192,7 @@ def addStructure(self, name, stru, periodic=True): Returns the new phase (ParameterSet appropriate for what was passed in - stru.) + structure.) """ # Based on periodic, create the proper generator. if periodic: @@ -205,7 +205,7 @@ def addStructure(self, name, stru, periodic=True): gen = DebyePDFGenerator(name) # Set up the generator - gen.setStructure(stru, "phase", periodic) + gen.set_structure(structure, "phase", periodic) self._setup_generator(gen) return gen.phase @@ -237,7 +237,7 @@ def addPhase(self, name, parset, periodic=True): Returns the new phase (ParameterSet appropriate for what was passed in - stru.) + structure.) """ # Based on periodic, create the proper generator. if periodic: @@ -259,7 +259,7 @@ def _setup_generator(self, gen): """Setup a generator. The generator must already have a managed SrRealParSet, added - with setStructure or setPhase. + with set_structure or setPhase. """ # Add the generator to this FitContribution self.add_profile_generator(gen) diff --git a/tests/test_pdf.py b/tests/test_pdf.py index abbd6b0..f5befce 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -160,13 +160,13 @@ def testGenerator( gen.setQmax(qmax) assert qmax == pytest.approx(gen.get_qmax()) - stru = PDFFitStructure() + structure = PDFFitStructure() ciffile = datafile("ni.cif") cif_path = str(ciffile) - stru.read(cif_path) + structure.read(cif_path) for i in range(4): - stru[i].Bisoequiv = 1 - gen.setStructure(stru) + structure[i].Bisoequiv = 1 + gen.set_structure(structure) calc = gen._calc # Test parameters @@ -193,7 +193,7 @@ def testGenerator( calc.rmax = r[-1] + 0.5 * calc.rstep calc.qmax = qmax calc.setScatteringFactorTableByType("N") - calc.eval(stru) + calc.eval(structure) yref = calc.pdf diff = y - yref From d2dd56b6c0374af422aa99c6b55085ba1c6a8719 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 18 Mar 2026 12:36:26 -0400 Subject: [PATCH 14/30] setPhase --> set_structure_from_parset --- src/diffpy/cmipdf/basepdfgenerator.py | 18 +++++++++--------- src/diffpy/cmipdf/debyepdfgenerator.py | 6 ++++-- src/diffpy/cmipdf/pdfcontribution.py | 6 +++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index bca8ec2..407b9da 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -247,7 +247,7 @@ def get_qmin(self): Returns ------- float - The minimum scattering vector used to generate the PDF. + The minimum scattering vector used to generate the PDF. """ return self._calc.qmin @@ -262,13 +262,13 @@ def set_structure(self, structure, name="phase", periodic=True): Parameters ---------- - structure - diffpy.structure.Structure, pyobjcryst.crystal.Crystal or - pyobjcryst.molecule.Molecule instance. Default None. - name - A name to give to the managed ParameterSet that adapts structure + structure : Structure or Crystal or Molecule + The diffpy.structure.Structure, pyobjcryst.crystal.Crystal or + pyobjcryst.molecule.Molecule instance. + name : str, optional + The name to give to the managed ParameterSet that adapts structure (default "phase"). - periodic + periodic : bool, optional The structure should be treated as periodic (default True). Note that some structures do not support periodicity, in which case this will have no effect on the @@ -279,10 +279,10 @@ def set_structure(self, structure, name="phase", periodic=True): parset = struToParameterSet(name, structure) # Set the phase - self.setPhase(parset, periodic) + self.set_structure_from_parset(parset, periodic) return - def setPhase(self, parset, periodic=True): + def set_structure_from_parset(self, parset, periodic=True): """Set the phase that will be used to calculate the PDF. Set the phase directly with a DiffpyStructureParSet, diff --git a/src/diffpy/cmipdf/debyepdfgenerator.py b/src/diffpy/cmipdf/debyepdfgenerator.py index 8ebe77f..18df808 100644 --- a/src/diffpy/cmipdf/debyepdfgenerator.py +++ b/src/diffpy/cmipdf/debyepdfgenerator.py @@ -110,7 +110,7 @@ def set_structure(self, structure, name="phase", periodic=False): """ return BasePDFGenerator.set_structure(self, structure, name, periodic) - def setPhase(self, parset, periodic=False): + def set_structure_from_parset(self, parset, periodic=False): """Set the phase that will be used to calculate the PDF. Set the phase directly with a DiffpyStructureParSet, @@ -130,7 +130,9 @@ def setPhase(self, parset, periodic=False): Note that some structures do not support periodicity, in which case this will be ignored. """ - return BasePDFGenerator.setPhase(self, parset, periodic) + return BasePDFGenerator.set_structure_from_parset( + self, parset, periodic + ) def __init__(self, name="pdf"): """Initialize the generator.""" diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index 6f42f85..74938e4 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -30,7 +30,7 @@ class PDFContribution(FitContribution): PDFContribution is a FitContribution that is customized for PDF fits. Data and phases can be added directly to the PDFContribution. Setup of constraints and restraints requires direct interaction with the generator - attributes (see setPhase). + attributes (see set_structure_from_parset). Attributes ---------- @@ -250,7 +250,7 @@ def addPhase(self, name, parset, periodic=True): gen = DebyePDFGenerator(name) # Set up the generator - gen.setPhase(parset, periodic) + gen.set_structure_from_parset(parset, periodic) self._setup_generator(gen) return gen.phase @@ -259,7 +259,7 @@ def _setup_generator(self, gen): """Setup a generator. The generator must already have a managed SrRealParSet, added - with set_structure or setPhase. + with set_structure or set_structure_from_parset. """ # Add the generator to this FitContribution self.add_profile_generator(gen) From 13abb509acb737bb40c7e7cf07dd8c1ef3cb4520 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 09:41:01 -0400 Subject: [PATCH 15/30] spheroidalCF --> spheroidal_particle --- src/diffpy/cmipdf/basepdfgenerator.py | 6 +- src/diffpy/cmipdf/characteristicfunctions.py | 93 +++++++++++--------- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index 407b9da..29003d9 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -292,12 +292,12 @@ def set_structure_from_parset(self, parset, periodic=True): Parameters ---------- - parset - A SrRealParSet that holds the structural information. + parset : SrRealParSet + The SrRealParSet that holds the structural information. This can be used to share the phase between multiple BasePDFGenerators, and have the changes in one reflect in another. - periodic + periodic : bool, optional The structure should be treated as periodic (default True). Note that some structures do not support periodicity, in which case this will be ignored. diff --git a/src/diffpy/cmipdf/characteristicfunctions.py b/src/diffpy/cmipdf/characteristicfunctions.py index 2fae86c..8a3a313 100644 --- a/src/diffpy/cmipdf/characteristicfunctions.py +++ b/src/diffpy/cmipdf/characteristicfunctions.py @@ -26,9 +26,8 @@ """ __all__ = [ - "sphericalCF", - "spheroidalCF", - "spheroidalCF2", + "spherical_particle", + "spheroidal_particle", "lognormalSphericalCF", "sheetCF", "shellCF", @@ -46,86 +45,91 @@ from diffpy.srfit.fitbase.calculator import Calculator -def sphericalCF(r, psize): +def spherical_particle(radial_dist, p_diameter): """Spherical nanoparticle characteristic function. Parameters ---------- - r - distance of interaction - psize - The particle diameter + radial_dist : float or array-like + The distance of interaction. + p_diameter : float + The particle diameter. From Kodama et al., Acta Cryst. A, 62, 444-453 (converted from radius to diameter) """ - f = numpy.zeros(numpy.shape(r), dtype=float) - if psize > 0: - x = numpy.array(r, dtype=float) / psize + f = numpy.zeros(numpy.shape(radial_dist), dtype=float) + if p_diameter > 0: + x = numpy.array(radial_dist, dtype=float) / p_diameter inside = x < 1.0 xin = x[inside] f[inside] = 1.0 - 1.5 * xin + 0.5 * xin * xin * xin return f -def spheroidalCF(r, erad, prad): +def spheroidal_particle(radial_dist, r_equatorial, r_polar): """Spheroidal characteristic function specified using radii. - Spheroid with radii (erad, erad, prad) + Spheroid with radii (r_equatorial, r_equatorial, r_polar) Parameters ---------- - prad - polar radius - erad - equatorial radius - - - erad < prad equates to a prolate spheroid - erad > prad equates to a oblate spheroid - erad == prad is a sphere + radial_dist : float or array-like + The distance of interaction. + r_polar : float + The polar radius of the spheroid. + r_equatorial + The equatorial radius of the spheroid. + + + Note + ---- + - `r_equatorial < r_polar` equates to a prolate spheroid + - `r_equatorial > r_polar` equates to a oblate spheroid + - `r_equatorial == r_polar` is a sphere """ - psize = 2.0 * erad - pelpt = 1.0 * prad / erad - return spheroidalCF2(r, psize, pelpt) + d_equatorial = 2.0 * r_equatorial + pelpt = 1.0 * r_polar / r_equatorial + return _calculate_spheroidal_cf(radial_dist, d_equatorial, pelpt) -def spheroidalCF2(r, psize, axrat): - """Spheroidal nanoparticle characteristic function. +def _calculate_spheroidal_cf(r, d_equatorial, axis_ratio): + """Calculate the spheroidal nanoparticle characteristic function. - Form factor for ellipsoid with radii (psize/2, psize/2, axrat*psize/2) + Form factor for ellipsoid with radii + (d_equatorial/2, d_equatorial/2, axis_ratio*d_equatorial/2) Parameters ---------- - r - distance of interaction - psize + r : float or array-like + The distance of interaction + d_equatorial : float The equatorial diameter - axrat + axis_ratio : float The ratio of axis lengths From Lei et al., Phys. Rev. B, 80, 024118 (2009) """ - pelpt = 1.0 * axrat + pelpt = 1.0 * axis_ratio - if psize <= 0 or pelpt <= 0: + if d_equatorial <= 0 or pelpt <= 0: return numpy.zeros_like(r) # to simplify the equations v = pelpt - d = 1.0 * psize + d = 1.0 * d_equatorial d2 = d * d v2 = v * v if v == 1: - return sphericalCF(r, psize) + return spherical_particle(r, d_equatorial) rx = r if v < 1: - r = rx[rx <= v * psize] + r = rx[rx <= v * d_equatorial] r2 = r * r f1 = ( 1 @@ -139,7 +143,7 @@ def spheroidalCF2(r, psize, axrat): * atanh(sqrt(1 - v2)) ) - r = rx[numpy.logical_and(rx > v * psize, rx <= psize)] + r = rx[numpy.logical_and(rx > v * d_equatorial, rx <= d_equatorial)] r2 = r * r f2 = ( ( @@ -154,14 +158,14 @@ def spheroidalCF2(r, psize, axrat): / sqrt(1 - v2) ) - r = rx[rx > psize] + r = rx[rx > d_equatorial] f3 = numpy.zeros_like(r) f = numpy.concatenate((f1, f2, f3)) elif v > 1: - r = rx[rx <= psize] + r = rx[rx <= d_equatorial] r2 = r * r f1 = ( 1 @@ -175,7 +179,7 @@ def spheroidalCF2(r, psize, axrat): * atan(sqrt(v2 - 1)) ) - r = rx[numpy.logical_and(rx > psize, rx <= v * psize)] + r = rx[numpy.logical_and(rx > d_equatorial, rx <= v * d_equatorial)] r2 = r * r f2 = ( 1 @@ -195,7 +199,7 @@ def spheroidalCF2(r, psize, axrat): * (atan(sqrt(v2 - 1)) - atan(sqrt(r2 / d2 - 1))) ) - r = rx[rx > v * psize] + r = rx[rx > v * d_equatorial] f3 = numpy.zeros_like(r) f = numpy.concatenate((f1, f2, f3)) @@ -238,7 +242,7 @@ def lognormalSphericalCF(r, psize, psig): if psize <= 0: return numpy.zeros_like(r) if psig <= 0: - return sphericalCF(r, psize) + return spherical_particle(r, psize) sqrt2 = sqrt(2.0) s = sqrt(log(psig * psig / (1.0 * psize * psize) + 1)) @@ -419,7 +423,8 @@ def __call__(self, r): # # The initial dr is somewhat arbitrary, but using dr = 0.01 allows for # the f(r) calculated from a particle of diameter 50, over r = - # arange(1, 60, 0.1) to agree with the sphericalCF with Rw < 1e-4%. + # arange(1, 60, 0.1) to agree with the spherical_particle with + # Rw < 1e-4%. # # We also have to make a q-spacing small enough to compute out to at # least the size of the signal. From f0851c32876af166f3425008baf75d2a1fdc5931 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 09:47:45 -0400 Subject: [PATCH 16/30] lognormalSphericalCF --> lognormal_spherical_distribution --- src/diffpy/cmipdf/characteristicfunctions.py | 58 ++++++++++---------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/diffpy/cmipdf/characteristicfunctions.py b/src/diffpy/cmipdf/characteristicfunctions.py index 8a3a313..427ea0e 100644 --- a/src/diffpy/cmipdf/characteristicfunctions.py +++ b/src/diffpy/cmipdf/characteristicfunctions.py @@ -28,7 +28,7 @@ __all__ = [ "spherical_particle", "spheroidal_particle", - "lognormalSphericalCF", + "lognormal_spherical_distribution", "sheetCF", "shellCF", "shellCF2", @@ -207,22 +207,24 @@ def _calculate_spheroidal_cf(r, d_equatorial, axis_ratio): return f -def lognormalSphericalCF(r, psize, psig): +def lognormal_spherical_distribution(radial_dist, p_diameter, p_sigma): """Spherical nanoparticle characteristic function with lognormal size distribution. Parameters ---------- - r - distance of interaction - psize - The mean particle diameter - psig - The log-normal width of the particle diameter + radial_dist : float or array-like + The distance of interaction. + p_diameter : float + The mean particle diameter. + p_sigma : float + The log-normal width of the particle diameter. - Here, r is the independent variable, mu is the mean of the distribution - (not of the particle size), and s is the width of the distribution. This is + Here, radial_dist is the independent variable, mu is the mean of the + distribution + (not of the particle size), and s is the width of the distribution. + This is the characteristic function for the lognormal distribution of particle diameter: @@ -231,36 +233,36 @@ def lognormalSphericalCF(r, psize, psig): - 0.75*r*Erfc((-mu-2*s^2+Log(r))/(sqrt(2)*s))*exp(-mu-2.5*s^2) The expectation value of the distribution gives the average particle - diameter, psize. The variance of the distribution gives psig^2. mu and s - can be expressed in terms of these as: + diameter, p_diameter. The variance of the distribution gives p_sigma^2. + mu and s can be expressed in terms of these as: - s^2 = log((psig/psize)^2 + 1) - mu = log(psize) - s^2/2 + s^2 = log((p_sigma/p_diameter)^2 + 1) + mu = log(p_diameter) - s^2/2 Source unknown """ - if psize <= 0: - return numpy.zeros_like(r) - if psig <= 0: - return spherical_particle(r, psize) + if p_diameter <= 0: + return numpy.zeros_like(radial_dist) + if p_sigma <= 0: + return spherical_particle(radial_dist, p_diameter) sqrt2 = sqrt(2.0) - s = sqrt(log(psig * psig / (1.0 * psize * psize) + 1)) - mu = log(psize) - s * s / 2 + s = sqrt(log(p_sigma * p_sigma / (1.0 * p_diameter * p_diameter) + 1)) + mu = log(p_diameter) - s * s / 2 if mu < 0: - return numpy.zeros_like(r) + return numpy.zeros_like(radial_dist) return ( - 0.5 * erfc((-mu - 3 * s * s + log(r)) / (sqrt2 * s)) + 0.5 * erfc((-mu - 3 * s * s + log(radial_dist)) / (sqrt2 * s)) + 0.25 - * r - * r - * r - * erfc((-mu + log(r)) / (sqrt2 * s)) + * radial_dist + * radial_dist + * radial_dist + * erfc((-mu + log(radial_dist)) / (sqrt2 * s)) * exp(-3 * mu - 4.5 * s * s) - 0.75 - * r - * erfc((-mu - 2 * s * s + log(r)) / (sqrt2 * s)) + * radial_dist + * erfc((-mu - 2 * s * s + log(radial_dist)) / (sqrt2 * s)) * exp(-mu - 2.5 * s * s) ) From 971fd7e34d331322d2e49c13d0245dc411aa0dca Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 09:50:16 -0400 Subject: [PATCH 17/30] sheetCF --> sheet_particle --- src/diffpy/cmipdf/characteristicfunctions.py | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/diffpy/cmipdf/characteristicfunctions.py b/src/diffpy/cmipdf/characteristicfunctions.py index 427ea0e..97d006a 100644 --- a/src/diffpy/cmipdf/characteristicfunctions.py +++ b/src/diffpy/cmipdf/characteristicfunctions.py @@ -29,7 +29,7 @@ "spherical_particle", "spheroidal_particle", "lognormal_spherical_distribution", - "sheetCF", + "sheet_particle", "shellCF", "shellCF2", "SASCF", @@ -267,33 +267,33 @@ def lognormal_spherical_distribution(radial_dist, p_diameter, p_sigma): ) -def sheetCF(r, sthick): +def sheet_particle(r, thickness): """Nanosheet characteristic function. Parameters ---------- - r - distance of interaction - sthick - Thickness of nanosheet + r: float or array-like + The distance of interaction. + thickness : float + The thickness of nanosheet. From Kodama et al., Acta Cryst. A, 62, 444-453 """ - # handle zero or negative sthick. make it work for scalars and arrays. - if sthick <= 0: - return 0 * sthick + # handle zero or negative thickness. make it work for scalars and arrays. + if thickness <= 0: + return 0 * thickness # process scalar r if numpy.isscalar(r): - rv = 1 - 0.5 * r / sthick if r < sthick else 0.5 * sthick / r + rv = 1 - 0.5 * r / thickness if r < thickness else 0.5 * thickness / r return rv # handle array-type r ra = numpy.asarray(r) - lo = ra < sthick + lo = ra < thickness hi = ~lo f = numpy.empty_like(ra, dtype=float) - f[lo] = 1 - 0.5 * ra[lo] / sthick - f[hi] = 0.5 * sthick / ra[hi] + f[lo] = 1 - 0.5 * ra[lo] / thickness + f[hi] = 0.5 * thickness / ra[hi] return f From 4c5284152c29ab675b716bad7f510004eee0ed8d Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 09:52:51 -0400 Subject: [PATCH 18/30] shellCF --> spherical_shell --- src/diffpy/cmipdf/characteristicfunctions.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/diffpy/cmipdf/characteristicfunctions.py b/src/diffpy/cmipdf/characteristicfunctions.py index 97d006a..39d7bef 100644 --- a/src/diffpy/cmipdf/characteristicfunctions.py +++ b/src/diffpy/cmipdf/characteristicfunctions.py @@ -30,7 +30,7 @@ "spheroidal_particle", "lognormal_spherical_distribution", "sheet_particle", - "shellCF", + "spherical_shell", "shellCF2", "SASCF", ] @@ -297,24 +297,26 @@ def sheet_particle(r, thickness): return f -def shellCF(r, radius, thickness): +def spherical_shell(radial_dist, inner_radius, thickness): """Spherical shell characteristic function. Parameters ---------- - radius - Inner radius - thickness - Thickness of shell + radial_dist : float or array-like + The distance of interaction. + inner_radius : float + The inner radius of the shell. + thickness : float + The thickness of shell. - outer radius = radius + thickness + outer radius = inner_radius + thickness From Lei et al., Phys. Rev. B, 80, 024118 (2009) """ d = 1.0 * thickness - a = 1.0 * radius + d / 2.0 - return shellCF2(r, a, d) + a = 1.0 * inner_radius + d / 2.0 + return shellCF2(radial_dist, a, d) def shellCF2(r, a, delta): From b742d4d34beab8f6bc9f9dde0165cb4caf4f042a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 09:53:46 -0400 Subject: [PATCH 19/30] shellCF2 --> _calculate_shell_cf --- src/diffpy/cmipdf/characteristicfunctions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/diffpy/cmipdf/characteristicfunctions.py b/src/diffpy/cmipdf/characteristicfunctions.py index 39d7bef..e5b73b5 100644 --- a/src/diffpy/cmipdf/characteristicfunctions.py +++ b/src/diffpy/cmipdf/characteristicfunctions.py @@ -31,7 +31,6 @@ "lognormal_spherical_distribution", "sheet_particle", "spherical_shell", - "shellCF2", "SASCF", ] @@ -316,10 +315,10 @@ def spherical_shell(radial_dist, inner_radius, thickness): """ d = 1.0 * thickness a = 1.0 * inner_radius + d / 2.0 - return shellCF2(radial_dist, a, d) + return _calculate_shell_cf(radial_dist, a, d) -def shellCF2(r, a, delta): +def _calculate_shell_cf(r, a, delta): """Spherical shell characteristic function. Parameters From 70e84033350486ec81e95e67eb06c1d9a8a01d78 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 13:34:16 -0400 Subject: [PATCH 20/30] update parser testing --- src/diffpy/cmipdf/basepdfgenerator.py | 16 +- tests/new_test_pdf.py | 323 ++++++++++++++++++++++++++ tests/test_pdf.py | 20 +- tests/testdata/ni-q27r100-neutron.gr | 80 +++---- tests/testdata/si-q27r60-xray.gr | 148 ++---------- 5 files changed, 394 insertions(+), 193 deletions(-) create mode 100644 tests/new_test_pdf.py diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index 29003d9..29f0229 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -72,7 +72,7 @@ class BasePDFGenerator(ProfileGenerator): Usable Metadata --------------- - scattering_type : str + stype : str The scattering type "X" for x-ray, "N" for neutron (see 'set_scattering_type'). qmax @@ -159,9 +159,9 @@ def _process_metadata(self): """Process the metadata once it gets set.""" ProfileGenerator._process_metadata(self) - scattering_type = self.meta.get("scattering_type") - if scattering_type is not None: - self.set_scattering_type(scattering_type) + stype = self.meta.get("stype") + if stype is not None: + self.set_scattering_type(stype) qmax = self.meta.get("qmax") if qmax is not None: @@ -179,12 +179,12 @@ def _process_metadata(self): return - def set_scattering_type(self, scattering_type="X"): + def set_scattering_type(self, stype="X"): """Set the scattering type. Parameters ---------- - scattering_type : str, optional + stype : str, optional The scattering type. Default is `"X"`. `"X"` for x-ray, `"N"` for neutron, `"E"` for electrons, or any registered type from diffpy.srreal from @@ -195,9 +195,9 @@ def set_scattering_type(self, scattering_type="X"): ValueError If the scattering type is unknown. """ - self._calc.setScatteringFactorTableByType(scattering_type) + self._calc.setScatteringFactorTableByType(stype) # update the meta dictionary only if there was no exception - self.meta["scattering_type"] = self.get_scattering_type() + self.meta["stype"] = self.get_scattering_type() return def get_scattering_type(self): diff --git a/tests/new_test_pdf.py b/tests/new_test_pdf.py new file mode 100644 index 0000000..908d5eb --- /dev/null +++ b/tests/new_test_pdf.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srfit by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010 The Trustees of Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Pavol Juhas +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE_DANSE.txt for license information. +# +############################################################################## +"""Tests for pdf package.""" + +import io +import pickle +import unittest +from itertools import chain + +import numpy +import pytest + +from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase import ProfileParser +from diffpy.srfit.pdf import PDFContribution, PDFGenerator, PDFParser + +# ---------------------------------------------------------------------------- + + +def testParser1(datafile): + data = datafile("ni-q27r100-neutron.gr") + parser = PDFParser() + parser.parseFile(data) + + meta = parser._meta + + assert data == meta["filename"] + assert 1 == meta["nbanks"] + assert "N" == meta["stype"] + assert 27 == meta["qmax"] + assert 300 == meta.get("temperature") + assert meta.get("qdamp") is None + assert meta.get("qbroad") is None + assert meta.get("spdiameter") is None + assert meta.get("scale") is None + assert meta.get("doping") is None + + x, y, dx, dy = parser.get_data() + assert dx is None + assert dy is None + + testx = numpy.linspace(0.01, 100, 10000) + diff = testx - x + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + testy = numpy.array( + [ + 1.144, + 2.258, + 3.312, + 4.279, + 5.135, + 5.862, + 6.445, + 6.875, + 7.150, + 7.272, + ] + ) + diff = testy - y[:10] + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + return + + +def testParser2(datafile): + data = datafile("si-q27r60-xray.gr") + parser = ProfileParser() + parser.parse_file(data) + + meta = parser._meta + + assert str(data) == meta["filename"] + assert 1 == meta["nbanks"] + assert "X" == meta["stype"] + assert 27 == meta["qmax"] + assert 300 == meta.get("temperature") + assert meta.get("qdamp") is None + assert meta.get("qbroad") is None + assert meta.get("spdiameter") is None + assert meta.get("scale") is None + assert meta.get("doping") is None + + x, y, dx, dy = parser.get_data() + testx = numpy.linspace(0.01, 60, 5999, endpoint=False) + diff = testx - x + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + testy = numpy.array( + [ + 0.1105784, + 0.2199684, + 0.3270088, + 0.4305913, + 0.5296853, + 0.6233606, + 0.7108060, + 0.7913456, + 0.8644501, + 0.9297440, + ] + ) + diff = testy - y[:10] + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + testdy = numpy.array( + [ + 0.001802192, + 0.003521449, + 0.005079115, + 0.006404892, + 0.007440527, + 0.008142955, + 0.008486813, + 0.008466340, + 0.008096858, + 0.007416456, + ] + ) + diff = testdy - dy[:10] + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + + assert dx.tolist() == [0] * len(dx) + return + + +def testGenerator( + diffpy_srreal_available, diffpy_structure_available, datafile +): + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + if not diffpy_srreal_available: + pytest.skip("diffpy.srreal package not available") + + from diffpy.srreal.pdfcalculator import PDFCalculator + from diffpy.structure import PDFFitStructure + + qmax = 27.0 + gen = PDFGenerator() + gen.setScatteringType("N") + assert "N" == gen.getScatteringType() + gen.setQmax(qmax) + assert qmax == pytest.approx(gen.getQmax()) + + stru = PDFFitStructure() + ciffile = datafile("ni.cif") + cif_path = str(ciffile) + stru.read(cif_path) + for i in range(4): + stru[i].Bisoequiv = 1 + gen.setStructure(stru) + + calc = gen._calc + # Test parameters + for par in gen.iterPars(recurse=False): + pname = par.name + defval = calc._getDoubleAttr(pname) + assert defval == par.getValue() + # Test setting values + par.set_value(1.0) + assert 1.0 == par.getValue() + par.set_value(defval) + assert defval == par.getValue() + + r = numpy.arange(0, 10, 0.1) + y = gen(r) + + # Now create a reference PDF. Since the calculator is testing its + # output, we just have to make sure we can calculate from the + # PDFGenerator interface. + + calc = PDFCalculator() + calc.rstep = r[1] - r[0] + calc.rmin = r[0] + calc.rmax = r[-1] + 0.5 * calc.rstep + calc.qmax = qmax + calc.setScatteringFactorTableByType("N") + calc.eval(stru) + yref = calc.pdf + + diff = y - yref + res = numpy.dot(diff, diff) + assert 0 == pytest.approx(res) + return + + +def test_setQmin(diffpy_structure_available, diffpy_srreal_available): + """Verify qmin is propagated to the calculator object.""" + if not diffpy_srreal_available: + pytest.skip("diffpy.srreal package not available") + + gen = PDFGenerator() + assert 0 == gen.getQmin() + assert 0 == gen._calc.qmin + gen.setQmin(0.93) + assert 0.93 == gen.getQmin() + assert 0.93 == gen._calc.qmin + return + + +def test_setQmax(diffpy_structure_available, diffpy_srreal_available): + """Check PDFContribution.setQmax()""" + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import Structure + + if not diffpy_srreal_available: + pytest.skip("diffpy.srreal package not available") + + pc = PDFContribution("pdf") + pc.setQmax(21) + pc.addStructure("empty", Structure()) + assert 21 == pc.empty.getQmax() + pc.setQmax(22) + assert 22 == pc.getQmax() + assert 22 == pc.empty.getQmax() + return + + +def test_getQmax(diffpy_structure_available, diffpy_srreal_available): + """Check PDFContribution.getQmax()""" + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import Structure + + if not diffpy_srreal_available: + pytest.skip("diffpy.srreal package not available") + + # cover all code branches in PDFContribution._get_meta_value + # (1) contribution metadata + pc1 = PDFContribution("pdf") + assert pc1.getQmax() is None + pc1.setQmax(17) + assert 17 == pc1.getQmax() + # (2) contribution metadata + pc2 = PDFContribution("pdf") + pc2.addStructure("empty", Structure()) + pc2.empty.setQmax(18) + assert 18 == pc2.getQmax() + # (3) profile metadata + pc3 = PDFContribution("pdf") + pc3.profile.meta["qmax"] = 19 + assert 19 == pc3.getQmax() + return + + +def test_savetxt( + diffpy_structure_available, diffpy_srreal_available, datafile +): + "check PDFContribution.savetxt()" + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import Structure + + if not diffpy_srreal_available: + pytest.skip("diffpy.srreal package not available") + + pc = PDFContribution("pdf") + pc.loadData(datafile("si-q27r60-xray.gr")) + pc.setCalculationRange(0, 10) + pc.addStructure("empty", Structure()) + fp = io.BytesIO() + with pytest.raises(SrFitError): + pc.savetxt(fp) + pc.evaluate() + pc.savetxt(fp) + txt = fp.getvalue().decode() + nlines = len(txt.strip().split("\n")) + assert 1001 == nlines + return + + +def test_pickling( + diffpy_structure_available, diffpy_srreal_available, datafile +): + "validate PDFContribution.residual() after pickling." + if not diffpy_structure_available: + pytest.skip("diffpy.structure package not available") + from diffpy.structure import loadStructure + + if not diffpy_srreal_available: + pytest.skip("diffpy.srreal package not available") + + pc = PDFContribution("pdf") + pc.loadData(datafile("ni-q27r100-neutron.gr")) + ciffile = datafile("ni.cif") + cif_path = str(ciffile) + ni = loadStructure(cif_path) + ni.Uisoequiv = 0.003 + pc.addStructure("ni", ni) + pc.setCalculationRange(0, 10) + pc2 = pickle.loads(pickle.dumps(pc)) + res0 = pc.residual() + assert numpy.array_equal(res0, pc2.residual()) + for p in chain( + pc.iterate_over_parameters("Uiso"), pc2.iterate_over_parameters("Uiso") + ): + p.value = 0.004 + res1 = pc.residual() + assert not numpy.allclose(res0, res1) + assert numpy.array_equal(res1, pc2.residual()) + return + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_pdf.py b/tests/test_pdf.py index f5befce..5e84a4c 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -23,7 +23,7 @@ import numpy import pytest -from diffpy.cmipdf import PDFContribution, PDFGenerator, PDFParser +from diffpy.cmipdf import PDFContribution, PDFGenerator from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase import ProfileParser @@ -31,13 +31,13 @@ def testParser1(datafile): - data = datafile("ni-q27r100-neutron.gr") - parser = PDFParser() - parser.parseFile(data) + filename = datafile("ni-q27r100-neutron.gr") + parser = ProfileParser() + parser.parse_file(filename) meta = parser._meta - assert data == meta["filename"] + assert str(filename) == meta["filename"] assert 1 == meta["nbanks"] assert "N" == meta["stype"] assert 27 == meta["qmax"] @@ -49,8 +49,8 @@ def testParser1(datafile): assert meta.get("doping") is None x, y, dx, dy = parser.get_data() - assert dx is None - assert dy is None + assert dx.tolist() == len(x) * [0] + assert dy.tolist() == len(x) * [0] testx = numpy.linspace(0.01, 100, 10000) diff = testx - x @@ -155,9 +155,9 @@ def testGenerator( qmax = 27.0 gen = PDFGenerator() - gen.setScatteringType("N") - assert "N" == gen.getScatteringType() - gen.setQmax(qmax) + gen.set_scattering_type("N") + assert "N" == gen.get_scattering_type() + gen.set_qmax(qmax) assert qmax == pytest.approx(gen.get_qmax()) structure = PDFFitStructure() diff --git a/tests/testdata/ni-q27r100-neutron.gr b/tests/testdata/ni-q27r100-neutron.gr index 1b18d9a..65dada7 100644 --- a/tests/testdata/ni-q27r100-neutron.gr +++ b/tests/testdata/ni-q27r100-neutron.gr @@ -1,54 +1,32 @@ -# History written: Tue May 6 11:04:33 2008 -# produced by bozin -# ##### Run Information runCorrection=T -# prep=gsas machine=npdf -# run=npdf_03315 background=npdf_03001 -# smooth=2 smoothParam=32 32 0 backKillThresh=-1.0 -# in beam: radius=0.45325 height=4.5 -# temp=300 runTitle=Run 3315: Ni commercial, RT_stick -# -# ##### Vanadium runCorrection=T -# run=npdf_03000 background=npdf_03001 -# smooth=2 smoothParam=32 32 0 vanKillThresh=-1.0 vBackKillThresh=-1.0 -# in beam: radius=0.47625 height=4.5 -# -# ##### Container runCorrection=T -# run=npdf_03002 background=npdf_03001 -# smooth=2 smoothParam=32 32 0 cBackKillThresh=-1.0 -# wallThick=0.023 atomDensity=0.072110 -# atomic information: scattCS=5.100 absorpCS=5.080 -# -# ##### Sample Material numElements=1 NormLaue=0.00000 -# Element relAtomNum atomMass atomCoherCS atomIncoherCS atomAbsorpCS -# Ni 1.0000 58.693 13.3000 5.2000 4.49000 -# density=7.0 effDensity=2.8777 -# -# ##### Banks=4 deltaQ=0.01 matchRef=0 matchScal=T matchOffset=T -# bank angle blendQmin blendQmax (0.0 means no info) -# 1 46.6 0.87 21.63 -# 2 90.0 1.51 37.66 -# 3 119.0 1.84 45.96 -# 4 148.0 2.05 51.25 -# -# ##### Program Specific Information -# ## Ft calcError=1 (1 for true, 0 for false) -# numRpoints=10000 maxR=100.0 numDensity=0.0 intMaxR=1.5 -# ## Damp Qmin=0.87 Qmax=27.0 startDampQ=27.0 QAveMin=0.6 -# dampFuncType=0 modEqn=1.0000*S(Q) +0.0000 +0.0000*Q dampExtraToZero=0 -# ## Blend numBanks=4 banks=1,2,3,4 -# soqCorrFile= -# ## Soqd minProcOut=0 -# samPlazcek=1 vanPlazcek=1 smoothData=0 modifyData=1 -# ## Corps minProcOut=0 numBanksMiss=0 -# -# ##### prepgsas prepOutput=1 numBanksMiss=0 fileExt=gsa -# instParamFile=npdf_TL-displex_2018.iparm -# numBanksAdd=0 -# numBanksMult=0 -##### start data -#O0 rg_int sig_rg_int low_int sig_low_int rmax rhofit -#S 1 - PDF from PDFgetN -#P0 -68.04163 47.30471 0.14884 0.13136 1.50 0.1091 +# xPDFsuite Configuration # + +#### NOTE: The metadata values are NOT actual metadata values from the data and are strictly used for testing +[PDF] +wavelength = 0.1 +dataformat = QA +inputfile = input.iq +backgroundfile = backgroundfile.iq +mode = neutron +bgscale = 1.0 +composition = TiSe2 +outputtype = gr +qmaxinst = 25.0 +qmin = 0.1 +qmax = 27.0 +rmax = 100.0 +rmin = 0.0 +rstep = 0.01 +rpoly = 0.7 +stype = N +temperature = 300 + +[Misc] +inputdir = /my/data/dir +savedir = /my/save/dir +backgroundfilefull = /my/data/dir/backgroundfile.iq + +#### start data +#S 1 #L r G(r) dr dG(r) 0.010 1.144 0.020 2.258 diff --git a/tests/testdata/si-q27r60-xray.gr b/tests/testdata/si-q27r60-xray.gr index e25d579..bbc9fd7 100644 --- a/tests/testdata/si-q27r60-xray.gr +++ b/tests/testdata/si-q27r60-xray.gr @@ -1,130 +1,30 @@ -History written: Mon Apr 21 20:48:28 2008 -Produced by -####### Get_XPDF ####### +# xPDFsuite Configuration # -##### General_Setting -title=X-ray PDF -workingdirectory=e:\Ahmad\MUCAT0804\standards\pdfgetx2 -sourcedir=C:\Program Files\PDFgetX2\ -logfile=.pdfgetx2.log -quiet=0 debug=0 autosave_isa=1 savefilenamebase=si325_mesh_300k_nor_4-8 -iqfilesurfix=.iq sqfilesurfix=.sq fqfilesurfix=.fq grfilesurfix=.gr +#### NOTE: The metadata values are NOT actual metadata values from the data and are strictly used for testing +[PDF] +wavelength = 0.1 +dataformat = QA +inputfile = input.iq +backgroundfile = backgroundfile.iq +mode = neutron +bgscale = 1.0 +composition = TiSe2 +outputtype = gr +qmaxinst = 25.0 +qmin = 0.1 +qmax = 27.0 +rmax = 60.0 +rmin = 0.0 +rstep = 0.01 +rpoly = 0.7 +stype = X +temperature = 300 -##### DataFileFormat -datatype=1 (0:SPEC, 1:CHI, 2:nxm column, 3:unknown) -num_skiplines=3 comment_id=# delimiter= -### SPEC Format scan_id=#S scan_delimiter= -columnname_id=#L columnname_delimiter= -data_id= data_delimiter= -### CHI Format -### nxm column Format -### End of file format +[Misc] +inputdir = /my/data/dir +savedir = /my/save/dir +backgroundfilefull = /my/data/dir/backgroundfile.iq -##### Data&Background -samfile=si325_mesh_300k_nor_4-8.chi num_sams=1 -sambkgfile=kapton_bgrd_300k_nor_2-3.chi num_sambkgs=1 -confile= num_cons=1 -conbkgfile= num_conbkgs=1 -det# used xcol detcol deterrcol xmin xmax add_det mul_det add_bkg mul_bkg add_con mul_con add_conbkg mul_conbkg - 0 1 0 1 3 0.600000 32.0000 0.000000 1.00000 0.000000 1.00000 0.000000 1.00000 0.000000 1.00000 - -##### Experiment_Setup -title=PDF analysis -user=me -facility=In house -temperature=300.000 containermut=0.000500000 filtermut=0.0200000 -## X-Ray radiationtype=3 - (0: Ag K_alpha, 1:Cu K_alpha, 2:Mo K_alpha, 3:Customize) -lambda=0.142773 energy=86.8406 polartype=0 polardegree=1.00000 -## MonoChromator crystaltype=0 (0:Perfect, 1:Mosaic, 2:None) -position=0 (0:Primary beam, 1:Diffracted beam) -dspacetype=0 (0:Si{111}, 1:Ge{111}, 2:Customize) dspacing=3.13200 - -##### Sample_Setup information num_atoms=1 -#L symbol valence fractions z user_f1 user_f2 user_macoef - Si 0.00 1.000000 14 0.000000 0.000000 0.001000 -geometry=2 mut=0.50000000 numberdensity=0.00600000 -thickness=2.00000 packingFraction=0.500000 theory_mut=0.00579218 - -##### GetIQ_Setup -xformat=1 -smoothcorr_isa=0 selfnormalize_isa=0 -#L par_name sample sample_bkg container container_bkg -smooth_degree 2 2 2 2 -smooth_width 6 6 6 6 -selfnormalize 0 0 0 0 -filtercorr_isa=0 samfiltercorr_isa=0 sambkgfiltercorr_isa=0 -confiltercorr_isa=0 conbkgfiltercorr_isa=0 -scatveffcorr_isa=1 samconveffcorr_isa=1 sambkgveffcorr_isa=0 -conbkgveffcorr_isa=0 -nonegative_isa=1 negativevalue=-1.00000 - -##### Calibration_Data -## Detection efficiency energy dependence detedepxaxis=0 -detedepfunctype=0 detedep_elastic=1.00000 detedep_fluores=1.80000 -detedep_quadra=0.000000 detedep_spline=0.000000 detedep_file= -## Detector transmission energy dependence dettcoefxaxis=0 -dettcoeffunctype=0 dettcoef_elastic=0.950000 dettcoef_fluores=0.600000 -dettcoef_quadra=0.000000 dettcoef_spline=0.000000 dettcoef_file= - -##### IQ_Simulation -### Elastic used_isa=1 mymethod=1 -do_samabsorp=1 do_multscat=1 do_conabsorp=0 do_airabsorp=0 -do_polarization=1 do_oblincident=0 do_energydep=0 -do_breitdirac=0 breitdiracexpo=2.00000 -do_rulandwin=0 rulandwinwidth=0.00100000 -do_useredit=0 add_user=0.000000 mul_user=1.00000 -### Compton used_isa=1 mymethod=1 -do_samabsorp=1 do_multscat=1 do_conabsorp=0 do_airabsorp=0 -do_polarization=1 do_oblincident=0 do_energydep=0 -do_breitdirac=0 breitdiracexpo=2.00000 -do_rulandwin=0 rulandwinwidth=0.00100000 -do_useredit=0 add_user=0.000000 mul_user=1.00000 -### Fluores used_isa=1 mymethod=1 -do_samabsorp=1 do_multscat=1 do_conabsorp=0 do_airabsorp=0 -do_polarization=1 do_oblincident=0 do_energydep=0 -do_breitdirac=0 breitdiracexpo=2.00000 -do_rulandwin=0 rulandwinwidth=0.00100000 -do_useredit=0 add_user=0.000000 mul_user=1.00000 - -##### Correction_Setup corrmethod=0 -oblincident_isa=1 dettranscoef=0.980000 samfluore_isa=1 -samfluoretype=0 samfluorescale=15.000000 -multiscat_isa=1 xraypolar_isa=1 samabsorp_isa=1 -highqscale_isa=1 highqratio=0.600000 scaleconst=0.039181914 -scaleconst_theory=0.039181914 -comptonscat_isa=1 rulandwin_isa=0 rulandintewidth=0.0100000 -comptonmethod=0 breitdirac_isa=1 breitdiracexponent=3 -detefficiency_isa=1 detefficiencytype=2 (0-1: linear, 2-3: quadratic) -detefficiency_a=-0.054826792 detefficiency_b=0.028062565 -lauediffuse_isa=1 -weight_isa=1 weighttype=0 (0: ^2, 1: , 2: Data Smoothed) -weightsmoothrmin=3.00000 weightsmoothwidth=100 weightsmoothcycles=600 -editsq_isa=0 editsqtype=0 add_sq=0.000000 mul_sq=1.00000 -editsqsmoothrmin=3.00000 editsqsmoothwidth=100 editsqsmoothcycles=600 -smoothdata_isa=0 smoothfunctype=0 smoothqmin=12.0000 smoothboxwidth=9 -interpolateqmin_isa=0 qmininterpolationtype=0 -dampfq_isa=0 dampfqtype=0 dampfqwidth=23.0000 - -##### SqGr_Optimization Setup -ftmethod=0 -## S(q) qmin=0.010000 qmax=27.000000 qgrid=0.000000 -## G(r) rmin=0.010000 rmax=60.000000 rgrid=0.010000 -## SqOptimization sqoptfunction=1 -optqmin=15.0000 optqmax=40.0000 optqgrid=0.000000 -optrmin=0.000000 optrmax=2.20000 optrgrid=0.0200000 -maxiter=20 relstep=0.000000 weighttype=0 weightfunc=0 -fitbkgmult_isa=0 fitsampmut_isa=1 fitpolariz_isa=1 -fitoblique_isa=0 fitfluores_isa=0 -fitrulandw_isa=0 fitenergya_isa=1 fitenergyb_isa=1 -fitsimurulandw_isa=1 fitDetEdepfluores_isa=0 fitDetEdepquadra_isa=0 -fitDetEdepspline_isa=0 fitDetTCoefElastic_isa=0 fitDetTCoefFluores_isa=0 -fitDetTcoefquadra_isa=0 fitDetTcoefspline_isa=0 - -##### Save&Plot Settings -datatype=GrData iqcorrtype=Int iqsimutype=SimuIq -sqcorrtype=Oblin sqtofqtype=DampFq -gropttype=OptFq miscdatatype=AtomASF ##### start data #F si325_mesh_300k_nor_4-8.gr #D Mon Apr 21 21:17:23 2008 From 3c2073a3530d1bbc1a783063c5a6cc94745e0301 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 13:48:28 -0400 Subject: [PATCH 21/30] tests passing checkpoint 2 --- src/diffpy/cmipdf/basepdfgenerator.py | 2 +- src/diffpy/cmipdf/pdfcontribution.py | 8 +- tests/new_test_pdf.py | 323 -------------------------- tests/test_pdf.py | 16 +- 4 files changed, 10 insertions(+), 339 deletions(-) delete mode 100644 tests/new_test_pdf.py diff --git a/src/diffpy/cmipdf/basepdfgenerator.py b/src/diffpy/cmipdf/basepdfgenerator.py index 29f0229..14ea083 100644 --- a/src/diffpy/cmipdf/basepdfgenerator.py +++ b/src/diffpy/cmipdf/basepdfgenerator.py @@ -304,7 +304,7 @@ def set_structure_from_parset(self, parset, periodic=True): """ # Store the ParameterSet for easy access self._phase = parset - self.structure = self._phase.structure + self.structure = self._phase.stru # Put this ParameterSet in the ProfileGenerator. self.add_parameter_set(parset) diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index 74938e4..8389fb6 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -196,11 +196,11 @@ def addStructure(self, name, structure, periodic=True): """ # Based on periodic, create the proper generator. if periodic: - from diffpy.srfit.pdf.pdfgenerator import PDFGenerator + from diffpy.cmipdf.pdfgenerator import PDFGenerator gen = PDFGenerator(name) else: - from diffpy.srfit.pdf.debyepdfgenerator import DebyePDFGenerator + from diffpy.cmipdf.debyepdfgenerator import DebyePDFGenerator gen = DebyePDFGenerator(name) @@ -241,11 +241,11 @@ def addPhase(self, name, parset, periodic=True): """ # Based on periodic, create the proper generator. if periodic: - from diffpy.srfit.pdf.pdfgenerator import PDFGenerator + from diffpy.cmipdf.pdfgenerator import PDFGenerator gen = PDFGenerator(name) else: - from diffpy.srfit.pdf.debyepdfgenerator import DebyePDFGenerator + from diffpy.cmipdf.debyepdfgenerator import DebyePDFGenerator gen = DebyePDFGenerator(name) diff --git a/tests/new_test_pdf.py b/tests/new_test_pdf.py deleted file mode 100644 index 908d5eb..0000000 --- a/tests/new_test_pdf.py +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 The Trustees of Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. -# -############################################################################## -"""Tests for pdf package.""" - -import io -import pickle -import unittest -from itertools import chain - -import numpy -import pytest - -from diffpy.srfit.exceptions import SrFitError -from diffpy.srfit.fitbase import ProfileParser -from diffpy.srfit.pdf import PDFContribution, PDFGenerator, PDFParser - -# ---------------------------------------------------------------------------- - - -def testParser1(datafile): - data = datafile("ni-q27r100-neutron.gr") - parser = PDFParser() - parser.parseFile(data) - - meta = parser._meta - - assert data == meta["filename"] - assert 1 == meta["nbanks"] - assert "N" == meta["stype"] - assert 27 == meta["qmax"] - assert 300 == meta.get("temperature") - assert meta.get("qdamp") is None - assert meta.get("qbroad") is None - assert meta.get("spdiameter") is None - assert meta.get("scale") is None - assert meta.get("doping") is None - - x, y, dx, dy = parser.get_data() - assert dx is None - assert dy is None - - testx = numpy.linspace(0.01, 100, 10000) - diff = testx - x - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - testy = numpy.array( - [ - 1.144, - 2.258, - 3.312, - 4.279, - 5.135, - 5.862, - 6.445, - 6.875, - 7.150, - 7.272, - ] - ) - diff = testy - y[:10] - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - return - - -def testParser2(datafile): - data = datafile("si-q27r60-xray.gr") - parser = ProfileParser() - parser.parse_file(data) - - meta = parser._meta - - assert str(data) == meta["filename"] - assert 1 == meta["nbanks"] - assert "X" == meta["stype"] - assert 27 == meta["qmax"] - assert 300 == meta.get("temperature") - assert meta.get("qdamp") is None - assert meta.get("qbroad") is None - assert meta.get("spdiameter") is None - assert meta.get("scale") is None - assert meta.get("doping") is None - - x, y, dx, dy = parser.get_data() - testx = numpy.linspace(0.01, 60, 5999, endpoint=False) - diff = testx - x - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - testy = numpy.array( - [ - 0.1105784, - 0.2199684, - 0.3270088, - 0.4305913, - 0.5296853, - 0.6233606, - 0.7108060, - 0.7913456, - 0.8644501, - 0.9297440, - ] - ) - diff = testy - y[:10] - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - testdy = numpy.array( - [ - 0.001802192, - 0.003521449, - 0.005079115, - 0.006404892, - 0.007440527, - 0.008142955, - 0.008486813, - 0.008466340, - 0.008096858, - 0.007416456, - ] - ) - diff = testdy - dy[:10] - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - assert dx.tolist() == [0] * len(dx) - return - - -def testGenerator( - diffpy_srreal_available, diffpy_structure_available, datafile -): - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - - from diffpy.srreal.pdfcalculator import PDFCalculator - from diffpy.structure import PDFFitStructure - - qmax = 27.0 - gen = PDFGenerator() - gen.setScatteringType("N") - assert "N" == gen.getScatteringType() - gen.setQmax(qmax) - assert qmax == pytest.approx(gen.getQmax()) - - stru = PDFFitStructure() - ciffile = datafile("ni.cif") - cif_path = str(ciffile) - stru.read(cif_path) - for i in range(4): - stru[i].Bisoequiv = 1 - gen.setStructure(stru) - - calc = gen._calc - # Test parameters - for par in gen.iterPars(recurse=False): - pname = par.name - defval = calc._getDoubleAttr(pname) - assert defval == par.getValue() - # Test setting values - par.set_value(1.0) - assert 1.0 == par.getValue() - par.set_value(defval) - assert defval == par.getValue() - - r = numpy.arange(0, 10, 0.1) - y = gen(r) - - # Now create a reference PDF. Since the calculator is testing its - # output, we just have to make sure we can calculate from the - # PDFGenerator interface. - - calc = PDFCalculator() - calc.rstep = r[1] - r[0] - calc.rmin = r[0] - calc.rmax = r[-1] + 0.5 * calc.rstep - calc.qmax = qmax - calc.setScatteringFactorTableByType("N") - calc.eval(stru) - yref = calc.pdf - - diff = y - yref - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - return - - -def test_setQmin(diffpy_structure_available, diffpy_srreal_available): - """Verify qmin is propagated to the calculator object.""" - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - - gen = PDFGenerator() - assert 0 == gen.getQmin() - assert 0 == gen._calc.qmin - gen.setQmin(0.93) - assert 0.93 == gen.getQmin() - assert 0.93 == gen._calc.qmin - return - - -def test_setQmax(diffpy_structure_available, diffpy_srreal_available): - """Check PDFContribution.setQmax()""" - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - from diffpy.structure import Structure - - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - - pc = PDFContribution("pdf") - pc.setQmax(21) - pc.addStructure("empty", Structure()) - assert 21 == pc.empty.getQmax() - pc.setQmax(22) - assert 22 == pc.getQmax() - assert 22 == pc.empty.getQmax() - return - - -def test_getQmax(diffpy_structure_available, diffpy_srreal_available): - """Check PDFContribution.getQmax()""" - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - from diffpy.structure import Structure - - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - - # cover all code branches in PDFContribution._get_meta_value - # (1) contribution metadata - pc1 = PDFContribution("pdf") - assert pc1.getQmax() is None - pc1.setQmax(17) - assert 17 == pc1.getQmax() - # (2) contribution metadata - pc2 = PDFContribution("pdf") - pc2.addStructure("empty", Structure()) - pc2.empty.setQmax(18) - assert 18 == pc2.getQmax() - # (3) profile metadata - pc3 = PDFContribution("pdf") - pc3.profile.meta["qmax"] = 19 - assert 19 == pc3.getQmax() - return - - -def test_savetxt( - diffpy_structure_available, diffpy_srreal_available, datafile -): - "check PDFContribution.savetxt()" - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - from diffpy.structure import Structure - - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - - pc = PDFContribution("pdf") - pc.loadData(datafile("si-q27r60-xray.gr")) - pc.setCalculationRange(0, 10) - pc.addStructure("empty", Structure()) - fp = io.BytesIO() - with pytest.raises(SrFitError): - pc.savetxt(fp) - pc.evaluate() - pc.savetxt(fp) - txt = fp.getvalue().decode() - nlines = len(txt.strip().split("\n")) - assert 1001 == nlines - return - - -def test_pickling( - diffpy_structure_available, diffpy_srreal_available, datafile -): - "validate PDFContribution.residual() after pickling." - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - from diffpy.structure import loadStructure - - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - - pc = PDFContribution("pdf") - pc.loadData(datafile("ni-q27r100-neutron.gr")) - ciffile = datafile("ni.cif") - cif_path = str(ciffile) - ni = loadStructure(cif_path) - ni.Uisoequiv = 0.003 - pc.addStructure("ni", ni) - pc.setCalculationRange(0, 10) - pc2 = pickle.loads(pickle.dumps(pc)) - res0 = pc.residual() - assert numpy.array_equal(res0, pc2.residual()) - for p in chain( - pc.iterate_over_parameters("Uiso"), pc2.iterate_over_parameters("Uiso") - ): - p.value = 0.004 - res1 = pc.residual() - assert not numpy.allclose(res0, res1) - assert numpy.array_equal(res1, pc2.residual()) - return - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 5e84a4c..89ad046 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -26,6 +26,7 @@ from diffpy.cmipdf import PDFContribution, PDFGenerator from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase import ProfileParser +from diffpy.structure import Structure # ---------------------------------------------------------------------------- @@ -170,7 +171,7 @@ def testGenerator( calc = gen._calc # Test parameters - for par in gen.iterPars(recurse=False): + for par in gen.iterate_over_parameters(recurse=False): pname = par.name defval = calc._getDoubleAttr(pname) assert defval == par.getValue() @@ -216,20 +217,13 @@ def test_set_qmin(diffpy_structure_available, diffpy_srreal_available): return -def test_setQmax(diffpy_structure_available, diffpy_srreal_available): +def test_setQmax(): """Check PDFContribution.setQmax()""" - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - from diffpy.structure import Structure - - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - pc = PDFContribution("pdf") - pc.setQmax(21) + pc.set_qmax(21) pc.addStructure("empty", Structure()) assert 21 == pc.empty.get_qmax() - pc.setQmax(22) + pc.set_qmax(22) assert 22 == pc.get_qmax() assert 22 == pc.empty.get_qmax() return From 0f2ed66f2496ee49b45073db8425b389ab201f06 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 13:51:18 -0400 Subject: [PATCH 22/30] all tests passing --- tests/test_pdf.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 89ad046..a8b7680 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -26,7 +26,7 @@ from diffpy.cmipdf import PDFContribution, PDFGenerator from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase import ProfileParser -from diffpy.structure import Structure +from diffpy.structure import Structure, loadStructure # ---------------------------------------------------------------------------- @@ -203,11 +203,8 @@ def testGenerator( return -def test_set_qmin(diffpy_structure_available, diffpy_srreal_available): +def test_set_qmin(): """Verify qmin is propagated to the calculator object.""" - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - gen = PDFGenerator() assert 0 == gen.get_qmin() assert 0 == gen._calc.qmin @@ -229,25 +226,18 @@ def test_setQmax(): return -def test_get_qmax(diffpy_structure_available, diffpy_srreal_available): +def test_get_qmax(): """Check PDFContribution.get_qmax()""" - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - from diffpy.structure import Structure - - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - # cover all code branches in PDFContribution._get_meta_value # (1) contribution metadata pc1 = PDFContribution("pdf") assert pc1.get_qmax() is None - pc1.setQmax(17) + pc1.set_qmax(17) assert 17 == pc1.get_qmax() # (2) contribution metadata pc2 = PDFContribution("pdf") pc2.addStructure("empty", Structure()) - pc2.empty.setQmax(18) + pc2.empty.set_qmax(18) assert 18 == pc2.get_qmax() # (3) profile metadata pc3 = PDFContribution("pdf") @@ -286,13 +276,6 @@ def test_pickling( diffpy_structure_available, diffpy_srreal_available, datafile ): "validate PDFContribution.residual() after pickling." - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - from diffpy.structure import loadStructure - - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - pc = PDFContribution("pdf") pc.loadData(datafile("ni-q27r100-neutron.gr")) ciffile = datafile("ni.cif") From f102f1b326d5059ab513edffe3c40ee459adf31d Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 13:51:42 -0400 Subject: [PATCH 23/30] rm unittest import --- tests/test_pdf.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/test_pdf.py b/tests/test_pdf.py index a8b7680..562bff3 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -17,7 +17,6 @@ import io import pickle -import unittest from itertools import chain import numpy @@ -295,7 +294,3 @@ def test_pickling( assert not numpy.allclose(res0, res1) assert numpy.array_equal(res1, pc2.residual()) return - - -if __name__ == "__main__": - unittest.main() From da8c24eea9f31918808a3c15b72c70c702ad7c34 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 13:56:23 -0400 Subject: [PATCH 24/30] debyepdfpgenerator.py --- src/diffpy/cmipdf/debyepdfgenerator.py | 16 ++++++++-------- tests/test_pdf.py | 11 ++--------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/diffpy/cmipdf/debyepdfgenerator.py b/src/diffpy/cmipdf/debyepdfgenerator.py index 18df808..7eae00a 100644 --- a/src/diffpy/cmipdf/debyepdfgenerator.py +++ b/src/diffpy/cmipdf/debyepdfgenerator.py @@ -96,13 +96,13 @@ def set_structure(self, structure, name="phase", periodic=False): Parameters ---------- - structure - diffpy.structure.Structure, pyobjcryst.crystal.Crystal or - pyobjcryst.molecule.Molecule instance. Default None. - name + structure : Structure object + The `diffpy.structure.Structure`, `pyobjcryst.crystal.Crystal` or + `pyobjcryst.molecule.Molecule` instance. + name : str, optional A name to give to the managed ParameterSet that adapts structure (default "phase"). - periodic + periodic : bool, optional The structure should be treated as periodic (default False). Note that some structures do not support periodicity, in which case this will have no effect on the @@ -120,12 +120,12 @@ def set_structure_from_parset(self, parset, periodic=False): Parameters ---------- - parset - A SrRealParSet that holds the structural information. + parset : SrealParSet object + The SrRealParSet that holds the structural information. This can be used to share the phase between multiple BasePDFGenerators, and have the changes in one reflect in another. - periodic + periodic : bool, optional The structure should be treated as periodic (default True). Note that some structures do not support periodicity, in which case this will be ignored. diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 562bff3..3755f30 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -25,7 +25,8 @@ from diffpy.cmipdf import PDFContribution, PDFGenerator from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase import ProfileParser -from diffpy.structure import Structure, loadStructure +from diffpy.srreal.pdfcalculator import PDFCalculator +from diffpy.structure import PDFFitStructure, Structure, loadStructure # ---------------------------------------------------------------------------- @@ -145,14 +146,6 @@ def testParser2(datafile): def testGenerator( diffpy_srreal_available, diffpy_structure_available, datafile ): - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - - from diffpy.srreal.pdfcalculator import PDFCalculator - from diffpy.structure import PDFFitStructure - qmax = 27.0 gen = PDFGenerator() gen.set_scattering_type("N") From 768580b2121c2f02d2c7e1ed2b2e4fb3f2d346ed Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 14:10:03 -0400 Subject: [PATCH 25/30] pdfcontribution.py --- src/diffpy/cmipdf/pdfcontribution.py | 66 +++++++++++++--------------- tests/test_pdf.py | 21 ++++----- 2 files changed, 39 insertions(+), 48 deletions(-) diff --git a/src/diffpy/cmipdf/pdfcontribution.py b/src/diffpy/cmipdf/pdfcontribution.py index 8389fb6..3163107 100644 --- a/src/diffpy/cmipdf/pdfcontribution.py +++ b/src/diffpy/cmipdf/pdfcontribution.py @@ -21,6 +21,8 @@ __all__ = ["PDFContribution"] +from diffpy.cmipdf.debyepdfgenerator import DebyePDFGenerator +from diffpy.cmipdf.pdfgenerator import PDFGenerator from diffpy.srfit.fitbase import FitContribution, Profile, ProfileParser @@ -106,7 +108,7 @@ def __init__(self, name): # Data methods - def loadData(self, datafile): + def load_data(self, datafile): """Load the data from a datafile. Parameters @@ -121,7 +123,7 @@ def loadData(self, datafile): self.profile.load_parsed_data(parser) return - def setCalculationRange(self, xmin=None, xmax=None, dx=None): + def set_calculation_range(self, xmin=None, xmax=None, dx=None): """Set epsilon-inclusive calculation range. Adhere to the observed ``xobs`` points when ``dx`` is the same @@ -166,24 +168,24 @@ def savetxt(self, fname, **kwargs): # Phase methods - def addStructure(self, name, structure, periodic=True): + def add_structure(self, structure, name="phase", periodic=True): """Add a phase that goes into the PDF calculation. Parameters ---------- - name + structure : Structure object + `diffpy.structure.Structure`, `pyobjcryst.crystal.Crystal` or + `pyobjcryst.molecule.Molecule` instance. + name : str, optional A name to give the generator that will manage the PDF calculation from the passed structure. The adapted structure will be accessible via the name "phase" as an attribute of the generator, e.g. contribution.name.phase, where 'contribution' is this contribution and 'name' is passed name. - (default), then the name will be set as "phase". - structure - diffpy.structure.Structure, pyobjcryst.crystal.Crystal or - pyobjcryst.molecule.Molecule instance. Default None. - periodic - The structure should be treated as periodic. If this is + Default is `"phase"`. + periodic : bool, optional + The structure should be treated as periodic. If this is True (default), then a PDFGenerator will be used to calculate the PDF from the phase. Otherwise, a DebyePDFGenerator will be used. Note that some structures @@ -191,17 +193,15 @@ def addStructure(self, name, structure, periodic=True): ignored. - Returns the new phase (ParameterSet appropriate for what was passed in - structure.) + Returns + ------- + The new phase (ParameterSet appropriate for what was passed in + structure.) """ # Based on periodic, create the proper generator. if periodic: - from diffpy.cmipdf.pdfgenerator import PDFGenerator - gen = PDFGenerator(name) else: - from diffpy.cmipdf.debyepdfgenerator import DebyePDFGenerator - gen = DebyePDFGenerator(name) # Set up the generator @@ -210,24 +210,25 @@ def addStructure(self, name, structure, periodic=True): return gen.phase - def addPhase(self, name, parset, periodic=True): - """Add a phase that goes into the PDF calculation. + def add_structure_from_parset(self, parset, name, periodic=True): + """Add a phase that goes into the PDF calculation from a + ParameterSet. Parameters ---------- - name - A name to give the generator that will manage the PDF + parset : SrealParSet object + A SrRealParSet that holds the structural information. + This can be used to share the phase between multiple + BasePDFGenerators, and have the changes in one reflect in + another. + name : str + The name to give the generator that will manage the PDF calculation from the passed parameter phase. The parset will be accessible via the name "phase" as an attribute of the generator, e.g., contribution.name.phase, where 'contribution' is this contribution and 'name' is passed name. - parset - A SrRealParSet that holds the structural information. - This can be used to share the phase between multiple - BasePDFGenerators, and have the changes in one reflect in - another. - periodic + periodic : bool, optional The structure should be treated as periodic. If this is True (default), then a PDFGenerator will be used to calculate the PDF from the phase. Otherwise, a @@ -235,24 +236,19 @@ def addPhase(self, name, parset, periodic=True): do not support periodicity, in which case this may be ignored. - - Returns the new phase (ParameterSet appropriate for what was passed in - structure.) + Returns + ------- + The new phase (ParameterSet appropriate for what was passed in + parset.) """ # Based on periodic, create the proper generator. if periodic: - from diffpy.cmipdf.pdfgenerator import PDFGenerator - gen = PDFGenerator(name) else: - from diffpy.cmipdf.debyepdfgenerator import DebyePDFGenerator - gen = DebyePDFGenerator(name) - # Set up the generator gen.set_structure_from_parset(parset, periodic) self._setup_generator(gen) - return gen.phase def _setup_generator(self, gen): diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 3755f30..b270a4e 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -210,7 +210,7 @@ def test_setQmax(): """Check PDFContribution.setQmax()""" pc = PDFContribution("pdf") pc.set_qmax(21) - pc.addStructure("empty", Structure()) + pc.add_structure(Structure(), name="empty") assert 21 == pc.empty.get_qmax() pc.set_qmax(22) assert 22 == pc.get_qmax() @@ -228,7 +228,7 @@ def test_get_qmax(): assert 17 == pc1.get_qmax() # (2) contribution metadata pc2 = PDFContribution("pdf") - pc2.addStructure("empty", Structure()) + pc2.add_structure(Structure(), name="empty") pc2.empty.set_qmax(18) assert 18 == pc2.get_qmax() # (3) profile metadata @@ -242,17 +242,12 @@ def test_savetxt( diffpy_structure_available, diffpy_srreal_available, datafile ): "check PDFContribution.savetxt()" - if not diffpy_structure_available: - pytest.skip("diffpy.structure package not available") from diffpy.structure import Structure - if not diffpy_srreal_available: - pytest.skip("diffpy.srreal package not available") - pc = PDFContribution("pdf") - pc.loadData(datafile("si-q27r60-xray.gr")) - pc.setCalculationRange(0, 10) - pc.addStructure("empty", Structure()) + pc.load_data(datafile("si-q27r60-xray.gr")) + pc.set_calculation_range(0, 10) + pc.add_structure(Structure(), name="empty") fp = io.BytesIO() with pytest.raises(SrFitError): pc.savetxt(fp) @@ -269,13 +264,13 @@ def test_pickling( ): "validate PDFContribution.residual() after pickling." pc = PDFContribution("pdf") - pc.loadData(datafile("ni-q27r100-neutron.gr")) + pc.load_data(datafile("ni-q27r100-neutron.gr")) ciffile = datafile("ni.cif") cif_path = str(ciffile) ni = loadStructure(cif_path) ni.Uisoequiv = 0.003 - pc.addStructure("ni", ni) - pc.setCalculationRange(0, 10) + pc.add_structure(ni, name="ni") + pc.set_calculation_range(0, 10) pc2 = pickle.loads(pickle.dumps(pc)) res0 = pc.residual() assert numpy.array_equal(res0, pc2.residual()) From 10262a7cddd6402993f5b3f333d7ff8c77c5dd6b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 14:50:56 -0400 Subject: [PATCH 26/30] add method to generate a PDF from a structure --- src/diffpy/cmipdf/debyepdfgenerator.py | 98 +++++++++++++++++++++++++- src/diffpy/cmipdf/pdfgenerator.py | 95 ++++++++++++++++++++++++- 2 files changed, 189 insertions(+), 4 deletions(-) diff --git a/src/diffpy/cmipdf/debyepdfgenerator.py b/src/diffpy/cmipdf/debyepdfgenerator.py index 7eae00a..689b78d 100644 --- a/src/diffpy/cmipdf/debyepdfgenerator.py +++ b/src/diffpy/cmipdf/debyepdfgenerator.py @@ -23,7 +23,11 @@ __all__ = ["DebyePDFGenerator"] +from pathlib import Path + from diffpy.cmipdf.basepdfgenerator import BasePDFGenerator +from diffpy.srreal.pdfcalculator import DebyePDFCalculator +from diffpy.structure import loadStructure class DebyePDFGenerator(BasePDFGenerator): @@ -136,12 +140,102 @@ def set_structure_from_parset(self, parset, periodic=False): def __init__(self, name="pdf"): """Initialize the generator.""" - from diffpy.srreal.pdfcalculator import DebyePDFCalculator - BasePDFGenerator.__init__(self, name) self._set_calculator(DebyePDFCalculator()) return + def generate_pdf_from_structure( + self, + structure, + rmin=0, + rmax=30, + qmin=0.1, + qmax=25.0, + qdamp=0.03, + qbroad=0.0, + delta1=0.0, + delta2=0.0, + uiso=0.007, + ): + """Calculate the PDF from a structure and return G vs. r. + + This is a convenience method that allows the user to calculate + the PDF from a + structure without having to set up the calculator and + structure ParameterSet + manually. The structure can be passed as a path to a + structure file or as a + `diffpy.structure.Structure` object. + + Parameters + ---------- + structure : Path, str, or diffpy.structure.Structure + The structure to calculate the PDF from. Can be a path + to a structure file or a diffpy.structure.Structure + object. + rmin : float, optional + The minimum r value in Angstroms for the PDF (default 0). + rmax : float, optional + The maximum r value in Angstroms for the PDF (default 30). + qmin : float, optional + The minimum scattering vector used to generate the PDF + (default 0.1). + qmax : float, optional + The maximum scattering vector used to generate the PDF + (default 25.0). + uiso : float, optional + The isotropic atomic displacement parameter to use for all + atoms in the structure (default 0.007). + qdamp : float, optional + The resolution dampening term to use in the PDF calculation + (default 0.03). + qbroad : float, optional + The resolution broadening term to use in the PDF calculation + (default 0.0). + delta1 : float, optional + The linear peak broadening term to use in the PDF calculation + (default 0.0). + delta2 : float, optional + The quadratic peak broadening term to use in the PDF calculation + (default 0.0). + uiso : float, optional + The isotropic atomic displacement parameter to use for all + atoms in the structure (default 0.007). + + Returns + ------- + r : numpy.ndarray + The r values for the PDF in units of Angstroms. + G : numpy.ndarray + The G values for the PDF in units of 1/Angstrom^2. + + Example + ------- + .. code-block:: python + xyz_path = "path/to/nanoparticle.xyz" + gen = PDFGenerator() + r, g = gen.generate_pdf_from_structure(xyz_path) + """ + if isinstance(structure, Path): + structure = loadStructure(str(structure)) + elif isinstance(structure, str): + structure = loadStructure(structure) + structure.Uisoequiv = uiso + + calc = DebyePDFCalculator() + calc.qmin = qmin + calc.qmax = qmax + calc.rstep = 0.01 + calc.rmin = rmin + calc.rmax = rmax + calc.qdamp = qdamp + calc.qbroad = qbroad + calc.delta1 = delta1 + calc.delta2 = delta2 + + r, g = calc(structure) + return r, g + # End class DebyePDFGenerator diff --git a/src/diffpy/cmipdf/pdfgenerator.py b/src/diffpy/cmipdf/pdfgenerator.py index ebfe2e6..7db5905 100644 --- a/src/diffpy/cmipdf/pdfgenerator.py +++ b/src/diffpy/cmipdf/pdfgenerator.py @@ -25,7 +25,11 @@ __all__ = ["PDFGenerator"] +from pathlib import Path + from diffpy.cmipdf.basepdfgenerator import BasePDFGenerator +from diffpy.srreal.pdfcalculator import PDFCalculator +from diffpy.structure import loadStructure class PDFGenerator(BasePDFGenerator): @@ -86,11 +90,98 @@ class PDFGenerator(BasePDFGenerator): def __init__(self, name="pdf"): """Initialize the generator.""" - from diffpy.srreal.pdfcalculator import PDFCalculator - BasePDFGenerator.__init__(self, name) self._set_calculator(PDFCalculator()) return + def generate_pdf_from_structure( + self, + structure, + rmin=0, + rmax=30, + qmin=0.1, + qmax=25.0, + qdamp=0.03, + qbroad=0.0, + delta1=0.0, + delta2=0.0, + uiso=0.007, + ): + """Calculate the PDF from a structure and return G vs. r. + + This is a convenience method that allows the user to calculate + the PDF from a + structure without having to set up the calculator and + structure ParameterSet + manually. The structure can be passed as a path to a + structure file or as a + `diffpy.structure.Structure` object. + + Parameters + ---------- + structure : Path, str, or diffpy.structure.Structure + The structure to calculate the PDF from. Can be a path + to a structure file or a diffpy.structure.Structure + object. + rmin : float, optional + The minimum r value in Angstroms for the PDF (default 0). + rmax : float, optional + The maximum r value in Angstroms for the PDF (default 30). + qmin : float, optional + The minimum scattering vector used to generate the PDF + (default 0.1). + qmax : float, optional + The maximum scattering vector used to generate the PDF + (default 25.0). + qdamp : float, optional + The resolution dampening term to use in the PDF calculation + (default 0.03). + qbroad : float, optional + The resolution broadening term to use in the PDF calculation + (default 0.0). + delta1 : float, optional + The linear peak broadening term to use in the PDF calculation + (default 0.0). + delta2 : float, optional + The quadratic peak broadening term to use in the PDF calculation + (default 0.0). + uiso : float, optional + The isotropic atomic displacement parameter to use for all + atoms in the structure (default 0.007). + + Returns + ------- + r : numpy.ndarray + The r values for the PDF in units of Angstroms. + G : numpy.ndarray + The G values for the PDF in units of 1/Angstrom^2. + + Example + ------- + .. code-block:: python + cif_path = "path/to/ni.cif" + gen = PDFGenerator() + r, g = gen.generate_pdf_from_structure(cifpath) + """ + if isinstance(structure, Path): + structure = loadStructure(str(structure)) + elif isinstance(structure, str): + structure = loadStructure(structure) + structure.Uisoequiv = uiso + + calc = PDFCalculator() + calc.qmin = qmin + calc.qmax = qmax + calc.rstep = 0.01 + calc.rmin = rmin + calc.rmax = rmax + calc.qdamp = qdamp + calc.qbroad = qbroad + calc.delta1 = delta1 + calc.delta2 = delta2 + + r, g = calc(structure) + return r, g + # End class PDFGenerator From 852ae74a7b77e2a577dca9652624132af7a0baeb Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 15:03:18 -0400 Subject: [PATCH 27/30] break out tests into separate files --- tests/test_generators.py | 67 ++++++++ tests/test_parser.py | 139 +++++++++++++++++ tests/test_pdf.py | 284 ---------------------------------- tests/test_pdfcontribution.py | 83 ++++++++++ 4 files changed, 289 insertions(+), 284 deletions(-) create mode 100644 tests/test_generators.py create mode 100644 tests/test_parser.py delete mode 100644 tests/test_pdf.py create mode 100644 tests/test_pdfcontribution.py diff --git a/tests/test_generators.py b/tests/test_generators.py new file mode 100644 index 0000000..b440992 --- /dev/null +++ b/tests/test_generators.py @@ -0,0 +1,67 @@ +import numpy as np +import pytest + +from diffpy.cmipdf import PDFGenerator +from diffpy.srreal.pdfcalculator import PDFCalculator +from diffpy.structure import PDFFitStructure + + +def testGenerator(datafile): + qmax = 27.0 + gen = PDFGenerator() + gen.set_scattering_type("N") + assert "N" == gen.get_scattering_type() + gen.set_qmax(qmax) + assert qmax == pytest.approx(gen.get_qmax()) + + structure = PDFFitStructure() + ciffile = datafile("ni.cif") + cif_path = str(ciffile) + structure.read(cif_path) + for i in range(4): + structure[i].Bisoequiv = 1 + gen.set_structure(structure) + + calc = gen._calc + # Test parameters + for par in gen.iterate_over_parameters(recurse=False): + pname = par.name + defval = calc._getDoubleAttr(pname) + assert defval == par.getValue() + # Test setting values + par.set_value(1.0) + assert 1.0 == par.getValue() + par.set_value(defval) + assert defval == par.getValue() + + r = np.arange(0, 10, 0.1) + y = gen(r) + + # Now create a reference PDF. Since the calculator is testing its + # output, we just have to make sure we can calculate from the + # PDFGenerator interface. + + calc = PDFCalculator() + calc.rstep = r[1] - r[0] + calc.rmin = r[0] + calc.rmax = r[-1] + 0.5 * calc.rstep + calc.qmax = qmax + calc.setScatteringFactorTableByType("N") + calc.eval(structure) + yref = calc.pdf + + diff = y - yref + res = np.dot(diff, diff) + assert 0 == pytest.approx(res) + return + + +def test_set_qmin(): + """Verify qmin is propagated to the calculator object.""" + gen = PDFGenerator() + assert 0 == gen.get_qmin() + assert 0 == gen._calc.qmin + gen.set_qmin(0.93) + assert 0.93 == gen.get_qmin() + assert 0.93 == gen._calc.qmin + return diff --git a/tests/test_parser.py b/tests/test_parser.py new file mode 100644 index 0000000..3c7952c --- /dev/null +++ b/tests/test_parser.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2025 Simon Billinge. +# All rights reserved. +# +# File coded by: Caden Myers, Simon Billinge, and members of the Billinge +# group. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.cmipdf/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## +"""Tests for pdf package.""" + + +import numpy as np +import pytest + +from diffpy.srfit.fitbase import ProfileParser + +# ---------------------------------------------------------------------------- + + +# The tests in this file are for ProfileParser which belongs to diffpy.srfit, +# but since it is used here, we will test it here on test data with modern +# diffpy format. +def testParser1(datafile): + filename = datafile("ni-q27r100-neutron.gr") + parser = ProfileParser() + parser.parse_file(filename) + + meta = parser._meta + + assert str(filename) == meta["filename"] + assert 1 == meta["nbanks"] + assert "N" == meta["stype"] + assert 27 == meta["qmax"] + assert 300 == meta.get("temperature") + assert meta.get("qdamp") is None + assert meta.get("qbroad") is None + assert meta.get("spdiameter") is None + assert meta.get("scale") is None + assert meta.get("doping") is None + + x, y, dx, dy = parser.get_data() + assert dx.tolist() == len(x) * [0] + assert dy.tolist() == len(x) * [0] + + testx = np.linspace(0.01, 100, 10000) + diff = testx - x + res = np.dot(diff, diff) + assert 0 == pytest.approx(res) + + testy = np.array( + [ + 1.144, + 2.258, + 3.312, + 4.279, + 5.135, + 5.862, + 6.445, + 6.875, + 7.150, + 7.272, + ] + ) + diff = testy - y[:10] + res = np.dot(diff, diff) + assert 0 == pytest.approx(res) + + return + + +def testParser2(datafile): + data = datafile("si-q27r60-xray.gr") + parser = ProfileParser() + parser.parse_file(data) + + meta = parser._meta + + assert str(data) == meta["filename"] + assert 1 == meta["nbanks"] + assert "X" == meta["stype"] + assert 27 == meta["qmax"] + assert 300 == meta.get("temperature") + assert meta.get("qdamp") is None + assert meta.get("qbroad") is None + assert meta.get("spdiameter") is None + assert meta.get("scale") is None + assert meta.get("doping") is None + + x, y, dx, dy = parser.get_data() + testx = np.linspace(0.01, 60, 5999, endpoint=False) + diff = testx - x + res = np.dot(diff, diff) + assert 0 == pytest.approx(res) + + testy = np.array( + [ + 0.1105784, + 0.2199684, + 0.3270088, + 0.4305913, + 0.5296853, + 0.6233606, + 0.7108060, + 0.7913456, + 0.8644501, + 0.9297440, + ] + ) + diff = testy - y[:10] + res = np.dot(diff, diff) + assert 0 == pytest.approx(res) + + testdy = np.array( + [ + 0.001802192, + 0.003521449, + 0.005079115, + 0.006404892, + 0.007440527, + 0.008142955, + 0.008486813, + 0.008466340, + 0.008096858, + 0.007416456, + ] + ) + diff = testdy - dy[:10] + res = np.dot(diff, diff) + assert 0 == pytest.approx(res) + + assert dx.tolist() == [0] * len(dx) + return diff --git a/tests/test_pdf.py b/tests/test_pdf.py deleted file mode 100644 index b270a4e..0000000 --- a/tests/test_pdf.py +++ /dev/null @@ -1,284 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# (c) 2025 Simon Billinge. -# All rights reserved. -# -# File coded by: Caden Myers, Simon Billinge, and members of the Billinge -# group. -# -# See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.cmipdf/graphs/contributors -# -# See LICENSE.rst for license information. -# -############################################################################## -"""Tests for pdf package.""" - -import io -import pickle -from itertools import chain - -import numpy -import pytest - -from diffpy.cmipdf import PDFContribution, PDFGenerator -from diffpy.srfit.exceptions import SrFitError -from diffpy.srfit.fitbase import ProfileParser -from diffpy.srreal.pdfcalculator import PDFCalculator -from diffpy.structure import PDFFitStructure, Structure, loadStructure - -# ---------------------------------------------------------------------------- - - -def testParser1(datafile): - filename = datafile("ni-q27r100-neutron.gr") - parser = ProfileParser() - parser.parse_file(filename) - - meta = parser._meta - - assert str(filename) == meta["filename"] - assert 1 == meta["nbanks"] - assert "N" == meta["stype"] - assert 27 == meta["qmax"] - assert 300 == meta.get("temperature") - assert meta.get("qdamp") is None - assert meta.get("qbroad") is None - assert meta.get("spdiameter") is None - assert meta.get("scale") is None - assert meta.get("doping") is None - - x, y, dx, dy = parser.get_data() - assert dx.tolist() == len(x) * [0] - assert dy.tolist() == len(x) * [0] - - testx = numpy.linspace(0.01, 100, 10000) - diff = testx - x - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - testy = numpy.array( - [ - 1.144, - 2.258, - 3.312, - 4.279, - 5.135, - 5.862, - 6.445, - 6.875, - 7.150, - 7.272, - ] - ) - diff = testy - y[:10] - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - return - - -def testParser2(datafile): - data = datafile("si-q27r60-xray.gr") - parser = ProfileParser() - parser.parse_file(data) - - meta = parser._meta - - assert str(data) == meta["filename"] - assert 1 == meta["nbanks"] - assert "X" == meta["stype"] - assert 27 == meta["qmax"] - assert 300 == meta.get("temperature") - assert meta.get("qdamp") is None - assert meta.get("qbroad") is None - assert meta.get("spdiameter") is None - assert meta.get("scale") is None - assert meta.get("doping") is None - - x, y, dx, dy = parser.get_data() - testx = numpy.linspace(0.01, 60, 5999, endpoint=False) - diff = testx - x - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - testy = numpy.array( - [ - 0.1105784, - 0.2199684, - 0.3270088, - 0.4305913, - 0.5296853, - 0.6233606, - 0.7108060, - 0.7913456, - 0.8644501, - 0.9297440, - ] - ) - diff = testy - y[:10] - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - testdy = numpy.array( - [ - 0.001802192, - 0.003521449, - 0.005079115, - 0.006404892, - 0.007440527, - 0.008142955, - 0.008486813, - 0.008466340, - 0.008096858, - 0.007416456, - ] - ) - diff = testdy - dy[:10] - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - - assert dx.tolist() == [0] * len(dx) - return - - -def testGenerator( - diffpy_srreal_available, diffpy_structure_available, datafile -): - qmax = 27.0 - gen = PDFGenerator() - gen.set_scattering_type("N") - assert "N" == gen.get_scattering_type() - gen.set_qmax(qmax) - assert qmax == pytest.approx(gen.get_qmax()) - - structure = PDFFitStructure() - ciffile = datafile("ni.cif") - cif_path = str(ciffile) - structure.read(cif_path) - for i in range(4): - structure[i].Bisoequiv = 1 - gen.set_structure(structure) - - calc = gen._calc - # Test parameters - for par in gen.iterate_over_parameters(recurse=False): - pname = par.name - defval = calc._getDoubleAttr(pname) - assert defval == par.getValue() - # Test setting values - par.set_value(1.0) - assert 1.0 == par.getValue() - par.set_value(defval) - assert defval == par.getValue() - - r = numpy.arange(0, 10, 0.1) - y = gen(r) - - # Now create a reference PDF. Since the calculator is testing its - # output, we just have to make sure we can calculate from the - # PDFGenerator interface. - - calc = PDFCalculator() - calc.rstep = r[1] - r[0] - calc.rmin = r[0] - calc.rmax = r[-1] + 0.5 * calc.rstep - calc.qmax = qmax - calc.setScatteringFactorTableByType("N") - calc.eval(structure) - yref = calc.pdf - - diff = y - yref - res = numpy.dot(diff, diff) - assert 0 == pytest.approx(res) - return - - -def test_set_qmin(): - """Verify qmin is propagated to the calculator object.""" - gen = PDFGenerator() - assert 0 == gen.get_qmin() - assert 0 == gen._calc.qmin - gen.set_qmin(0.93) - assert 0.93 == gen.get_qmin() - assert 0.93 == gen._calc.qmin - return - - -def test_setQmax(): - """Check PDFContribution.setQmax()""" - pc = PDFContribution("pdf") - pc.set_qmax(21) - pc.add_structure(Structure(), name="empty") - assert 21 == pc.empty.get_qmax() - pc.set_qmax(22) - assert 22 == pc.get_qmax() - assert 22 == pc.empty.get_qmax() - return - - -def test_get_qmax(): - """Check PDFContribution.get_qmax()""" - # cover all code branches in PDFContribution._get_meta_value - # (1) contribution metadata - pc1 = PDFContribution("pdf") - assert pc1.get_qmax() is None - pc1.set_qmax(17) - assert 17 == pc1.get_qmax() - # (2) contribution metadata - pc2 = PDFContribution("pdf") - pc2.add_structure(Structure(), name="empty") - pc2.empty.set_qmax(18) - assert 18 == pc2.get_qmax() - # (3) profile metadata - pc3 = PDFContribution("pdf") - pc3.profile.meta["qmax"] = 19 - assert 19 == pc3.get_qmax() - return - - -def test_savetxt( - diffpy_structure_available, diffpy_srreal_available, datafile -): - "check PDFContribution.savetxt()" - from diffpy.structure import Structure - - pc = PDFContribution("pdf") - pc.load_data(datafile("si-q27r60-xray.gr")) - pc.set_calculation_range(0, 10) - pc.add_structure(Structure(), name="empty") - fp = io.BytesIO() - with pytest.raises(SrFitError): - pc.savetxt(fp) - pc.evaluate() - pc.savetxt(fp) - txt = fp.getvalue().decode() - nlines = len(txt.strip().split("\n")) - assert 1001 == nlines - return - - -def test_pickling( - diffpy_structure_available, diffpy_srreal_available, datafile -): - "validate PDFContribution.residual() after pickling." - pc = PDFContribution("pdf") - pc.load_data(datafile("ni-q27r100-neutron.gr")) - ciffile = datafile("ni.cif") - cif_path = str(ciffile) - ni = loadStructure(cif_path) - ni.Uisoequiv = 0.003 - pc.add_structure(ni, name="ni") - pc.set_calculation_range(0, 10) - pc2 = pickle.loads(pickle.dumps(pc)) - res0 = pc.residual() - assert numpy.array_equal(res0, pc2.residual()) - for p in chain( - pc.iterate_over_parameters("Uiso"), pc2.iterate_over_parameters("Uiso") - ): - p.value = 0.004 - res1 = pc.residual() - assert not numpy.allclose(res0, res1) - assert numpy.array_equal(res1, pc2.residual()) - return diff --git a/tests/test_pdfcontribution.py b/tests/test_pdfcontribution.py new file mode 100644 index 0000000..4931d08 --- /dev/null +++ b/tests/test_pdfcontribution.py @@ -0,0 +1,83 @@ +import io +import pickle +from itertools import chain + +import numpy as np +import pytest + +from diffpy.cmipdf import PDFContribution +from diffpy.srfit.exceptions import SrFitError +from diffpy.structure import Structure, loadStructure + + +def test_set_qmax(): + """Check PDFContribution.setQmax()""" + pc = PDFContribution("pdf") + pc.set_qmax(21) + pc.add_structure(Structure(), name="empty") + assert 21 == pc.empty.get_qmax() + pc.set_qmax(22) + assert 22 == pc.get_qmax() + assert 22 == pc.empty.get_qmax() + return + + +def test_get_qmax(): + """Check PDFContribution.get_qmax()""" + # cover all code branches in PDFContribution._get_meta_value + # (1) contribution metadata + pc1 = PDFContribution("pdf") + assert pc1.get_qmax() is None + pc1.set_qmax(17) + assert 17 == pc1.get_qmax() + # (2) contribution metadata + pc2 = PDFContribution("pdf") + pc2.add_structure(Structure(), name="empty") + pc2.empty.set_qmax(18) + assert 18 == pc2.get_qmax() + # (3) profile metadata + pc3 = PDFContribution("pdf") + pc3.profile.meta["qmax"] = 19 + assert 19 == pc3.get_qmax() + return + + +def test_savetxt(datafile): + "check PDFContribution.savetxt()" + + pc = PDFContribution("pdf") + pc.load_data(datafile("si-q27r60-xray.gr")) + pc.set_calculation_range(0, 10) + pc.add_structure(Structure(), name="empty") + fp = io.BytesIO() + with pytest.raises(SrFitError): + pc.savetxt(fp) + pc.evaluate() + pc.savetxt(fp) + txt = fp.getvalue().decode() + nlines = len(txt.strip().split("\n")) + assert 1001 == nlines + return + + +def test_pickling(datafile): + "validate PDFContribution.residual() after pickling." + pc = PDFContribution("pdf") + pc.load_data(datafile("ni-q27r100-neutron.gr")) + ciffile = datafile("ni.cif") + cif_path = str(ciffile) + ni = loadStructure(cif_path) + ni.Uisoequiv = 0.003 + pc.add_structure(ni, name="ni") + pc.set_calculation_range(0, 10) + pc2 = pickle.loads(pickle.dumps(pc)) + res0 = pc.residual() + assert np.array_equal(res0, pc2.residual()) + for p in chain( + pc.iterate_over_parameters("Uiso"), pc2.iterate_over_parameters("Uiso") + ): + p.value = 0.004 + res1 = pc.residual() + assert not np.allclose(res0, res1) + assert np.array_equal(res1, pc2.residual()) + return From ed5b1739a34fe67ab2d302cf97084447fe6a7a9d Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 17:11:24 -0400 Subject: [PATCH 28/30] rm PDF generator functions, will put on another PR --- .pre-commit-config.yaml | 20 +++--- src/diffpy/cmipdf/debyepdfgenerator.py | 94 -------------------------- src/diffpy/cmipdf/pdfgenerator.py | 91 ------------------------- 3 files changed, 10 insertions(+), 195 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e4a84d..f189a94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: submodules: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: - id: check-yaml - id: end-of-file-fixer @@ -21,45 +21,45 @@ repos: - id: check-toml - id: check-added-large-files - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 26.3.1 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.3.0 hooks: - id: flake8 - repo: https://github.com/pycqa/isort - rev: 5.13.2 + rev: 8.0.1 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/kynan/nbstripout - rev: 0.7.1 + rev: 0.9.1 hooks: - id: nbstripout - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: no-commit-to-branch name: Prevent Commit to Main Branch args: ["--branch", "main"] stages: [pre-commit] - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.2 hooks: - id: codespell additional_dependencies: - tomli # prettier - multi formatter for .json, .yml, and .md files - repo: https://github.com/pre-commit/mirrors-prettier - rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 + rev: v4.0.0-alpha.8 hooks: - id: prettier additional_dependencies: - "prettier@^3.2.4" # docformatter - PEP 257 compliant docstring formatter - - repo: https://github.com/s-weigand/docformatter - rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + - repo: https://github.com/PyCQA/docformatter + rev: v1.7.7 hooks: - id: docformatter additional_dependencies: [tomli] diff --git a/src/diffpy/cmipdf/debyepdfgenerator.py b/src/diffpy/cmipdf/debyepdfgenerator.py index 689b78d..1825b5d 100644 --- a/src/diffpy/cmipdf/debyepdfgenerator.py +++ b/src/diffpy/cmipdf/debyepdfgenerator.py @@ -23,11 +23,9 @@ __all__ = ["DebyePDFGenerator"] -from pathlib import Path from diffpy.cmipdf.basepdfgenerator import BasePDFGenerator from diffpy.srreal.pdfcalculator import DebyePDFCalculator -from diffpy.structure import loadStructure class DebyePDFGenerator(BasePDFGenerator): @@ -144,98 +142,6 @@ def __init__(self, name="pdf"): self._set_calculator(DebyePDFCalculator()) return - def generate_pdf_from_structure( - self, - structure, - rmin=0, - rmax=30, - qmin=0.1, - qmax=25.0, - qdamp=0.03, - qbroad=0.0, - delta1=0.0, - delta2=0.0, - uiso=0.007, - ): - """Calculate the PDF from a structure and return G vs. r. - - This is a convenience method that allows the user to calculate - the PDF from a - structure without having to set up the calculator and - structure ParameterSet - manually. The structure can be passed as a path to a - structure file or as a - `diffpy.structure.Structure` object. - - Parameters - ---------- - structure : Path, str, or diffpy.structure.Structure - The structure to calculate the PDF from. Can be a path - to a structure file or a diffpy.structure.Structure - object. - rmin : float, optional - The minimum r value in Angstroms for the PDF (default 0). - rmax : float, optional - The maximum r value in Angstroms for the PDF (default 30). - qmin : float, optional - The minimum scattering vector used to generate the PDF - (default 0.1). - qmax : float, optional - The maximum scattering vector used to generate the PDF - (default 25.0). - uiso : float, optional - The isotropic atomic displacement parameter to use for all - atoms in the structure (default 0.007). - qdamp : float, optional - The resolution dampening term to use in the PDF calculation - (default 0.03). - qbroad : float, optional - The resolution broadening term to use in the PDF calculation - (default 0.0). - delta1 : float, optional - The linear peak broadening term to use in the PDF calculation - (default 0.0). - delta2 : float, optional - The quadratic peak broadening term to use in the PDF calculation - (default 0.0). - uiso : float, optional - The isotropic atomic displacement parameter to use for all - atoms in the structure (default 0.007). - - Returns - ------- - r : numpy.ndarray - The r values for the PDF in units of Angstroms. - G : numpy.ndarray - The G values for the PDF in units of 1/Angstrom^2. - - Example - ------- - .. code-block:: python - xyz_path = "path/to/nanoparticle.xyz" - gen = PDFGenerator() - r, g = gen.generate_pdf_from_structure(xyz_path) - """ - if isinstance(structure, Path): - structure = loadStructure(str(structure)) - elif isinstance(structure, str): - structure = loadStructure(structure) - structure.Uisoequiv = uiso - - calc = DebyePDFCalculator() - calc.qmin = qmin - calc.qmax = qmax - calc.rstep = 0.01 - calc.rmin = rmin - calc.rmax = rmax - calc.qdamp = qdamp - calc.qbroad = qbroad - calc.delta1 = delta1 - calc.delta2 = delta2 - - r, g = calc(structure) - return r, g - # End class DebyePDFGenerator diff --git a/src/diffpy/cmipdf/pdfgenerator.py b/src/diffpy/cmipdf/pdfgenerator.py index 7db5905..9f71c97 100644 --- a/src/diffpy/cmipdf/pdfgenerator.py +++ b/src/diffpy/cmipdf/pdfgenerator.py @@ -25,11 +25,9 @@ __all__ = ["PDFGenerator"] -from pathlib import Path from diffpy.cmipdf.basepdfgenerator import BasePDFGenerator from diffpy.srreal.pdfcalculator import PDFCalculator -from diffpy.structure import loadStructure class PDFGenerator(BasePDFGenerator): @@ -94,94 +92,5 @@ def __init__(self, name="pdf"): self._set_calculator(PDFCalculator()) return - def generate_pdf_from_structure( - self, - structure, - rmin=0, - rmax=30, - qmin=0.1, - qmax=25.0, - qdamp=0.03, - qbroad=0.0, - delta1=0.0, - delta2=0.0, - uiso=0.007, - ): - """Calculate the PDF from a structure and return G vs. r. - - This is a convenience method that allows the user to calculate - the PDF from a - structure without having to set up the calculator and - structure ParameterSet - manually. The structure can be passed as a path to a - structure file or as a - `diffpy.structure.Structure` object. - - Parameters - ---------- - structure : Path, str, or diffpy.structure.Structure - The structure to calculate the PDF from. Can be a path - to a structure file or a diffpy.structure.Structure - object. - rmin : float, optional - The minimum r value in Angstroms for the PDF (default 0). - rmax : float, optional - The maximum r value in Angstroms for the PDF (default 30). - qmin : float, optional - The minimum scattering vector used to generate the PDF - (default 0.1). - qmax : float, optional - The maximum scattering vector used to generate the PDF - (default 25.0). - qdamp : float, optional - The resolution dampening term to use in the PDF calculation - (default 0.03). - qbroad : float, optional - The resolution broadening term to use in the PDF calculation - (default 0.0). - delta1 : float, optional - The linear peak broadening term to use in the PDF calculation - (default 0.0). - delta2 : float, optional - The quadratic peak broadening term to use in the PDF calculation - (default 0.0). - uiso : float, optional - The isotropic atomic displacement parameter to use for all - atoms in the structure (default 0.007). - - Returns - ------- - r : numpy.ndarray - The r values for the PDF in units of Angstroms. - G : numpy.ndarray - The G values for the PDF in units of 1/Angstrom^2. - - Example - ------- - .. code-block:: python - cif_path = "path/to/ni.cif" - gen = PDFGenerator() - r, g = gen.generate_pdf_from_structure(cifpath) - """ - if isinstance(structure, Path): - structure = loadStructure(str(structure)) - elif isinstance(structure, str): - structure = loadStructure(structure) - structure.Uisoequiv = uiso - - calc = PDFCalculator() - calc.qmin = qmin - calc.qmax = qmax - calc.rstep = 0.01 - calc.rmin = rmin - calc.rmax = rmax - calc.qdamp = qdamp - calc.qbroad = qbroad - calc.delta1 = delta1 - calc.delta2 = delta2 - - r, g = calc(structure) - return r, g - # End class PDFGenerator From 30127270e52502f006389fb93dca2223aa2c410d Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 17:12:03 -0400 Subject: [PATCH 29/30] news --- news/migrate-changes.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/migrate-changes.rst diff --git a/news/migrate-changes.rst b/news/migrate-changes.rst new file mode 100644 index 0000000..caff897 --- /dev/null +++ b/news/migrate-changes.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: updated all functions to be snake_case. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 688a5fa291325d95d7952a9aea2b3cd22b22ff78 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 19 Mar 2026 17:23:00 -0400 Subject: [PATCH 30/30] add srreal srfit and structure to this conda.txt and pip.txt --- requirements/conda.txt | 1 + requirements/pip.txt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/requirements/conda.txt b/requirements/conda.txt index 6b826ba..6b1daa8 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -1,3 +1,4 @@ numpy +diffpy.srreal diffpy.srfit diffpy.structure diff --git a/requirements/pip.txt b/requirements/pip.txt index 24ce15a..6b1daa8 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1 +1,4 @@ numpy +diffpy.srreal +diffpy.srfit +diffpy.structure