diff --git a/news/stretch-extrapolation.rst b/news/stretch-extrapolation.rst new file mode 100644 index 0000000..1992487 --- /dev/null +++ b/news/stretch-extrapolation.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: Add warning for extrapolation in morphstretch and tests for extrapolations. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/morph/morphapp.py b/src/diffpy/morph/morphapp.py index 27799d0..4397698 100755 --- a/src/diffpy/morph/morphapp.py +++ b/src/diffpy/morph/morphapp.py @@ -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 @@ -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( @@ -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) diff --git a/src/diffpy/morph/morphs/morphstretch.py b/src/diffpy/morph/morphs/morphstretch.py index 78c25c8..21f50a6 100644 --- a/src/diffpy/morph/morphs/morphstretch.py +++ b/src/diffpy/morph/morphs/morphstretch.py @@ -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 diff --git a/tests/helper.py b/tests/helper.py new file mode 100644 index 0000000..e21bdce --- /dev/null +++ b/tests/helper.py @@ -0,0 +1,16 @@ +def create_morph_data_file( + data_dir_path, x_morph, y_morph, x_target, y_target +): + morph_file = data_dir_path / "morph_data.txt" + 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.txt" + 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 diff --git a/tests/test_morphshift.py b/tests/test_morphshift.py index c01f2c6..1ae2e0f 100644 --- a/tests/test_morphshift.py +++ b/tests/test_morphshift.py @@ -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") @@ -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) diff --git a/tests/test_morphsqueeze.py b/tests/test_morphsqueeze.py index 7913023..07b9937 100644 --- a/tests/test_morphsqueeze.py +++ b/tests/test_morphsqueeze.py @@ -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} @@ -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() @@ -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 diff --git a/tests/test_morphstretch.py b/tests/test_morphstretch.py index 8e97bb1..311c92e 100644 --- a/tests/test_morphstretch.py +++ b/tests/test_morphstretch.py @@ -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") @@ -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)