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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions docs/examples/coreshellnp.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@
FitRecipe,
FitResults,
Profile,
ProfileParser,
)
from diffpy.srfit.pdf import PDFGenerator
from diffpy.srfit.pdf import PDFGenerator, PDFParser

# Example Code

Expand All @@ -42,8 +41,8 @@ def makeRecipe(stru1, stru2, datname):
profile = Profile()

# Load data and add it to the profile
parser = ProfileParser()
parser.parseFile(datname)
parser = PDFParser()
parser.parse_file(datname)
profile.load_parsed_data(parser)
profile.set_calculation_range(xmin=1.5, xmax=45, dx=0.1)

Expand Down
11 changes: 5 additions & 6 deletions docs/examples/crystalpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@
FitRecipe,
FitResults,
Profile,
ProfileParser,
)
from diffpy.srfit.pdf import PDFGenerator
from diffpy.srfit.pdf import PDFGenerator, PDFParser
from diffpy.structure import Structure

######
Expand All @@ -48,13 +47,13 @@ def makeRecipe(ciffile, datname):
profile = Profile()

# Load data and add it to the Profile. Unlike in other examples, we use a
# class (ProfileParser) to help us load the data. This class will read the
# class (PDFParser) to help us load the data. This class will read the
# data and relevant metadata from a two- to four-column data file generated
# with PDFGetX2 or PDFGetN. The metadata will be passed to the PDFGenerator
# when they are associated in the FitContribution, which saves some
# configuration steps.
parser = ProfileParser()
parser.parseFile(datname)
parser = PDFParser()
parser.parse_file(datname)
profile.load_parsed_data(parser)
profile.set_calculation_range(xmax=20)

Expand All @@ -63,7 +62,7 @@ def makeRecipe(ciffile, datname):
# we want to refine a Structure object from diffpy.structure. We tell the
# PDFGenerator that with the 'setStructure' method. All other configuration
# options will be inferred from the metadata that is read by the
# ProfileParser.
# PDFParser.
# In particular, this will set the scattering type (x-ray or neutron), the
# Qmax value, as well as initial values for the non-structural Parameters.
generator = PDFGenerator("G")
Expand Down
7 changes: 3 additions & 4 deletions docs/examples/crystalpdfall.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@
FitRecipe,
FitResults,
Profile,
ProfileParser,
)
from diffpy.srfit.pdf import PDFGenerator
from diffpy.srfit.pdf import PDFGenerator, PDFParser

######
# Example Code
Expand All @@ -38,8 +37,8 @@
def makeProfile(datafile):
"""Make an place data within a Profile."""
profile = Profile()
parser = ProfileParser()
parser.parseFile(datafile)
parser = PDFParser()
parser.parse_file(datafile)
profile.load_parsed_data(parser)
profile.set_calculation_range(xmax=20)
return profile
Expand Down
9 changes: 4 additions & 5 deletions docs/examples/crystalpdfobjcryst.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@
FitRecipe,
FitResults,
Profile,
ProfileParser,
)
from diffpy.srfit.pdf import PDFGenerator
from diffpy.srfit.pdf import PDFGenerator, PDFParser

######
# Example Code
Expand All @@ -42,12 +41,12 @@ def makeRecipe(ciffile, datname):
# This will be used to store the observed and calculated PDF profile.
profile = Profile()

# Load data and add it to the Profile. As before we use a ProfileParser.
# Load data and add it to the Profile. As before we use a PDFParser.
# The metadata is still passed to the PDFGenerator later on.
# The interaction between the PDFGenerator and the metadata does not
# depend on type of structure being refined.
parser = ProfileParser()
parser.parseFile(datname)
parser = PDFParser()
parser.parse_file(datname)
profile.load_parsed_data(parser)
profile.set_calculation_range(xmax=20)

Expand Down
11 changes: 5 additions & 6 deletions docs/examples/crystalpdftwodata.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@
FitRecipe,
FitResults,
Profile,
ProfileParser,
)
from diffpy.srfit.pdf import PDFGenerator
from diffpy.srfit.pdf import PDFGenerator, PDFParser

######
# Example Code
Expand All @@ -46,13 +45,13 @@ def makeRecipe(ciffile, xdatname, ndatname):
nprofile = Profile()

# Load data and add it to the proper Profile.
parser = ProfileParser()
parser.parseFile(xdatname)
parser = PDFParser()
parser.parse_file(xdatname)
xprofile.load_parsed_data(parser)
xprofile.set_calculation_range(xmax=20)

parser = ProfileParser()
parser.parseFile(ndatname)
parser = PDFParser()
parser.parse_file(ndatname)
nprofile.load_parsed_data(parser)
nprofile.set_calculation_range(xmax=20)

Expand Down
7 changes: 3 additions & 4 deletions docs/examples/crystalpdftwophase.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@
FitRecipe,
FitResults,
Profile,
ProfileParser,
)
from diffpy.srfit.pdf import PDFGenerator
from diffpy.srfit.pdf import PDFGenerator, PDFParser

######
# Example Code
Expand All @@ -43,8 +42,8 @@ def makeRecipe(niciffile, siciffile, datname):
profile = Profile()

