Skip to content

Commit 2d791dd

Browse files
committed
feat: detect extrapolation for morphstretch
1 parent 2e5391d commit 2d791dd

File tree

7 files changed

+168
-22
lines changed

7 files changed

+168
-22
lines changed

news/stretch-extrapolation.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* No news added: Add warning for extrapolation in morphstretch and tests for extrapolations.
4+
5+
**Changed:**
6+
7+
* <news item>
8+
9+
**Deprecated:**
10+
11+
* <news item>
12+
13+
**Removed:**
14+
15+
* <news item>
16+
17+
**Fixed:**
18+
19+
* <news item>
20+
21+
**Security:**
22+
23+
* <news item>

src/diffpy/morph/morphapp.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,9 +585,11 @@ def single_morph(
585585
refpars.append("scale")
586586
# Stretch
587587
# Only enable stretch if squeeze is lower than degree 1
588+
stretch_morph = None
588589
if opts.stretch is not None and squeeze_poly_deg < 1:
590+
stretch_morph = morphs.MorphStretch()
591+
chain.append(stretch_morph)
589592
stretch_in = opts.stretch
590-
chain.append(morphs.MorphStretch())
591593
config["stretch"] = stretch_in
592594
refpars.append("stretch")
593595
# Smear
@@ -703,6 +705,7 @@ def single_morph(
703705
# THROW ANY WARNINGS HERE
704706
io.handle_warnings(squeeze_morph)
705707
io.handle_warnings(shift_morph)
708+
io.handle_warnings(stretch_morph)
706709

707710
# Get Rw for the morph range
708711
rw = tools.getRw(chain)

src/diffpy/morph/morphs/morphstretch.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def morph(self, x_morph, y_morph, x_target, y_target):
4848

4949
r = self.x_morph_in / (1.0 + self.stretch)
5050
self.y_morph_out = numpy.interp(r, self.x_morph_in, self.y_morph_in)
51+
self.set_extrapolation_info(self.x_morph_in, r)
5152
return self.xyallout
5253

5354

tests/helper.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
def create_morph_data_file(
2+
data_dir_path, x_morph, y_morph, x_target, y_target
3+
):
4+
morph_file = data_dir_path / "morph_data"
5+
morph_data_text = [
6+
str(x_morph[i]) + " " + str(y_morph[i]) for i in range(len(x_morph))
7+
]
8+
morph_data_text = "\n".join(morph_data_text)
9+
morph_file.write_text(morph_data_text)
10+
target_file = data_dir_path / "target_data"
11+
target_data_text = [
12+
str(x_target[i]) + " " + str(y_target[i]) for i in range(len(x_target))
13+
]
14+
target_data_text = "\n".join(target_data_text)
15+
target_file.write_text(target_data_text)
16+
return morph_file, target_file

tests/test_morphshift.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
import numpy
77
import pytest
88

9+
import diffpy.morph.morphpy as morphpy
10+
from diffpy.morph.morphapp import create_option_parser, single_morph
911
from diffpy.morph.morphs.morphshift import MorphShift
12+
from tests.helper import create_morph_data_file
1013

1114
# useful variables
1215
thisfile = locals().get("__file__", "file.py")
@@ -44,3 +47,61 @@ def test_morph(self, setup):
4447
assert numpy.allclose(self.x_target, x_target)
4548
assert numpy.allclose(self.y_target, y_target)
4649
return
50+
51+
52+
@pytest.mark.parametrize(
53+
"hshift, wmsg_gen",
54+
[
55+
# extrapolate below
56+
(
57+
0.01,
58+
lambda x: (
59+
"Warning: points with grid value below "
60+
f"{x[0]} are extrapolated."
61+
),
62+
),
63+
# extrapolate above
64+
(
65+
-0.01,
66+
lambda x: (
67+
"Warning: points with grid value above "
68+
f"{x[1]} are extrapolated."
69+
),
70+
),
71+
],
72+
)
73+
def test_morphshift_extrapolate(user_filesystem, capsys, hshift, wmsg_gen):
74+
x_morph = numpy.linspace(0, 10, 101)
75+
y_morph = numpy.sin(x_morph)
76+
x_target = x_morph.copy()
77+
y_target = y_morph.copy()
78+
with pytest.warns() as w:
79+
morphpy.morph_arrays(
80+
numpy.array([x_morph, y_morph]).T,
81+
numpy.array([x_target, y_target]).T,
82+
hshift=hshift,
83+
apply=True,
84+
)
85+
assert len(w) == 1
86+
assert w[0].category is UserWarning
87+
actual_wmsg = str(w[0].message)
88+
expected_wmsg = wmsg_gen([min(x_morph), max(x_morph)])
89+
assert actual_wmsg == expected_wmsg
90+
91+
# CLI test
92+
morph_file, target_file = create_morph_data_file(
93+
user_filesystem / "cwd_dir", x_morph, y_morph, x_target, y_target
94+
)
95+
96+
parser = create_option_parser()
97+
(opts, pargs) = parser.parse_args(
98+
[
99+
f"--hshift={hshift}",
100+
f"{morph_file.as_posix()}",
101+
f"{target_file.as_posix()}",
102+
"--apply",
103+
"-n",
104+
]
105+
)
106+
with pytest.warns(UserWarning, match=expected_wmsg):
107+
single_morph(parser, opts, pargs, stdout_flag=False)

tests/test_morphsqueeze.py

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import diffpy.morph.morphpy as morphpy
66
from diffpy.morph.morphapp import create_option_parser, single_morph
77
from diffpy.morph.morphs.morphsqueeze import MorphSqueeze
8+
from tests.helper import create_morph_data_file
89

910
squeeze_coeffs_dic = [
1011
# The order of coefficients is {a0, a1, a2, ..., an}
@@ -128,9 +129,7 @@ def test_morphsqueeze(x_morph, x_target, squeeze_coeffs):
128129
),
129130
],
130131
)
131-
def test_morphsqueeze_extrapolate(
132-
user_filesystem, capsys, squeeze_coeffs, wmsg_gen
133-
):
132+
def test_morphsqueeze_extrapolate(user_filesystem, squeeze_coeffs, wmsg_gen):
134133
x_morph = np.linspace(0, 10, 101)
135134
y_morph = np.sin(x_morph)
136135
x_target = x_morph.copy()
@@ -171,21 +170,3 @@ def test_morphsqueeze_extrapolate(
171170
)
172171
with pytest.warns(UserWarning, match=expected_wmsg):
173172
single_morph(parser, opts, pargs, stdout_flag=False)
174-
175-
176-
def create_morph_data_file(
177-
data_dir_path, x_morph, y_morph, x_target, y_target
178-
):
179-
morph_file = data_dir_path / "morph_data"
180-
morph_data_text = [
181-
str(x_morph[i]) + " " + str(y_morph[i]) for i in range(len(x_morph))
182-
]
183-
morph_data_text = "\n".join(morph_data_text)
184-
morph_file.write_text(morph_data_text)
185-
target_file = data_dir_path / "target_data"
186-
target_data_text = [
187-
str(x_target[i]) + " " + str(y_target[i]) for i in range(len(x_target))
188-
]
189-
target_data_text = "\n".join(target_data_text)
190-
target_file.write_text(target_data_text)
191-
return morph_file, target_file

