Skip to content

Commit 4914488

Browse files
authored
Merge pull request #182 from Luiskitsu/squeeze_morph
Squeeze morph: Adding UCs tests
2 parents d1c2695 + fcee820 commit 4914488

File tree

3 files changed

+183
-0
lines changed

3 files changed

+183
-0
lines changed

news/morphsqueeze.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* Polynomial squeeze of x-axis of morphed data
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>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import numpy as np
2+
from numpy.polynomial import Polynomial
3+
from scipy.interpolate import CubicSpline
4+
5+
from diffpy.morph.morphs.morph import LABEL_GR, LABEL_RA, Morph
6+
7+
8+
class MorphSqueeze(Morph):
9+
"""Apply a polynomial to squeeze the morph function. The morphed
10+
data is returned on the same grid as the unmorphed data."""
11+
12+
# Define input output types
13+
summary = "Squeeze morph by polynomial shift"
14+
xinlabel = LABEL_RA
15+
yinlabel = LABEL_GR
16+
xoutlabel = LABEL_RA
17+
youtlabel = LABEL_GR
18+
parnames = ["squeeze"]
19+
# extrap_index_low: last index before interpolation region
20+
# extrap_index_high: first index after interpolation region
21+
extrap_index_low = None
22+
extrap_index_high = None
23+
24+
def morph(self, x_morph, y_morph, x_target, y_target):
25+
"""Squeeze the morph function.
26+
27+
This applies a polynomial to squeeze the morph non-linearly.
28+
29+
Configuration Variables
30+
-----------------------
31+
squeeze : list
32+
The polynomial coefficients [a0, a1, ..., an] for the squeeze
33+
function where the polynomial would be of the form
34+
a0 + a1*x + a2*x^2 and so on. The order of the polynomial is
35+
determined by the length of the list.
36+
37+
Returns
38+
-------
39+
A tuple (x_morph_out, y_morph_out, x_target_out, y_target_out)
40+
where the target values remain the same and the morph data is
41+
shifted according to the squeeze. The morphed data is returned on
42+
the same grid as the unmorphed data.
43+
44+
Example
45+
-------
46+
Import the squeeze morph function:
47+
>>> from diffpy.morph.morphs.morphsqueeze import MorphSqueeze
48+
Provide initial guess for squeezing coefficients:
49+
>>> squeeze_coeff = [0.1, -0.01, 0.005]
50+
Run the squeeze morph given input morph array (x_morph, y_morph)
51+
and target array (x_target, y_target):
52+
>>> morph = MorphSqueeze()
53+
>>> morph.squeeze = squeeze_coeff
54+
>>> x_morph_out, y_morph_out, x_target_out, y_target_out = morph(
55+
... x_morph, y_morph, x_target, y_target)
56+
To access parameters from the morph instance:
57+
>>> x_morph_in = morph.x_morph_in
58+
>>> y_morph_in = morph.y_morph_in
59+
>>> x_target_in = morph.x_target_in
60+
>>> y_target_in = morph.y_target_in
61+
>>> squeeze_coeff_out = morph.squeeze
62+
"""
63+
Morph.morph(self, x_morph, y_morph, x_target, y_target)
64+
65+
squeeze_polynomial = Polynomial(self.squeeze)
66+
x_squeezed = self.x_morph_in + squeeze_polynomial(self.x_morph_in)
67+
self.y_morph_out = CubicSpline(x_squeezed, self.y_morph_in)(
68+
self.x_morph_in
69+
)
70+
low_extrap = np.where(self.x_morph_in < x_squeezed[0])[0]
71+
high_extrap = np.where(self.x_morph_in > x_squeezed[-1])[0]
72+
self.extrap_index_low = low_extrap[-1] if low_extrap.size else None
73+
self.extrap_index_high = high_extrap[0] if high_extrap.size else None
74+
return self.xyallout

