Skip to content

Commit 651fad5

Browse files
Add squeeze morph to CLI (#216)
* Add squeeze morph to CLI * [pre-commit.ci] auto fixes from pre-commit hooks * Add news and documentation --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent ed2c088 commit 651fad5

File tree

7 files changed

+794
-18
lines changed

7 files changed

+794
-18
lines changed

doc/source/squeeze.rst

Lines changed: 404 additions & 0 deletions
Large diffs are not rendered by default.

news/squeeze_cli.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
**Added:**
2+
3+
* Squeeze morph now added to CLI.
4+
5+
**Changed:**
6+
7+
* Stretch disabled when squeeze is above polynomial order 0.
8+
* Horizontal shift morph disabled when squeeze is enabled.
9+
10+
**Deprecated:**
11+
12+
* <news item>
13+
14+
**Removed:**
15+
16+
* <news item>
17+
18+
**Fixed:**
19+
20+
* <news item>
21+
22+
**Security:**
23+
24+
* <news item>

src/diffpy/morph/morph_io.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ def single_morph_output(
6767
)
6868

6969
morphs_out = "# Optimized morphing parameters:\n"
70+
# Handle special inputs
71+
if "squeeze" in morph_results:
72+
sq_dict = morph_results.pop("squeeze")
73+
rw_pos = list(morph_results.keys()).index("Rw")
74+
morph_results_list = list(morph_results.items())
75+
for idx, _ in enumerate(sq_dict):
76+
morph_results_list.insert(
77+
rw_pos + idx, (f"squeeze a{idx}", sq_dict[f"a{idx}"])
78+
)
79+
morph_results = dict(morph_results_list)
80+
# Normal inputs
7081
morphs_out += "\n".join(
7182
f"# {key} = {morph_results[key]:.6f}" for key in morph_results.keys()
7283
)

src/diffpy/morph/morphapp.py

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,26 +148,50 @@ def custom_error(self, msg):
148148
action="append",
149149
dest="exclude",
150150
metavar="MANIP",
151-
help="""Exclude a manipulation from refinement by name. This can
152-
appear multiple times.""",
151+
help=(
152+
"Exclude a manipulation from refinement by name. "
153+
"This can appear multiple times."
154+
),
153155
)
154156
group.add_option(
155157
"--scale",
156158
type="float",
157159
metavar="SCALE",
158-
help="Apply scale factor SCALE.",
160+
help=(
161+
"Apply scale factor SCALE. "
162+
"This multiplies the function ordinate by SCALE."
163+
),
159164
)
160165
group.add_option(
161166
"--stretch",
162167
type="float",
163168
metavar="STRETCH",
164-
help="Stretch PDF by a fraction STRETCH.",
169+
help=(
170+
"Stretch function grid by a fraction STRETCH. "
171+
"This multiplies the function grid by 1+STRETCH."
172+
),
173+
)
174+
group.add_option(
175+
"--squeeze",
176+
metavar="a0,a1,...,an",
177+
help=(
178+
"Squeeze function grid given a polynomial "
179+
"a0+a1*x+a2*x^2+...a_n*x^n."
180+
"n is dependent on the number of values in the "
181+
"user-inputted comma-separated list. "
182+
"When this option is enabled, --hshift is disabled. "
183+
"When n>1, --stretch is disabled. "
184+
"See online documentation for more information."
185+
),
165186
)
166187
group.add_option(
167188
"--smear",
168189
type="float",
169190
metavar="SMEAR",
170-
help="Smear peaks with a Gaussian of width SMEAR.",
191+
help=(
192+
"Smear peaks with a Gaussian of width SMEAR. "
193+
"This convolves the function with a Gaussian of width SMEAR."
194+
),
171195
)
172196
group.add_option(
173197
"--slope",
@@ -444,22 +468,37 @@ def single_morph(parser, opts, pargs, stdout_flag=True):
444468
chain.append(morphs.MorphRGrid())
445469
refpars = []
446470

471+
# Squeeze
472+
squeeze_poly_deg = -1
473+
if opts.squeeze is not None:
474+
squeeze_coeffs = opts.squeeze.strip().split(",")
475+
squeeze_dict_in = {}
476+
for idx, coeff in enumerate(squeeze_coeffs):
477+
squeeze_dict_in.update({f"a{idx}": float(coeff)})
478+
squeeze_poly_deg = len(squeeze_coeffs) - 1
479+
chain.append(morphs.MorphSqueeze())
480+
config["squeeze"] = squeeze_dict_in
481+
refpars.append("squeeze")
447482
# Scale
448483
if opts.scale is not None:
449484
scale_in = opts.scale
450485
chain.append(morphs.MorphScale())
451486
config["scale"] = scale_in
452487
refpars.append("scale")
453488
# Stretch
454-
if opts.stretch is not None:
489+
# Only enable stretch if squeeze is lower than degree 1
490+
if opts.stretch is not None and squeeze_poly_deg < 1:
455491
stretch_in = opts.stretch
456492
chain.append(morphs.MorphStretch())
457493
config["stretch"] = stretch_in
458494
refpars.append("stretch")
459495
# Shift
460-
if opts.hshift is not None or opts.vshift is not None:
496+
# Only enable hshift is squeeze is not enabled
497+
if (
498+
opts.hshift is not None and squeeze_poly_deg < 0
499+
) or opts.vshift is not None:
461500
chain.append(morphs.MorphShift())
462-
if opts.hshift is not None:
501+
if opts.hshift is not None and squeeze_poly_deg < 0:
463502
hshift_in = opts.hshift
464503
config["hshift"] = hshift_in
465504
refpars.append("hshift")
@@ -557,13 +596,23 @@ def single_morph(parser, opts, pargs, stdout_flag=True):
557596
chain[0] = morphs.Morph()
558597
chain(x_morph, y_morph, x_target, y_target)
559598

599+
# FOR FUTURE MAINTAINERS
600+
# Any new morph should have their input morph parameters updated here
560601
# Input morph parameters
561602
morph_inputs = {
562603
"scale": scale_in,
563604
"stretch": stretch_in,
564605
"smear": smear_in,
565606
}
566607
morph_inputs.update({"hshift": hshift_in, "vshift": vshift_in})
608+
# More complex input morph parameters are only displayed conditionally
609+
if opts.squeeze is not None:
610+
squeeze_coeffs = opts.squeeze.strip().split(",")
611+
squeeze_dict = {}
612+
for idx, coeff in enumerate(squeeze_coeffs):
613+
squeeze_dict.update({f"a{idx}": float(coeff)})
614+
for idx, _ in enumerate(squeeze_dict):
615+
morph_inputs.update({f"squeeze a{idx}": squeeze_dict[f"a{idx}"]})
567616

568617
# Output morph parameters
569618
morph_results = dict(config.items())

tests/test_morphio.py

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pathlib import Path
44

5+
import numpy as np
56
import pytest
67

78
from diffpy.morph.morphapp import (
@@ -27,6 +28,27 @@
2728
tssf = testdata_dir.joinpath("testsequence_serialfile.json")
2829

2930

31+
# Ignore PATH data when comparing files
32+
def ignore_path(line):
33+
# Lines containing FILE PATH data begin with '# from '
34+
if "# from " in line:
35+
return False
36+
# Lines containing DIRECTORY PATH data begin with '# with '
37+
if "# with " in line:
38+
return False
39+
return True
40+
41+
42+
def isfloat(s):
43+
"""True if s is convertible to float."""
44+
try:
45+
float(s)
46+
return True
47+
except ValueError:
48+
pass
49+
return False
50+
51+
3052
class TestApp:
3153
@pytest.fixture
3254
def setup(self):
@@ -46,16 +68,6 @@ def setup(self):
4668
return
4769

4870
def test_morph_outputs(self, setup, tmp_path):
49-
# Ignore PATH data when comparing files
50-
def ignore_path(line):
51-
# Lines containing FILE PATH data begin with '# from '
52-
if "# from " in line:
53-
return False
54-
# Lines containing DIRECTORY PATH data begin with '# with '
55-
if "# with " in line:
56-
return False
57-
return True
58-
5971
morph_file = self.testfiles[0]
6072
target_file = self.testfiles[-1]
6173

@@ -137,3 +149,46 @@ def ignore_path(line):
137149
generated = filter(ignore_path, gf)
138150
target = filter(ignore_path, tf)
139151
assert all(x == y for x, y in zip(generated, target))
152+
153+
def test_morph_squeeze_outputs(self, setup, tmp_path):
154+
# The file squeeze_morph has a squeeze and stretch applied
155+
morph_file = testdata_dir / "squeeze_morph.cgr"
156+
target_file = testdata_dir / "squeeze_target.cgr"
157+
sqr = tmp_path / "squeeze_morph_result.cgr"
158+
sqr_name = sqr.resolve().as_posix()
159+
# Note that stretch and hshift should not be considered
160+
(opts, _) = self.parser.parse_args(
161+
[
162+
"--scale",
163+
"2",
164+
"--squeeze",
165+
"0,-0.001,-0.0001,0.0001",
166+
"--stretch",
167+
"1",
168+
"--hshift",
169+
"1",
170+
"-s",
171+
sqr_name,
172+
"-n",
173+
"--verbose",
174+
]
175+
)
176+
pargs = [morph_file, target_file]
177+
single_morph(self.parser, opts, pargs, stdout_flag=False)
178+
179+
# Check squeeze morph generates the correct output
180+
with open(sqr) as mf:
181+
with open(target_file) as tf:
182+
morphed = filter(ignore_path, mf)
183+
target = filter(ignore_path, tf)
184+
for m, t in zip(morphed, target):
185+
m_row = m.split()
186+
t_row = t.split()
187+
assert len(m_row) == len(t_row)
188+
for idx, _ in enumerate(m_row):
189+
if isfloat(m_row[idx]) and isfloat(t_row[idx]):
190+
assert np.isclose(
191+
float(m_row[idx]), float(t_row[idx])
192+
)
193+
else:
194+
assert m_row[idx] == t_row[idx]

tests/testdata/squeeze_morph.cgr

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# PDF created by diffpy.morph
2+
# from PATH
3+
4+
# Labels: [r] [gr]
5+
0.000000000000000000e+00 0.000000000000000000e+00
6+
1.000000000000000056e-01 2.016607210740701817e-01
7+
2.000000000000000111e-01 4.012816425967311584e-01
8+
3.000000000000000444e-01 5.968384836782407721e-01
9+
4.000000000000000222e-01 7.863460390718853832e-01
10+
5.000000000000000000e-01 9.678774221035963965e-01
11+
6.000000000000000888e-01 1.139582805695501078e+00
12+
7.000000000000000666e-01 1.299707497430252046e+00
13+
8.000000000000000444e-01 1.446609189174991705e+00
14+
9.000000000000000222e-01 1.578774227495413296e+00
15+
1.000000000000000000e+00 1.694832757920656352e+00
16+
1.100000000000000089e+00 1.793572604043012886e+00
17+
1.200000000000000178e+00 1.873951751421987222e+00
18+
1.300000000000000044e+00 1.935109316274401969e+00
19+
1.400000000000000133e+00 1.976374889929940482e+00
20+
1.500000000000000000e+00 1.997276161968231323e+00
21+
1.600000000000000089e+00 1.997544737777092294e+00
22+
1.700000000000000178e+00 1.977120079923743390e+00
23+
1.800000000000000044e+00 1.936151517147448820e+00
24+
1.900000000000000133e+00 1.874998279892714503e+00
25+
2.000000000000000000e+00 1.794227537029879693e+00
26+
2.100000000000000089e+00 1.694610424671280313e+00
27+
2.200000000000000178e+00 1.577116074695904757e+00
28+
2.300000000000000266e+00 1.442903667646756372e+00
29+
2.400000000000000355e+00 1.293312551959283851e+00
30+
2.500000000000000000e+00 1.129850488905959738e+00
31+
2.600000000000000089e+00 9.541801000842509151e-01
32+
2.700000000000000178e+00 7.681036116097271771e-01
33+
2.800000000000000266e+00 5.735460062730138864e-01
34+
2.900000000000000355e+00 3.725367116437122150e-01
35+
3.000000000000000000e+00 1.671899683154590699e-01
36+
3.100000000000000089e+00 -4.031596196150075834e-02
37+
3.200000000000000178e+00 -2.477605737698468646e-01
38+
3.300000000000000266e+00 -4.529036928636547832e-01
39+
3.400000000000000355e+00 -6.535086474160737291e-01
40+
3.500000000000000000e+00 -8.473658233427887598e-01
41+
3.600000000000000089e+00 -1.032316381403495242e+00
42+
3.700000000000000178e+00 -1.206275905404040172e+00
43+
3.800000000000000266e+00 -1.367257744179736667e+00
44+
3.900000000000000355e+00 -1.513395805283929141e+00
45+
4.000000000000000000e+00 -1.642966555577477061e+00
46+
4.100000000000000533e+00 -1.754409983348725222e+00
47+
4.200000000000000178e+00 -1.846349278313989206e+00
48+
4.299999999999999822e+00 -1.917608989968017763e+00
49+
4.400000000000000355e+00 -1.967231431369953931e+00
50+
4.500000000000000000e+00 -1.994491104644685819e+00
51+
4.600000000000000533e+00 -1.998906936314116090e+00
52+
4.700000000000000178e+00 -1.980252125089067672e+00
53+
4.800000000000000711e+00 -1.938561421967258891e+00
54+
4.900000000000000355e+00 -1.874135682387076951e+00
55+
5.000000000000000000e+00 -1.787543552743151798e+00
56+
5.100000000000000533e+00 -1.679620178709657630e+00
57+
5.200000000000000178e+00 -1.551462850439542906e+00
58+
5.300000000000000711e+00 -1.404423529676374072e+00
59+
5.400000000000000355e+00 -1.240098235957646233e+00
60+
5.500000000000000000e+00 -1.060313303193321621e+00
61+
5.600000000000000533e+00 -8.671085537216786099e-01
62+
5.700000000000000178e+00 -6.627174741864847451e-01
63+
5.800000000000000711e+00 -4.495445159157776538e-01
64+
5.900000000000000355e+00 -2.301396815438895571e-01
65+
6.000000000000000000e+00 -7.170598996826864358e-03
66+
6.100000000000000533e+00 2.166076767888532317e-01
67+
6.200000000000000178e+00 4.383848554024750710e-01
68+
6.300000000000000711e+00 6.553292792208245121e-01
69+
6.400000000000000355e+00 8.646231160092439083e-01
70+
6.500000000000000000e+00 1.063498489850685624e+00
71+
6.600000000000000533e+00 1.249274133578965218e+00
72+
6.700000000000000178e+00 1.419392118491213939e+00
73+
6.800000000000000711e+00 1.571454193726408821e+00
74+
6.900000000000000355e+00 1.703257248921869360e+00
75+
7.000000000000000000e+00 1.812827400185563764e+00
76+
7.100000000000000533e+00 1.898452191616431639e+00
77+
7.200000000000000178e+00 1.958710403102522868e+00
78+
7.300000000000000711e+00 1.992498960420514109e+00
79+
7.400000000000000355e+00 1.999056456187634501e+00
80+
7.500000000000000000e+00 1.977982810349711684e+00
81+
7.600000000000000533e+00 1.929254626918742277e+00
82+
7.700000000000000178e+00 1.853235839800372542e+00
83+
7.800000000000000711e+00 1.750683284871877854e+00
84+
7.900000000000000355e+00 1.622746887964007678e+00
85+
8.000000000000000000e+00 1.470964218916802180e+00
86+
8.099999999999999645e+00 1.297249230133007636e+00
87+
8.200000000000001066e+00 1.103875073606781942e+00
88+
8.300000000000000711e+00 8.934509726687409614e-01
89+
8.400000000000000355e+00 6.688932129054430131e-01
90+
8.500000000000000000e+00 4.333904099623434036e-01
91+
8.599999999999999645e+00 1.903633091336342231e-01
92+
8.700000000000001066e+00 -5.658052847403799435e-02
93+
8.800000000000000711e+00 -3.036966972619735139e-01
94+
8.900000000000000355e+00 -5.471580191460774234e-01
95+
9.000000000000000000e+00 -7.831120606064566614e-01
96+
9.099999999999999645e+00 -1.007741344138793016e+00
97+
9.200000000000001066e+00 -1.217325414256249960e+00
98+
9.300000000000000711e+00 -1.408303836788730168e+00
99+
9.400000000000000355e+00 -1.577339138701817634e+00
100+
9.500000000000000000e+00 -1.721378636094831327e+00
101+
9.600000000000001421e+00 -1.837714052498993222e+00
102+
9.700000000000001066e+00 -1.924037800077388338e+00
103+
9.800000000000000711e+00 -1.978494784713575205e+00
104+
9.900000000000000355e+00 -1.999728603987454667e+00
105+
1.000000000000000000e+01 -1.986921036185803846e+00

0 commit comments

Comments
 (0)