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
23 changes: 23 additions & 0 deletions news/extrapolate-warning.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* No news added: Add warning for extrapolation in morphsqueeze.py.

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
32 changes: 32 additions & 0 deletions src/diffpy/morph/morphs/morphsqueeze.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
"""Class MorphSqueeze -- Apply a polynomial to squeeze the morph
function."""

import warnings

import numpy as np
from numpy.polynomial import Polynomial
from scipy.interpolate import CubicSpline

from diffpy.morph.morphs.morph import LABEL_GR, LABEL_RA, Morph


def custom_formatwarning(msg, *args, **kwargs):
return f"{msg}\n"


warnings.formatwarning = custom_formatwarning


class MorphSqueeze(Morph):
"""Squeeze the morph function.

Expand Down Expand Up @@ -85,4 +94,27 @@ def morph(self, x_morph, y_morph, x_target, y_target):
high_extrap = np.where(self.x_morph_in > x_squeezed[-1])[0]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do we want to remove these few lines of code? I can't find other references to the variables defined here.

Copy link
Collaborator

@Sparks29032 Sparks29032 Aug 26, 2025

Choose a reason for hiding this comment

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

These were variables we made before to store what you have now called begin_end_squeeze. You can delete it if you prefer using begin_end_squeeze.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I find these variables provide a handy interface to test the extrapolated effect in test_morphsqueeze, so I think we should keep them.

self.extrap_index_low = low_extrap[-1] if low_extrap.size else None
self.extrap_index_high = high_extrap[0] if high_extrap.size else None
below_extrap = min(x_morph) < min(x_squeezed)
above_extrap = max(x_morph) > max(x_squeezed)
if below_extrap or above_extrap:
if not above_extrap:
wmsg = (
"Warning: points with grid value below "
f"{min(x_squeezed)} will be extrapolated."
)
elif not below_extrap:
wmsg = (
"Warning: points with grid value above "
f"{max(x_squeezed)} will be extrapolated."
)
else:
wmsg = (
"Warning: points with grid value below "
f"{min(x_squeezed)} and above {max(x_squeezed)} will be "
"extrapolated."
)
warnings.warn(
wmsg,
UserWarning,
)
return self.xyallout
82 changes: 82 additions & 0 deletions tests/test_morphsqueeze.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import subprocess

import numpy as np
import pytest
from numpy.polynomial import Polynomial
Expand Down Expand Up @@ -85,3 +87,83 @@ def test_morphsqueeze(x_morph, x_target, squeeze_coeffs):
assert np.allclose(x_morph_actual, x_morph_expected)
assert np.allclose(x_target_actual, x_target)
assert np.allclose(y_target_actual, y_target)


@pytest.mark.parametrize(
"squeeze_coeffs, wmsg_gen",
[
# extrapolate below
(
{"a0": 0.01},
lambda x: (
"Warning: points with grid value below "
f"{x[0]} will be extrapolated."
),
),
# extrapolate above
(
{"a0": -0.01},
lambda x: (
"Warning: points with grid value above "
f"{x[1]} will be extrapolated."
),
),
# extrapolate below and above
(
{"a0": 0.01, "a1": -0.002},
lambda x: (
"Warning: points with grid value below "
f"{x[0]} and above {x[1]} will be "
"extrapolated."
),
),
],
)
def test_morphsqueeze_extrapolate(user_filesystem, squeeze_coeffs, wmsg_gen):
x_morph = np.linspace(0, 10, 101)
y_morph = np.sin(x_morph)
x_target = x_morph
y_target = y_morph
morph = MorphSqueeze()
morph.squeeze = squeeze_coeffs
coeffs = [squeeze_coeffs[f"a{i}"] for i in range(len(squeeze_coeffs))]
squeeze_polynomial = Polynomial(coeffs)
x_squeezed = x_morph + squeeze_polynomial(x_morph)
with pytest.warns() as w:
x_morph_actual, y_morph_actual, x_target_actual, y_target_actual = (
morph(x_morph, y_morph, x_target, y_target)
)
assert len(w) == 1
assert w[0].category is UserWarning
actual_wmsg = str(w[0].message)
expected_wmsg = wmsg_gen([min(x_squeezed), max(x_squeezed)])
assert actual_wmsg == expected_wmsg

# CLI test
morph_file, target_file = create_morph_data_file(
user_filesystem / "cwd_dir", x_morph, y_morph, x_target, y_target
)
run_cmd = ["diffpy.morph"]
run_cmd.extend(["--squeeze=" + ",".join(map(str, coeffs))])
run_cmd.extend([str(morph_file), str(target_file)])
run_cmd.append("-n")
result = subprocess.run(run_cmd, capture_output=True, text=True)
assert expected_wmsg in result.stderr


def create_morph_data_file(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do we want to create a helper.py module in \tests? If we want to add more CLI tests, we might need a handy function to create temporary *.cgr files. We can write this function in helper.py, and other tests can import it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That would be helpful! (see #233)

data_dir_path, x_morph, y_morph, x_target, y_target
):
morph_file = data_dir_path / "morph_data"
morph_data_text = [
str(x_morph[i]) + " " + str(y_morph[i]) for i in range(len(x_morph))
]
morph_data_text = "\n".join(morph_data_text)
morph_file.write_text(morph_data_text)
target_file = data_dir_path / "target_data"
target_data_text = [
str(x_target[i]) + " " + str(y_target[i]) for i in range(len(x_target))
]
target_data_text = "\n".join(target_data_text)
target_file.write_text(target_data_text)
return morph_file, target_file
Loading