# Load data and add it to the profile
parser = ProfileParser()
parser.parseFile(datname)
parser = PDFParser()
parser.parse_file(datname)
profile.load_parsed_data(parser)
profile.set_calculation_range(xmax=20)

Expand Down
2 changes: 1 addition & 1 deletion docs/examples/ellipsoidsas.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def makeRecipe(datname):
# Load data and add it to the Profile. We use a SASParser to load the data
# properly and pass the metadata along.
parser = SASParser()
parser.parseFile(datname)
parser.parse_file(datname)
profile.load_parsed_data(parser)

# The ProfileGenerator
Expand Down
7 changes: 3 additions & 4 deletions docs/examples/nppdfcrystal.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,17 @@
FitRecipe,
FitResults,
Profile,
ProfileParser,
)
from diffpy.srfit.pdf import PDFGenerator
from diffpy.srfit.pdf import PDFGenerator, PDFParser


def makeRecipe(ciffile, grdata):
"""Make a recipe to model a crystal-like nanoparticle PDF."""
# Set up a PDF fit as has been done in other examples.
pdfprofile = Profile()

pdfparser = ProfileParser()
pdfparser.parseFile(grdata)
pdfparser = PDFParser()
pdfparser.parse_file(grdata)
pdfprofile.load_parsed_data(pdfparser)
pdfprofile.set_calculation_range(xmin=0.1, xmax=20)

Expand Down
9 changes: 4 additions & 5 deletions docs/examples/nppdfsas.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@
FitRecipe,
FitResults,
Profile,
ProfileParser,
)
from diffpy.srfit.pdf import PDFGenerator
from diffpy.srfit.pdf import PDFGenerator, PDFParser
from diffpy.srfit.pdf.characteristicfunctions import SASCF
from diffpy.srfit.sas import SASGenerator, SASParser

