Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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/stretch-extrapolation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* No news added: Add warning for extrapolation in morphstretch and tests for extrapolations.

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
7 changes: 6 additions & 1 deletion src/diffpy/morph/morphapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,9 +585,11 @@ def single_morph(
refpars.append("scale")
# Stretch
# Only enable stretch if squeeze is lower than degree 1
stretch_morph = None
if opts.stretch is not None and squeeze_poly_deg < 1:
stretch_morph = morphs.MorphStretch()
chain.append(stretch_morph)
stretch_in = opts.stretch
chain.append(morphs.MorphStretch())
config["stretch"] = stretch_in
refpars.append("stretch")
# Smear
Expand Down Expand Up @@ -665,6 +667,8 @@ def single_morph(
# Now remove non-refinable parameters
if opts.exclude is not None:
refpars = list(set(refpars) - set(opts.exclude))
if "stretch" in opts.exclude:
stretch_morph = None

# Refine or execute the morph
refiner = refine.Refiner(
Expand Down Expand Up @@ -703,6 +707,7 @@ def single_morph(
# THROW ANY WARNINGS HERE
io.handle_warnings(squeeze_morph)
io.handle_warnings(shift_morph)
io.handle_warnings(stretch_morph)

# Get Rw for the morph range
rw = tools.getRw(chain)
Expand Down
1 change: 1 addition & 0 deletions src/diffpy/morph/morphs/morphstretch.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def morph(self, x_morph, y_morph, x_target, y_target):

r = self.x_morph_in / (1.0 + self.stretch)
self.y_morph_out = numpy.interp(r, self.x_morph_in, self.y_morph_in)
self.set_extrapolation_info(self.x_morph_in, r)
return self.xyallout


Expand Down
16 changes: 16 additions & 0 deletions tests/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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.

Create helper.py to contain reused functions.

data_dir_path, x_morph, y_morph, x_target, y_target
):
morph_file = data_dir_path / "morph_data"
Copy link
Contributor

Choose a reason for hiding this comment

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

do these files end up having an extension? If they are text files maybe make them .txt? But I am not 100% sure what is going on so please advise if I am off base here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These files do not end with an extension. This helper function create these files in tmp_dir to facilitate CLI testing when test_func has the corresponding data. I will add the extension ".txt" to make the implementation more explicit.

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"
Copy link
Contributor

Choose a reason for hiding this comment

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

same here

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
61 changes: 61 additions & 0 deletions tests/test_morphshift.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import numpy
import pytest

import diffpy.morph.morphpy as morphpy
from diffpy.morph.morphapp import create_option_parser, single_morph
from diffpy.morph.morphs.morphshift import MorphShift
from tests.helper import create_morph_data_file

# useful variables
thisfile = locals().get("__file__", "file.py")
Expand Down Expand Up @@ -44,3 +47,61 @@ def test_morph(self, setup):
assert numpy.allclose(self.x_target, x_target)
assert numpy.allclose(self.y_target, y_target)
return


@pytest.mark.parametrize(
"hshift, wmsg_gen",
[
# extrapolate below
(
0.01,
lambda x: (
"Warning: points with grid value below "
f"{x[0]} are extrapolated."
),
),
# extrapolate above
(
-0.01,
lambda x: (
"Warning: points with grid value above "
f"{x[1]} are extrapolated."
),
),
],
)
def test_morphshift_extrapolate(user_filesystem, capsys, hshift, wmsg_gen):
x_morph = numpy.linspace(0, 10, 101)
y_morph = numpy.sin(x_morph)
x_target = x_morph.copy()
y_target = y_morph.copy()
with pytest.warns() as w:
morphpy.morph_arrays(
numpy.array([x_morph, y_morph]).T,
numpy.array([x_target, y_target]).T,
hshift=hshift,
apply=True,
)
assert len(w) == 1
assert w[0].category is UserWarning
actual_wmsg = str(w[0].message)
expected_wmsg = wmsg_gen([min(x_morph), max(x_morph)])
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
)

parser = create_option_parser()
(opts, pargs) = parser.parse_args(
[
f"--hshift={hshift}",
f"{morph_file.as_posix()}",
f"{target_file.as_posix()}",
"--apply",
"-n",
]
)
with pytest.warns(UserWarning, match=expected_wmsg):
single_morph(parser, opts, pargs, stdout_flag=False)
23 changes: 2 additions & 21 deletions tests/test_morphsqueeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import diffpy.morph.morphpy as morphpy
from diffpy.morph.morphapp import create_option_parser, single_morph
from diffpy.morph.morphs.morphsqueeze import MorphSqueeze
from tests.helper import create_morph_data_file

squeeze_coeffs_dic = [
# The order of coefficients is {a0, a1, a2, ..., an}
Expand Down Expand Up @@ -128,9 +129,7 @@ def test_morphsqueeze(x_morph, x_target, squeeze_coeffs):
),
],
)
def test_morphsqueeze_extrapolate(
user_filesystem, capsys, squeeze_coeffs, wmsg_gen
):
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.copy()
Expand Down Expand Up @@ -171,21 +170,3 @@ def test_morphsqueeze_extrapolate(
)
with pytest.warns(UserWarning, match=expected_wmsg):
single_morph(parser, opts, pargs, stdout_flag=False)


def create_morph_data_file(
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
61 changes: 61 additions & 0 deletions tests/test_morphstretch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import numpy
import pytest

import diffpy.morph.morphpy as morphpy
from diffpy.morph.morphapp import create_option_parser, single_morph
from diffpy.morph.morphs.morphstretch import MorphStretch
from tests.helper import create_morph_data_file

# useful variables
thisfile = locals().get("__file__", "file.py")
Expand Down Expand Up @@ -70,3 +73,61 @@ def heaviside(x, lb, ub):
y[x < lb] = 0.0
y[x > ub] = 0.0
return y


@pytest.mark.parametrize(
"stretch, wmsg_gen",
[
# extrapolate below
(
0.01,
lambda x: (
"Warning: points with grid value below "
f"{x[0]} are extrapolated."
),
),
# extrapolate above
(
-0.01,
lambda x: (
"Warning: points with grid value above "
f"{x[1]} are extrapolated."
),
),
],
)
def test_morphshift_extrapolate(user_filesystem, stretch, wmsg_gen):
x_morph = numpy.linspace(1, 10, 101)
y_morph = numpy.sin(x_morph)
x_target = x_morph.copy()
y_target = y_morph.copy()
with pytest.warns() as w:
morphpy.morph_arrays(
numpy.array([x_morph, y_morph]).T,
numpy.array([x_target, y_target]).T,
stretch=stretch,
apply=True,
)
assert len(w) == 1
assert w[0].category is UserWarning
actual_wmsg = str(w[0].message)
expected_wmsg = wmsg_gen([min(x_morph), max(x_morph)])
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
)

parser = create_option_parser()
(opts, pargs) = parser.parse_args(
[
f"--stretch={stretch}",
f"{morph_file.as_posix()}",
f"{target_file.as_posix()}",
"--apply",
"-n",
]
)
with pytest.warns(UserWarning, match=expected_wmsg):
single_morph(parser, opts, pargs, stdout_flag=False)
Loading