Skip to content

Rename PDF-specific smear option to --smear-pdf #220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 24, 2025
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
23 changes: 23 additions & 0 deletions news/smear-update.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* New --smear option applies the smear morph directly to the function (without transforming to RDF).

**Changed:**

* Former --smear option renamed to --smear-pdf (converts PDF to RDF before applying the smear morph).

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
36 changes: 28 additions & 8 deletions src/diffpy/morph/morphapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,31 @@ def custom_error(self, msg):
type="float",
metavar="SMEAR",
help=(
"Smear peaks with a Gaussian of width SMEAR. "
"This convolves the function with a Gaussian of width SMEAR."
"Smear the peaks with a Gaussian of width SMEAR. "
"This is done by convolving the function with a "
"Gaussian with standard deviation SMEAR. "
"If both --smear and --smear-pdf are enabled, "
"only --smear-pdf will be applied."
),
)
group.add_option(
"--smear-pdf",
type="float",
metavar="SMEAR",
help=(
"Convert PDF to RDF. "
"Then, smear peaks with a Gaussian of width SMEAR. "
"Convert back to PDF. "
"If both --smear and --smear-pdf are enabled, "
"only --smear-pdf will be applied."
),
)
group.add_option(
"--slope",
type="float",
dest="baselineslope",
help="""Slope of the baseline. This is used when applying the smear
factor. It will be estimated if not provided.""",
help="""Slope of the baseline. This is used with the option --smear-pdf
to convert from the PDF to RDF. It will be estimated if not provided.""",
)
group.add_option(
"--hshift",
Expand Down Expand Up @@ -520,8 +535,8 @@ def single_morph(parser, opts, pargs, stdout_flag=True):
config["vshift"] = vshift_in
refpars.append("vshift")
# Smear
if opts.smear is not None:
smear_in = opts.smear
if opts.smear_pdf is not None:
smear_in = opts.smear_pdf
chain.append(helpers.TransformXtalPDFtoRDF())
chain.append(morphs.MorphSmear())
chain.append(helpers.TransformXtalRDFtoPDF())
Expand All @@ -532,6 +547,11 @@ def single_morph(parser, opts, pargs, stdout_flag=True):
if opts.baselineslope is None:
config["baselineslope"] = -0.5
refpars.append("baselineslope")
elif opts.smear is not None:
smear_in = opts.smear
chain.append(morphs.MorphSmear())
refpars.append("smear")
config["smear"] = smear_in
# Size
radii = [opts.radius, opts.pradius]
nrad = 2 - radii.count(None)
Expand Down Expand Up @@ -806,7 +826,7 @@ def multiple_targets(parser, opts, pargs, stdout_flag=True):
morph_inputs = {
"scale": opts.scale,
"stretch": opts.stretch,
"smear": opts.smear,
"smear": opts.smear_pdf,
}
morph_inputs.update({"hshift": opts.hshift, "vshift": opts.vshift})

Expand Down Expand Up @@ -989,7 +1009,7 @@ def multiple_morphs(parser, opts, pargs, stdout_flag=True):
morph_inputs = {
"scale": opts.scale,
"stretch": opts.stretch,
"smear": opts.smear,
"smear": opts.smear_pdf,
}
morph_inputs.update({"hshift": opts.hshift, "vshift": opts.vshift})

Expand Down
63 changes: 63 additions & 0 deletions tests/test_morphapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from pathlib import Path

import numpy as np
import pytest

from diffpy.morph.morphapp import (
Expand Down Expand Up @@ -255,6 +256,68 @@ def test_morphsequence(self, setup_morphsequence):
)
assert s_sequence_results == sequence_results

def test_morphsmear(self, setup_parser, tmp_path):
def gaussian(x, mu, sigma):
return np.exp(-((x - mu) ** 2) / (2 * sigma**2)) / (
sigma * np.sqrt(2 * np.pi)
)

# Generate the test files
x_grid = np.linspace(1, 101, 1001)
# Gaussian with STD 3 (morph)
g2 = gaussian(x_grid, 51, 3)
mf = tmp_path / "morph.txt"
with open(mf, "w") as f:
np.savetxt(f, np.array([x_grid, g2]).T)
# Gaussian with STD 5 (target)
g3 = gaussian(x_grid, 51, 5)
tf = tmp_path / "target.txt"
with open(tf, "w") as f:
np.savetxt(f, np.array([x_grid, g3]).T)
# Gaussian with STD 3 and baseline slope -0.5 (PDF morph)
g2_bl = gaussian(x_grid, 51, 3) / x_grid - 0.5 * x_grid
pmf = tmp_path / "pdf_morph.txt"
with open(pmf, "w") as f:
np.savetxt(f, np.array([x_grid, g2_bl]).T)
# Gaussian with STD 5 with baseline slope -0.5 (PDF target)
g3_bl = gaussian(x_grid, 51, 5) / x_grid - 0.5 * x_grid
ptf = tmp_path / "pdf_target.txt"
with open(ptf, "w") as f:
np.savetxt(f, np.array([x_grid, g3_bl]).T)

# No PDF smear (should not activate baseline slope)
(opts, _) = self.parser.parse_args(
[
"--smear",
"1",
"-n",
]
)
pargs = [mf, tf]
smear_results = single_morph(
self.parser, opts, pargs, stdout_flag=False
)
# Variances add, and 3^2+4^2=5^2
assert pytest.approx(abs(smear_results["smear"])) == 4.0
assert pytest.approx(smear_results["Rw"]) == 0.0

# PDF-specific smear (should activate baseline slope)
(opts, _) = self.parser.parse_args(
[
"--smear",
"100",
"--smear-pdf",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code block also tests the default behavior if a user inputs both morphs. The pdf smear takes precedence.

"1",
"-n",
]
)
pargs = [pmf, ptf]
pdf_smear_results = single_morph(
self.parser, opts, pargs, stdout_flag=False
)
assert pytest.approx(abs(pdf_smear_results["smear"])) == 4.0
assert pytest.approx(pdf_smear_results["Rw"]) == 0.0


if __name__ == "__main__":
TestApp()
Loading