tests/test_morphstretch.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
import numpy
77
import pytest
88

9+
import diffpy.morph.morphpy as morphpy
10+
from diffpy.morph.morphapp import create_option_parser, single_morph
911
from diffpy.morph.morphs.morphstretch import MorphStretch
12+
from tests.helper import create_morph_data_file
1013

1114
# useful variables
1215
thisfile = locals().get("__file__", "file.py")
@@ -70,3 +73,61 @@ def heaviside(x, lb, ub):
7073
y[x < lb] = 0.0
7174
y[x > ub] = 0.0
7275
return y
76+
77+
78+
@pytest.mark.parametrize(
79+
"stretch, wmsg_gen",
80+
[
81+
# extrapolate below
82+
(
83+
0.01,
84+
lambda x: (
85+
"Warning: points with grid value below "
86+
f"{x[0]} are extrapolated."
87+
),
88+
),
89+
# extrapolate above
90+
(
91+
-0.01,
92+
lambda x: (
93+
"Warning: points with grid value above "
94+
f"{x[1]} are extrapolated."
95+
),
96+
),
97+
],
98+
)
99+
def test_morphshift_extrapolate(user_filesystem, stretch, wmsg_gen):
100+
x_morph = numpy.linspace(1, 10, 101)
101+
y_morph = numpy.sin(x_morph)
102+
x_target = x_morph.copy()
103+
y_target = y_morph.copy()
104+
with pytest.warns() as w:
105+
morphpy.morph_arrays(
106+
numpy.array([x_morph, y_morph]).T,
107+
numpy.array([x_target, y_target]).T,
108+
stretch=stretch,
109+
apply=True,
110+
)
111+
assert len(w) == 1
112+
assert w[0].category is UserWarning
113+
actual_wmsg = str(w[0].message)
114+
expected_wmsg = wmsg_gen([min(x_morph), max(x_morph)])
115+
assert actual_wmsg == expected_wmsg
116+
117+
# CLI test
118+
morph_file, target_file = create_morph_data_file(
119+
user_filesystem / "cwd_dir", x_morph, y_morph, x_target, y_target
120+
)
121+
122+
parser = create_option_parser()
123+
(opts, pargs) = parser.parse_args(
124+
[
125+
f"--stretch={stretch}",
126+
f"{morph_file.as_posix()}",
127+
f"{target_file.as_posix()}",
128+
"--apply",
129+
"-n",
130+
]
131+
)
132+
with pytest.warns(UserWarning, match=expected_wmsg):
133+
single_morph(parser, opts, pargs, stdout_flag=False)

0 commit comments

Comments
 (0)