tests/test_morphsqueeze.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import numpy as np
2+
import pytest
3+
from numpy.polynomial import Polynomial
4+
5+
from diffpy.morph.morphs.morphsqueeze import MorphSqueeze
6+
7+
squeeze_coeffs_list = [
8+
# The order of coefficients is [a0, a1, a2, ..., an]
9+
# Negative cubic squeeze coefficients
10+
[-0.01, -0.0005, -0.0005, -1e-6],
11+
# Positive cubic squeeze coefficients
12+
[0.2, 0.01, 0.001, 0.0001],
13+
# Positive and negative cubic squeeze coefficients
14+
[0.2, -0.01, 0.002, -0.0001],
15+
# Quadratic squeeze coefficients
16+
[-0.2, 0.005, -0.0004],
17+
# Linear squeeze coefficients
18+
[0.1, 0.3],
19+
# 4th order squeeze coefficients
20+
[0.2, -0.01, 0.001, -0.001, 0.0001],
21+
# Zeros and non-zeros, the full polynomial is applied
22+
[0, 0.03, 0, -0.0001],
23+
# Testing zeros, expect no squeezing
24+
[0, 0, 0, 0, 0, 0],
25+
]
26+
morph_target_grids = [
27+
# UCs from issue 181: https://github.com/diffpy/diffpy.morph/issues/181
28+
# UC2: Same range and same grid density
29+
(np.linspace(0, 10, 101), np.linspace(0, 10, 101)),
30+
# UC4: Target range wider than morph, same grid density
31+
(np.linspace(0, 10, 101), np.linspace(-2, 20, 221)),
32+
# UC6: Target range wider than morph, target grid density finer than morph
33+
(np.linspace(0, 10, 101), np.linspace(-2, 20, 421)),
34+
# UC8: Target range wider than morph, morph grid density finer than target
35+
(np.linspace(0, 10, 401), np.linspace(-2, 20, 200)),
36+
# UC10: Morph range starts and ends earlier than target, same grid density
37+
(np.linspace(-2, 10, 121), np.linspace(0, 20, 201)),
38+
# UC12: Morph range wider than target, same grid density
39+
(np.linspace(-2, 20, 201), np.linspace(0, 10, 101)),
40+
]
41+
42+
43+
@pytest.mark.parametrize("x_morph, x_target", morph_target_grids)
44+
@pytest.mark.parametrize("squeeze_coeffs", squeeze_coeffs_list)
45+
def test_morphsqueeze(x_morph, x_target, squeeze_coeffs):
46+
y_target = np.sin(x_target)
47+
squeeze_polynomial = Polynomial(squeeze_coeffs)
48+
x_squeezed = x_morph + squeeze_polynomial(x_morph)
49+
y_morph = np.sin(x_squeezed)
50+
low_extrap = np.where(x_morph < x_squeezed[0])[0]
51+
high_extrap = np.where(x_morph > x_squeezed[-1])[0]
52+
extrap_index_low_expected = low_extrap[-1] if low_extrap.size else None
53+
extrap_index_high_expected = high_extrap[0] if high_extrap.size else None
54+
x_morph_expected = x_morph
55+
y_morph_expected = np.sin(x_morph)
56+
morph = MorphSqueeze()
57+
morph.squeeze = squeeze_coeffs
58+
x_morph_actual, y_morph_actual, x_target_actual, y_target_actual = morph(
59+
x_morph, y_morph, x_target, y_target
60+
)
61+
extrap_index_low = morph.extrap_index_low
62+
extrap_index_high = morph.extrap_index_high
63+
if extrap_index_low is None:
64+
extrap_index_low = 0
65+
elif extrap_index_high is None:
66+
extrap_index_high = -1
67+
assert np.allclose(
68+
y_morph_actual[extrap_index_low + 1 : extrap_index_high],
69+
y_morph_expected[extrap_index_low + 1 : extrap_index_high],
70+
atol=1e-6,
71+
)
72+
assert np.allclose(
73+
y_morph_actual[:extrap_index_low],
74+
y_morph_expected[:extrap_index_low],
75+
atol=1e-3,
76+
)
77+
assert np.allclose(
78+
y_morph_actual[extrap_index_high:],
79+
y_morph_expected[extrap_index_high:],
80+
atol=1e-3,
81+
)
82+
assert morph.extrap_index_low == extrap_index_low_expected
83+
assert morph.extrap_index_high == extrap_index_high_expected
84+
assert np.allclose(x_morph_actual, x_morph_expected)
85+
assert np.allclose(x_target_actual, x_target)
86+
assert np.allclose(y_target_actual, y_target)

0 commit comments

Comments
 (0)