Expand All @@ -47,8 +46,8 @@ def makeRecipe(ciffile, grdata, iqdata):
"""
# Create a PDF contribution as before
pdfprofile = Profile()
pdfparser = ProfileParser()
pdfparser.parseFile(grdata)
pdfparser = PDFParser()
pdfparser.parse_file(grdata)
pdfprofile.load_parsed_data(pdfparser)
pdfprofile.set_calculation_range(xmin=0.1, xmax=20)

Expand All @@ -66,7 +65,7 @@ def makeRecipe(ciffile, grdata, iqdata):
# elliptical.
sasprofile = Profile()
sasparser = SASParser()
sasparser.parseFile(iqdata)
sasparser.parse_file(iqdata)
sasprofile.load_parsed_data(sasparser)
if all(sasprofile.dy == 0):
sasprofile.dy[:] = 1
Expand Down
75 changes: 55 additions & 20 deletions docs/source/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,29 +122,65 @@ functions as in the second example.
Extending Profile Parsers
--------------------------

The ``ProfileParser`` class is located in the ``diffpy.srfit.fitbase.parser``
module. The purpose of this class is to read data and metadata from a file or
string and pass those data and metadata to a ``Profile`` instance. The
``Profile`` in turn will pass this information to a ``ProfileGenerator``.
The ``ProfileParser`` class is located in the
``diffpy.srfit.fitbase.profileparser`` module. The purpose of this class is to
read data and metadata from a file and pass those data and metadata to a
``Profile`` instance. The ``Profile`` in turn will pass this information to a
``ProfileGenerator``.

The simplest way to extend the ``ProfileParser`` is to derive a new class from
``ProfileParser`` and overload the ``parseString`` method. By default, the
``parseFile`` method can read an ASCII file and passes the loaded string to the
``parseString`` method. For non-ASCII data one should overload both of these
methods. An example of a customized ``ProfileParser`` is the ``PDFParser``
class in the ``diffpy.srfit.pdf.pdfparser`` module.
``parse_file`` is a template method that a subclass is not expected to
override. It resets the parser, calls the two hooks described below, records
the file name and selects the first bank. A format is customized by overriding
one or both hooks:

Here is a simple example demonstrating how to extract (x,y) data from a
two-column string. ::
``_parse_metadata(filename)``
Return the metadata read from the header, as a dictionary. The default
implementation uses ``load_data`` from ``diffpy.utils.parsers`` to collect
plain ``name = value`` pairs.

def parseString(self, datastring):
``_parse_data(filename, column_format=None, **kwargs)``
Append one entry to ``self._banks`` for each data set in the file. The
default implementation reads a single bank with ``load_data`` and maps its
columns onto ``x``, ``y``, ``dx`` and ``dy``.

The ``PDFParser`` class in the ``diffpy.srfit.pdf.pdfparser`` module is a
``ProfileParser`` subclass for PDFgetX and PDFgetN data. PDFgetX and PDFgetN
write their header as plain ``name = value`` pairs, including ``stype``,
``qmin``, ``qmax`` and other PDF specific values, so ``PDFParser`` inherits
both ``_parse_metadata`` and ``_parse_data`` unchanged and only sets
``_format`` to identify the data as PDF data.

Here is a simple example demonstrating how to read metadata that is stored as
``name: value`` pairs rather than the default ``name = value``. ::

def _parse_metadata(self, filename):

meta = {}

for line in Path(filename).read_text().splitlines():
if not line.startswith("#"):
break
name, sep, value = line.lstrip("# ").partition(":")
if sep:
meta[name.strip()] = value.strip()

return meta

The parser can put any information into the returned dictionary; it is merged
into the ``_meta`` attribute. It is up to a ``ProfileGenerator`` that may use
the parsed data to define and retrieve usable metadata.

A format whose data block is not a plain matrix of columns overrides
``_parse_data`` instead. ::

def _parse_data(self, filename, column_format=None, **kwargs):

xvals = []
yvals = []
dxvals = None
dyvals = None

for line in datastring.splitlines():
for line in Path(filename).read_text().splitlines():

sline = line.split()
x, y = map(float, sline)
Expand All @@ -158,16 +194,15 @@ The ``self._banks.append`` line puts the data arrays into the ``_banks`` list.
This list is for collecting multiple data sets that may be present within a
single file. The ``dxvals`` and ``dyvals`` are the uncertainty values on the
``xvals`` and ``yvals``. In this simple example they are not present, and so
are set to None.

In general, the data string may contain metadata. The ``ProfileParser`` has a
dictionary attribute named ``_meta``. The parser can put any information into
this dictionary. It is up to a ``ProfileGenerator`` that may use the parsed
data to define and retrieve usable metadata.
are set to None. A ``Profile`` reads that as an unweighted data set.

If the data are not in a form that can be stored in a ``Profile`` then it is
the responsibility of the parser to convert this data to a usable form.

Parsers read from a file, not from a string. The older ``parseString`` method
has been removed, and ``parseFile`` is deprecated: it now delegates to
``parse_file`` and will be removed in version 4.0.0.


Extending Profiles
--------------------------
Expand Down
19 changes: 12 additions & 7 deletions news/profileparser_dep.rst
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
**Added:**

* Add ``parse_file`` method to ``ProfileParser`` to parse a file directly with ``load_data`` from ``diffpy.utils``.
* Add ``get_num_bank`` method to ``ProfileParser`` to replace ``getNumBank``.
* Add ``_parse_metadata`` and ``_parse_data`` hooks to ``ProfileParser``, which subclasses override to support a new data format. ``parse_file`` is a template method that calls them.
* Add ``get_num_banks`` method to ``ProfileParser`` to replace ``getNumBanks``.
* Add ``select_bank`` method to ``ProfileParser`` to replace ``selectBank``.
* Add ``get_format`` method to ``ProfileParser`` to replace ``getFormat``.
* Add ``get_data`` method to ``ProfileParser`` to replace ``getData``.
* Add ``get_meta_data`` method to ``ProfileParser`` to replace ``getMetaData``.
* Add ``get_metadata`` method to ``ProfileParser`` to replace ``getMetaData``.

**Changed:**

* <news item>
* Change ``PDFParser`` to inherit ``_parse_metadata`` and ``_parse_data`` unchanged from ``ProfileParser``, since PDFgetX and PDFgetN headers are now plain ``name = value`` pairs. ``PDFParser.parse_file`` returns the same data and metadata that ``PDFParser.parseFile`` did.
* Change ``ProfileParser.parse_file`` to accept the keyword arguments of ``load_data``, such as ``usecols``, ``delimiter`` and ``comments``. Use ``usecols`` to select columns from a file with more than four of them.
* Change ``PDFContribution.loadData`` to take a file name only.
* Change the headers of the ``si-q27r60-xray.gr`` and ``ni-q27r100-neutron.gr`` test files to the modern ``diffpy.pdfgetx`` and xPDFsuite configuration formats, replacing the 2008-era PDFgetX2/PDFgetN headers. The data values are unchanged.

**Deprecated:**

* Deprecate ``PDFParser``. Use ``ProfileParser`` instead.
* Deprecate ``getNumBank``, ``selectBank``, ``getFormat``, ``getData``, and ``getMetaData`` in ``ProfileParser``.
* Deprecate ``getNumBanks``, ``selectBank``, ``getFormat``, ``getData``, and ``getMetaData`` in ``ProfileParser``.
* Deprecate ``ProfileParser.parseFile``, which now delegates to ``parse_file``. Use ``parse_file`` instead.

**Removed:**

* <news item>
* Remove ``parseString`` from ``ProfileParser``, and ``PDFParser``. Parsers read from a file, so a format is now supported by overriding the ``_parse_metadata`` and ``_parse_data`` hooks.
* Remove support for passing a data string or an open file object to ``PDFContribution.loadData``. Pass a file name instead.

**Fixed:**

* <news item>
* Fix ``PDFParser.parse_file`` and ``PDFContribution.loadData`` discarding PDF metadata. They parsed PDFgetX and PDFgetN headers as generic ``name = value`` pairs, which dropped ``stype``, ``qmin``, ``qmax``, ``qdamp`` and ``qbroad``, so ``PDFGenerator`` silently fell back to its default scattering type and Q range.

**Security:**

Expand Down
Loading
Loading